Clients
A client represents a pet owner: the human responsible for one or more patients (pets) at a clinic. Clients carry the billing relationship, the contact details, and the household-level preferences. Every patient in the Shepherd API is linked to at least one client, and most clinical and financial workflows start from a client lookup.
Clients have several related sub-resources (phones, notes, info, discount) that are managed through their own endpoints rather than as deep writes on the client object itself.
Schema
Section titled “Schema”Authoritative schemas live in the live Swagger UI. The fields below are a representative subset for orientation.
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the client. |
identifier | string | Short human-facing identifier shown in the Shepherd UI. |
firstName / middleName / lastName | string | Name parts. |
email | string | Primary email address. |
dateOfBirth | string | Client’s date of birth. |
clientPhones | array | Phone numbers. Populated with embed=clientPhones. |
clientInfo | object | The contact block, including the postal address. Populated with embed=clientInfo; there are no address fields on the client itself. |
clientCoOwner | object | Co-owner, with their own phone list. Populated with embed=clientCoOwner. |
clientStatusId / clientTypeId / genderId | string | Lookup references. |
clientDiscountId | string | Discount program applied to this client. |
authorizedAgents | array | People authorised to act for the client. |
isTaxExempt | boolean | Whether the client is exempt from taxes. |
clinicId | string | The owning clinic. |
legacyId | string | Identifier carried over from a previous practice-management system. |
dateCreated | string | ISO 8601 UTC timestamp of creation. |
dateUpdated | string | ISO 8601 UTC timestamp of last update. |
deleted | boolean | Soft-delete flag; filter on this in queries. |
List / search
Section titled “List / search”/pav2/open-api-clients Returns a paginated collection of clients for the clinic identified by X-Clinic-Id. Accepts the standard collection parameters: page, rpp, sort, searchQuery, embed, and the dateCreatedFrom / dateCreatedTo date-range pair. See Conventions for the full parameter rules.
Example
Section titled “Example”curl -X POST https://open-api.shepherd.vet/pav2/open-api-clients \ -H "Content-Type: application/json" \ -H "X-Integration-Public-Key: $SHEPHERD_PUBLIC_KEY" \ -H "X-Integration-Private-Key: $SHEPHERD_PRIVATE_KEY" \ -H "X-Clinic-Id: $SHEPHERD_CLINIC_ID" \ -d '{ "page": 1, "rpp": 25, "sort": "lastName|asc", "searchQuery": "smith", "embed": "clientPhones" }'import osimport requests
resp = requests.post( "https://open-api.shepherd.vet/pav2/open-api-clients", headers={ "Content-Type": "application/json", "X-Integration-Public-Key": os.environ["SHEPHERD_PUBLIC_KEY"], "X-Integration-Private-Key": os.environ["SHEPHERD_PRIVATE_KEY"], "X-Clinic-Id": os.environ["SHEPHERD_CLINIC_ID"], }, json={ "page": 1, "rpp": 25, "sort": "lastName|asc", "searchQuery": "smith", "embed": "clientPhones", }, timeout=30,)resp.raise_for_status()payload = resp.json()for client in payload["item"]: print(client["id"], client["firstName"], client["lastName"])using System.Net.Http;using System.Net.Http.Json;
using var http = new HttpClient();http.DefaultRequestHeaders.Add("X-Integration-Public-Key", Environment.GetEnvironmentVariable("SHEPHERD_PUBLIC_KEY"));http.DefaultRequestHeaders.Add("X-Integration-Private-Key", Environment.GetEnvironmentVariable("SHEPHERD_PRIVATE_KEY"));http.DefaultRequestHeaders.Add("X-Clinic-Id", Environment.GetEnvironmentVariable("SHEPHERD_CLINIC_ID"));
var response = await http.PostAsJsonAsync( "https://open-api.shepherd.vet/pav2/open-api-clients", new { page = 1, rpp = 25, sort = "lastName|asc", searchQuery = "smith", embed = "clientPhones", });response.EnsureSuccessStatusCode();var payload = await response.Content.ReadFromJsonAsync<JsonElement>();<?php$ch = curl_init('https://open-api.shepherd.vet/pav2/open-api-clients');curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-Integration-Public-Key: ' . getenv('SHEPHERD_PUBLIC_KEY'), 'X-Integration-Private-Key: ' . getenv('SHEPHERD_PRIVATE_KEY'), 'X-Clinic-Id: ' . getenv('SHEPHERD_CLINIC_ID'), ], CURLOPT_POSTFIELDS => json_encode([ 'page' => 1, 'rpp' => 25, 'sort' => 'lastName|asc', 'searchQuery' => 'smith', 'embed' => 'clientPhones', ]),]);$body = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if ($status >= 400) { throw new RuntimeException("Shepherd API $status: $body");}$payload = json_decode($body, true);const response = await fetch('https://open-api.shepherd.vet/pav2/open-api-clients', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Integration-Public-Key': process.env.SHEPHERD_PUBLIC_KEY, 'X-Integration-Private-Key': process.env.SHEPHERD_PRIVATE_KEY, 'X-Clinic-Id': process.env.SHEPHERD_CLINIC_ID, }, body: JSON.stringify({ page: 1, rpp: 25, sort: 'lastName|asc', searchQuery: 'smith', embed: 'clientPhones', }),});if (!response.ok) { throw new Error(`Shepherd API ${response.status}: ${await response.text()}`);}const payload = await response.json();Response
Section titled “Response”{ "item": [ { "id": "c_01HXYZ...", "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "clientPhones": [ { "id": "7d7ea1ee-3329-4b06-9966-b2f800c16f53", "countryId": "158d44c8-d163-4c27-8e77-b2b300f2522f", "phoneNumber": "5555550123", "phoneTypeId": "534494bb-d287-eb09-e90b-8a2b4e5b9398", "isPrimary": true } ], "dateCreated": "2026-03-14T18:22:11Z", "dateUpdated": "2026-05-01T09:14:00Z", "deleted": false } ], "totalRecords": 184, "page": 1, "recordsPerPage": 25, "sort": "lastName|asc", "searchQuery": "smith", "embed": "clientPhones", "links": []}Create
Section titled “Create”/pav2/open-api-clients/write Creates a new client record for the clinic.
Required: firstName, lastName, and clientPhones. The phone array is required by the model, so send it even if the client has no number on file; pass [] in that case.
Each entry in clientPhones requires countryId, phoneNumber, and phoneTypeId, all resolved from Lookups (open-api-countries and open-api-phone-types). isPrimary and phoneNumberName are optional.
A co-owner, the contact block, and authorized agents can be created in the same call via the clientCoOwner, clientInfo, and authorizedAgents objects. Notes and discounts are separate sub-resources.
Example
Section titled “Example”curl -X POST https://open-api.shepherd.vet/pav2/open-api-clients/write \ -H "Content-Type: application/json" \ -H "X-Integration-Public-Key: $SHEPHERD_PUBLIC_KEY" \ -H "X-Integration-Private-Key: $SHEPHERD_PRIVATE_KEY" \ -H "X-Clinic-Id: $SHEPHERD_CLINIC_ID" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "clientPhones": [ { "countryId": "158d44c8-d163-4c27-8e77-b2b300f2522f", "phoneNumber": "5555550123", "phoneTypeId": "534494bb-d287-eb09-e90b-8a2b4e5b9398", "isPrimary": true } ] }'import osimport requests
resp = requests.post( "https://open-api.shepherd.vet/pav2/open-api-clients/write", headers={ "Content-Type": "application/json", "X-Integration-Public-Key": os.environ["SHEPHERD_PUBLIC_KEY"], "X-Integration-Private-Key": os.environ["SHEPHERD_PRIVATE_KEY"], "X-Clinic-Id": os.environ["SHEPHERD_CLINIC_ID"], }, json={ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "clientPhones": [ { "countryId": "158d44c8-d163-4c27-8e77-b2b300f2522f", "phoneNumber": "5555550123", "phoneTypeId": "534494bb-d287-eb09-e90b-8a2b4e5b9398", "isPrimary": True, } ], }, timeout=30,)resp.raise_for_status()client = resp.json()using System.Net.Http;using System.Net.Http.Json;
using var http = new HttpClient();http.DefaultRequestHeaders.Add("X-Integration-Public-Key", Environment.GetEnvironmentVariable("SHEPHERD_PUBLIC_KEY"));http.DefaultRequestHeaders.Add("X-Integration-Private-Key", Environment.GetEnvironmentVariable("SHEPHERD_PRIVATE_KEY"));http.DefaultRequestHeaders.Add("X-Clinic-Id", Environment.GetEnvironmentVariable("SHEPHERD_CLINIC_ID"));
var response = await http.PostAsJsonAsync( "https://open-api.shepherd.vet/pav2/open-api-clients/write", new { firstName = "Jane", lastName = "Smith", email = "jane.smith@example.com", clientPhones = new[] { new { countryId = "158d44c8-d163-4c27-8e77-b2b300f2522f", phoneNumber = "5555550123", phoneTypeId = "534494bb-d287-eb09-e90b-8a2b4e5b9398", isPrimary = true, }, }, });response.EnsureSuccessStatusCode();<?php$ch = curl_init('https://open-api.shepherd.vet/pav2/open-api-clients/write');curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-Integration-Public-Key: ' . getenv('SHEPHERD_PUBLIC_KEY'), 'X-Integration-Private-Key: ' . getenv('SHEPHERD_PRIVATE_KEY'), 'X-Clinic-Id: ' . getenv('SHEPHERD_CLINIC_ID'), ], CURLOPT_POSTFIELDS => json_encode([ 'firstName' => 'Jane', 'lastName' => 'Smith', 'email' => 'jane.smith@example.com', 'clientPhones' => [[ 'countryId' => '158d44c8-d163-4c27-8e77-b2b300f2522f', 'phoneNumber' => '5555550123', 'phoneTypeId' => '534494bb-d287-eb09-e90b-8a2b4e5b9398', 'isPrimary' => true, ]], ]),]);$body = curl_exec($ch);curl_close($ch);$client = json_decode($body, true);const response = await fetch('https://open-api.shepherd.vet/pav2/open-api-clients/write', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Integration-Public-Key': process.env.SHEPHERD_PUBLIC_KEY, 'X-Integration-Private-Key': process.env.SHEPHERD_PRIVATE_KEY, 'X-Clinic-Id': process.env.SHEPHERD_CLINIC_ID, }, body: JSON.stringify({ firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com', clientPhones: [ { countryId: '158d44c8-d163-4c27-8e77-b2b300f2522f', phoneNumber: '5555550123', phoneTypeId: '534494bb-d287-eb09-e90b-8a2b4e5b9398', isPrimary: true, }, ], }),});if (!response.ok) { throw new Error(`Shepherd API ${response.status}: ${await response.text()}`);}const client = await response.json();Update
Section titled “Update”/pav2/open-api-clients/{id}/update Updates an existing client. This is a PUT, so it is a full replacement: send the complete client representation, not just the fields you changed. Any editable field omitted from the body is cleared. See Updating records for the general rule.
Required on every update: firstName, lastName, and clientPhones. Omitting any of them is rejected.
clientStatusId and the clientInfo contact block are preserved when omitted. The home clinic, system identifiers, and deleted status cannot be changed here. authorizedAgents is an edit list rather than a replacement: each entry adds an agent (omit id), edits one in place (include id), or removes one (set authorizedAgentIdForRemove with a removalNote); agents you do not reference are left alone.
Preserving co-owners and their phone numbers
Section titled “Preserving co-owners and their phone numbers”The most common data-loss report against this endpoint involves co-owners. clientCoOwner is a nested object that carries its own clientCoOwnerPhones array, so a body that includes the co-owner but not their phones removes those phone numbers.
Fetch the full co-owner record before you merge. There are two ways to get it:
Embed it in the client read. Ask for the co-owner and their phones by name, using the dot notation described in Embedding related entities:
{ "ids": "c_01HXYZ...", "embed": "clientCoOwner,clientCoOwner.clientCoOwnerPhones" }Or query the co-owner endpoint directly. POST /pav2/open-api-client-co-owners returns co-owner records with their phone information attached. Co-owners are keyed by the owning client’s ID, so passing that ID in ids returns the co-owner for that client:
{ "ids": "c_01HXYZ...", "embed": "client" }Either way you end up with the complete co-owner object to resubmit.
Example: read, merge, write
Section titled “Example: read, merge, write”# 1. Read the current client, embedding the nested collections you will resend.curl -X POST https://open-api.shepherd.vet/pav2/open-api-clients \ -H "Content-Type: application/json" \ -H "X-Integration-Public-Key: $SHEPHERD_PUBLIC_KEY" \ -H "X-Integration-Private-Key: $SHEPHERD_PRIVATE_KEY" \ -H "X-Clinic-Id: $SHEPHERD_CLINIC_ID" \ -d '{ "ids": "c_01HXYZ...", "embed": "clientPhones,clientCoOwner,clientCoOwner.clientCoOwnerPhones" }'
# 2. Merge your change into that full representation, then# 3. PUT the complete object back.curl -X PUT https://open-api.shepherd.vet/pav2/open-api-clients/c_01HXYZ.../update \ -H "Content-Type: application/json" \ -H "X-Integration-Public-Key: $SHEPHERD_PUBLIC_KEY" \ -H "X-Integration-Private-Key: $SHEPHERD_PRIVATE_KEY" \ -H "X-Clinic-Id: $SHEPHERD_CLINIC_ID" \ -d @merged-client.jsonimport osimport requests
BASE = "https://open-api.shepherd.vet/pav2"HEADERS = { "Content-Type": "application/json", "X-Integration-Public-Key": os.environ["SHEPHERD_PUBLIC_KEY"], "X-Integration-Private-Key": os.environ["SHEPHERD_PRIVATE_KEY"], "X-Clinic-Id": os.environ["SHEPHERD_CLINIC_ID"],}client_id = "c_01HXYZ..."
# 1. Read, embedding every nested collection you intend to resend.read = requests.post( f"{BASE}/open-api-clients", headers=HEADERS, json={ "ids": client_id, "embed": "clientPhones,clientCoOwner,clientCoOwner.clientCoOwnerPhones", }, timeout=30,)read.raise_for_status()current = read.json()["item"][0]
# 2. Merge: start from the full record, change only what you mean to.payload = { "firstName": current["firstName"], "lastName": current["lastName"], "email": "jane.newaddress@example.com", # the actual change "clientPhones": current.get("clientPhones") or [], "clientCoOwner": current.get("clientCoOwner"), # phones travel with it}
# 3. Write the complete object back.write = requests.put( f"{BASE}/open-api-clients/{client_id}/update", headers=HEADERS, json=payload, timeout=30,)write.raise_for_status()updated = write.json()using System.Net.Http;using System.Net.Http.Json;using System.Text.Json;
const string Base = "https://open-api.shepherd.vet/pav2";var clientId = "c_01HXYZ...";
using var http = new HttpClient();http.DefaultRequestHeaders.Add("X-Integration-Public-Key", Environment.GetEnvironmentVariable("SHEPHERD_PUBLIC_KEY"));http.DefaultRequestHeaders.Add("X-Integration-Private-Key", Environment.GetEnvironmentVariable("SHEPHERD_PRIVATE_KEY"));http.DefaultRequestHeaders.Add("X-Clinic-Id", Environment.GetEnvironmentVariable("SHEPHERD_CLINIC_ID"));
// 1. Read with embeds.var read = await http.PostAsJsonAsync($"{Base}/open-api-clients", new{ ids = clientId, embed = "clientPhones,clientCoOwner,clientCoOwner.clientCoOwnerPhones",});read.EnsureSuccessStatusCode();var current = (await read.Content.ReadFromJsonAsync<JsonElement>()) .GetProperty("item")[0];
// 2. Merge into the full representation.var payload = new{ firstName = current.GetProperty("firstName").GetString(), lastName = current.GetProperty("lastName").GetString(), email = "jane.newaddress@example.com", clientPhones = current.GetProperty("clientPhones"), clientCoOwner = current.GetProperty("clientCoOwner"),};
// 3. Write it back.var write = await http.PutAsJsonAsync($"{Base}/open-api-clients/{clientId}/update", payload);write.EnsureSuccessStatusCode();<?php$base = 'https://open-api.shepherd.vet/pav2';$clientId = 'c_01HXYZ...';$headers = [ 'Content-Type: application/json', 'X-Integration-Public-Key: ' . getenv('SHEPHERD_PUBLIC_KEY'), 'X-Integration-Private-Key: ' . getenv('SHEPHERD_PRIVATE_KEY'), 'X-Clinic-Id: ' . getenv('SHEPHERD_CLINIC_ID'),];
// 1. Read with embeds.$ch = curl_init("$base/open-api-clients");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode([ 'ids' => $clientId, 'embed' => 'clientPhones,clientCoOwner,clientCoOwner.clientCoOwnerPhones', ]),]);$current = json_decode(curl_exec($ch), true)['item'][0];curl_close($ch);
// 2. Merge into the full representation.$payload = [ 'firstName' => $current['firstName'], 'lastName' => $current['lastName'], 'email' => 'jane.newaddress@example.com', 'clientPhones' => $current['clientPhones'] ?? [], 'clientCoOwner' => $current['clientCoOwner'] ?? null,];
// 3. Write it back.$ch = curl_init("$base/open-api-clients/$clientId/update");curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($payload),]);$updated = json_decode(curl_exec($ch), true);curl_close($ch);const BASE = 'https://open-api.shepherd.vet/pav2';const clientId = 'c_01HXYZ...';const headers = { 'Content-Type': 'application/json', 'X-Integration-Public-Key': process.env.SHEPHERD_PUBLIC_KEY, 'X-Integration-Private-Key': process.env.SHEPHERD_PRIVATE_KEY, 'X-Clinic-Id': process.env.SHEPHERD_CLINIC_ID,};
// 1. Read, embedding every nested collection you intend to resend.const read = await fetch(`${BASE}/open-api-clients`, { method: 'POST', headers, body: JSON.stringify({ ids: clientId, embed: 'clientPhones,clientCoOwner,clientCoOwner.clientCoOwnerPhones', }),});if (!read.ok) throw new Error(`Shepherd API ${read.status}: ${await read.text()}`);const [current] = (await read.json()).item;
// 2. Merge: start from the full record, change only what you mean to.const payload = { firstName: current.firstName, lastName: current.lastName, email: 'jane.newaddress@example.com', clientPhones: current.clientPhones ?? [], clientCoOwner: current.clientCoOwner ?? null,};
// 3. Write the complete object back.const write = await fetch(`${BASE}/open-api-clients/${clientId}/update`, { method: 'PUT', headers, body: JSON.stringify(payload),});if (!write.ok) throw new Error(`Shepherd API ${write.status}: ${await write.text()}`);const updated = await write.json();Sub-resources
Section titled “Sub-resources”Client-adjacent data lives on its own endpoints. Each follows the same list / write conventions as the parent resource.
| Endpoint | Purpose |
|---|---|
/pav2/open-api-client-phones | Phone numbers attached to a client (mobile, home, work). |
/pav2/open-api-client-infos | Extended profile fields and household metadata. |
/pav2/open-api-client-notes | Free-form notes recorded by clinic staff against the client. |
/pav2/open-api-client-discounts | Discount programs and percentage adjustments applied to the client’s invoices. |
/pav2/open-api-client-co-owners | Co-owners attached to a client, with their phone numbers. Keyed by the owning client’s ID. |
See also
Section titled “See also”- Conventions: pagination, embed, sort, and date-range rules.
- Authentication: the three required headers.
- Errors: HTTP status codes and the two JSON error shapes.
- Patients: the pets attached to each client.