Skip to content

Patients

A patient is the pet: a dog, cat, rabbit, parrot, or any other animal the clinic treats. Every patient belongs to one or more clients (the pet owners), and patients are the anchor for clinical workflows: appointments, SOAP records, vitals, prescriptions, imaging, and attached files all hang off a patient ID.

For file uploads, the API uses a three-step flow with an S3 presigned URL so binaries never transit the Shepherd application servers; see the file uploads section below.

Authoritative schemas live in the live Swagger UI. The fields below are a representative subset for orientation.

FieldTypeDescription
idstringUnique identifier for the patient.
namestringPatient name (e.g. “Biscuit”).
identifierstringShort human-facing identifier shown in the Shepherd UI.
breedIdstringReference to a breed from the breeds lookup.
sexIdstringReference to a patient sex from the sexes lookup.
statusIdstringReference to a patient status from the statuses lookup.
colorstringColour or markings, free text (e.g. “Brown”).
dateOfBirthstringDate of birth. Stored as a date; any time component is ignored.
microchipNumberstringMicrochip number, free text.
rabiesTagstringRabies tag number, free text.
alertsstringImportant handling notes surfaced in the chart.
additionalTextstringFree-text additional information.
clientsarrayOwning clients. Populated with embed=clients.
breed / patientSex / patientStatusobjectExpanded lookup records. null unless embedded.
dateDeceasedstringDate of death, or null.
dateCreatedstringISO 8601 UTC timestamp of creation.
dateUpdatedstringISO 8601 UTC timestamp of last update.
deletedbooleanSoft-delete flag.

Weight is not a scalar field on the patient either. It is a separate series of weight entries; one can be created alongside the patient (see Create) and further entries are recorded through the patient-weight endpoint.

POST /pav2/open-api-patients

Returns a paginated collection of patients for the clinic. Use searchQuery for free-text matching against name, microchip number, and rabies tag, embed to pull related data inline (for example embed: "clients,clients.clientPhones"), and the standard page, rpp, sort, and dateCreatedFrom / dateCreatedTo parameters from Conventions. Narrow by owner with clientIds, by status with patientStatusIds, or drop deceased patients with excludeDeceased.

Terminal window
curl -X POST https://open-api.shepherd.vet/pav2/open-api-patients \
-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": 50,
"sort": "name|asc",
"embed": "clients"
}'
{
"item": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"name": "Biscuit",
"identifier": "K2X9MV1",
"breedId": "1a2b3c4d-5e6f-789a-bcde-0123456789ab",
"sexId": "6a5b4c3d-2e1f-0a9b-8765-4321fedcba09",
"statusId": "9f8e7d6c-5b4a-3210-a9f8-fedcba098765",
"color": "Brown",
"dateOfBirth": "2019-04-12T00:00:00Z",
"microchipNumber": "985141001234567",
"rabiesTag": "R123456",
"dateDeceased": null,
"clients": [],
"breed": null,
"patientSex": null,
"patientStatus": null,
"dateCreated": "2026-03-14T18:22:11Z",
"dateUpdated": "2026-05-10T14:00:00Z",
"deleted": false
}
],
"totalRecords": 421,
"page": 1,
"recordsPerPage": 50,
"sort": "name|asc",
"searchQuery": null,
"embed": "clients",
"links": []
}

breed, patientSex, and patientStatus are the expanded lookup records. They stay null until you name them in embed; the *Id fields are always present.

POST /pav2/open-api-patients/write

Creates a new patient under an existing client.

Every reference field is a lookup ID, not a name. There is no free-text species, breed, or sex on this endpoint; resolve each one against its lookup first (see Resolving the lookup IDs below).

Required on create:

FieldNotes
nameThe patient’s name.
clientIdThe owning client. Must already exist.
breedIdFrom the breeds lookup. Determines the species.
sexIdFrom the patient-sexes lookup.
statusIdFrom the patient-statuses lookup.
dateOfBirthMust not be in the future. Stored as a date; any time component is ignored.

Everything else is optional: color, alerts, additionalText, microchipNumber, rabiesTag, discountId, patientCprstatusId, patientCPRStatusNote, preferredProviderIds, referralSourceIds, and patientWeight.

Unless the Shepherd account is configured as a group account, you do not need to supply a home clinic; the clinic in X-Clinic-Id is used. Default provider (preferredProviderIds) and every other field not listed above are optional.

breedId, sexId, and statusId must come from the lookups on the clinic’s own environment. Lookup IDs differ between sandbox and production, and between clinics for breeds, so IDs harvested from one environment will not validate in another. This is the most common cause of a rejected create.

FieldSourceScoping
breedIdPOST /pav2/open-api-breedsClinic-scoped. Pass getBreedsByClinicId: true for the clinic’s own list, and isLegacy: false to skip retired entries. Filter by speciesId to offer a species-then-breed picker.
sexIdPOST /pav2/open-api-patient-sexesNot clinic-scoped, and there is no legacy flag. The list is the same for every clinic in an environment.
statusIdPOST /pav2/open-api-patient-statusesNot clinic-scoped, and there is no legacy flag.

Cache these; they change rarely. See Lookups for the caching guidance.

Weight is not a field on the patient. To capture an intake weight at the same time as the record, include the nested patientWeight object; weight and weightUnitId are both required within it, and date and note are optional.

{
"patientWeight": {
"weight": 5.0,
"weightUnitId": "a28f2c7e-5f45-4a32-ae3f-4e87c49a47a3",
"date": "2026-08-20T10:12:45Z",
"note": "Intake weight"
}
}

A weight of zero or less is rejected, as is a value that fails validation for the unit you named.

Terminal window
curl -X POST https://open-api.shepherd.vet/pav2/open-api-patients/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 '{
"name": "Biscuit",
"clientId": "e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6",
"breedId": "f2c19d83-9f87-4f6f-a290-d08e59d7d29e",
"sexId": "5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219",
"statusId": "8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4",
"dateOfBirth": "2019-04-12T00:00:00Z",
"color": "Chocolate",
"patientWeight": {
"weight": 5.0,
"weightUnitId": "a28f2c7e-5f45-4a32-ae3f-4e87c49a47a3"
}
}'
PUT /pav2/open-api-patients/{id}/update

Updates an existing patient. This is a PUT, so it is a full replacement: send the complete patient representation, not just the fields you changed. Any editable field omitted from the body is cleared. See Updating records for the general rule and the read-merge-write pattern.

Required on update: name, breedId, sexId, statusId, dateOfBirth, and color. Note that color is required here even though it is optional on create.

Two differences from create are worth calling out:

  • clientId is not accepted. The patient’s client association cannot be changed through this endpoint, along with the home clinic, system identifiers, and deleted status.
  • patientWeight is not accepted. Weight entries are their own series; record new ones through the patient-weight endpoint rather than through the patient update.

Returns 200 OK with the updated patient, not 201. A 404 means the {id} does not resolve within the clinic named by X-Clinic-Id.

Terminal window
curl -X PUT https://open-api.shepherd.vet/pav2/open-api-patients/a1b2c3d4-e5f6-7890-abcd-ef0123456789/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 '{
"name": "Biscuit",
"breedId": "f2c19d83-9f87-4f6f-a290-d08e59d7d29e",
"sexId": "5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219",
"statusId": "8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4",
"dateOfBirth": "2019-04-12T00:00:00Z",
"color": "Chocolate",
"microchipNumber": "985141001234567",
"rabiesTag": "R123456",
"alerts": "Allergic to penicillin"
}'

Both write endpoints reject the whole request rather than partially applying it. When a create or update comes back 400, work down this list:

  1. Are all the required fields present? Create needs name, clientId, breedId, sexId, statusId, dateOfBirth. Update needs those minus clientId, plus color. A missing required field produces the generic “The request is invalid.” envelope described in Errors, which does not currently name the offending field.
  2. Are the lookup IDs from the right environment? Sandbox and production have entirely separate ID sets. An ID copied from production will not validate against sandbox, and vice versa.
  3. Is the breed from this clinic’s list and not retired? Query breeds with getBreedsByClinicId: true and isLegacy: false.
  4. Is dateOfBirth in the past? A future date of birth is rejected.
  5. Is the weight valid? If you sent patientWeight, weight must be greater than zero and must pass validation for the weightUnitId you named.
  6. Still stuck on a create? Include a valid patientWeight block; see the note under Create.

Note that unknown fields in the body are ignored rather than rejected, so a stray speciesId or a misspelled key will not itself cause a 400; it simply will not be applied.

Patient files (lab PDFs, imaging stills, intake forms) are uploaded in three steps:

  1. Request a presigned upload URL. POST /pav2/open-api-patients/file-presigned-upload-url takes patientId and fileName, and returns url (the short-lived S3 URL) and path (the key that identifies the object).
  2. PUT the binary directly to S3. Bypasses the Shepherd application servers entirely. Send the raw file bytes with the appropriate Content-Type (e.g. application/pdf, image/jpeg).
  3. Register the file entry. POST /pav2/open-api-patients/file-entry-write records patientId, fileName, and the path returned in step 1, so the file shows up in the patient chart.

For images, also send width and height on the step 3 request. They default to zero when omitted, which is rarely what you want for a stored image.

Terminal window
curl -X POST https://open-api.shepherd.vet/pav2/open-api-patients/file-presigned-upload-url \
-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 '{
"patientId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"fileName": "lab-results.pdf"
}'

This call goes to S3, not to the Shepherd API; do not send the integration headers. Use the URL returned in step 1 and match the Content-Type you registered.

Terminal window
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @lab-results.pdf
Terminal window
curl -X POST https://open-api.shepherd.vet/pav2/open-api-patients/file-entry-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 '{
"patientId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"path": "<path from step 1>",
"fileName": "lab-results"
}'

POST /pav2/open-api-patients/attachments returns the registered files for a patient, paginated with the same conventions as every other collection endpoint. Pass a patientId filter to scope to a single chart.

  • Conventions: pagination, embed, sort, and date-range rules.
  • Clients: the owners patients belong to.
  • Appointments: scheduled visits for a patient.
  • SOAP records: clinical notes captured during a visit.
  • Errors: HTTP status codes and the two JSON error shapes.