Appointments
An appointment is a scheduled visit: a slot on the clinic calendar that ties a patient to a provider for a particular reason (wellness exam, dental, recheck, surgery, etc.). Appointments are the typical entry point for day-of workflows, and they’re the parent record that most SOAP notes are written against.
Appointment listings are heavily date-filtered in practice. Use the dateCreatedFrom / dateCreatedTo pair (max 1-month range) to scope queries and avoid the 10,000-record total cap; see Conventions for the full rules.
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 appointment. |
clientId | string | The owning client (paying customer). |
appointmentPatients | array | The patients on the appointment. An appointment can cover more than one pet for the same client, so there is no single patientId. |
providerId | string | The veterinarian or staff member assigned. |
startDate | string | ISO 8601 UTC scheduled start. |
endDate | string | ISO 8601 UTC scheduled end. |
appointmentTypeId | string | Reference to the appointment-type taxonomy. A null type means the slot is blocked off rather than booked. |
appointmentStatusId | string | Reference to the current status. Defaults to upcoming. |
appointmentCancellationReasonId | string | Set when the appointment was cancelled. |
visitReason | string | Free-text reason for visit. Becomes the initial complaint on the SOAP. |
schedulingMethod | string | How the appointment was booked. |
clinicId | string | The owning clinic. |
dateCreated | string | ISO 8601 UTC timestamp of creation. |
dateUpdated | string | ISO 8601 UTC timestamp of last update. |
isDeleted | boolean | Soft-delete flag. Note this is isDeleted here, not deleted. |
List / search
Section titled “List / search”/pav2/open-api-appointments Returns a paginated collection of appointments for the clinic. The most common pattern is a date-range query for “today” or “this week”; the example below shows the exact ISO 8601 timestamps to send.
dateCreatedFrom and dateCreatedTo filter on when the appointment record was created, not on the scheduled start time. The window must be 1 month or less, and the total result set across pages is capped at 10,000; if a clinic’s volume risks exceeding that, narrow the date range and page through smaller windows.
Example: today’s appointments
Section titled “Example: today’s appointments”For a clinic local to UTC, “today” (2026-05-22) is 2026-05-22T00:00:00Z through 2026-05-23T00:00:00Z. Adjust the bounds to your clinic’s local timezone as needed.
curl -X POST https://open-api.shepherd.vet/pav2/open-api-appointments \ -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": 100, "sort": "startDate|asc", "dateCreatedFrom": "2026-05-22T00:00:00Z", "dateCreatedTo": "2026-05-23T00:00:00Z", "embed": "patient,client,provider" }'import osfrom datetime import datetime, timedelta, timezoneimport requests
start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)end = start + timedelta(days=1)
resp = requests.post( "https://open-api.shepherd.vet/pav2/open-api-appointments", 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": 100, "sort": "startDate|asc", "dateCreatedFrom": start.strftime("%Y-%m-%dT%H:%M:%SZ"), "dateCreatedTo": end.strftime("%Y-%m-%dT%H:%M:%SZ"), "embed": "patient,client,provider", }, timeout=30,)resp.raise_for_status()for appt in resp.json()["item"]: print(appt["startDate"], appt["clientId"], appt.get("visitReason"))using System.Net.Http;using System.Net.Http.Json;
var start = DateTime.UtcNow.Date;var end = start.AddDays(1);
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-appointments", new { page = 1, rpp = 100, sort = "startDate|asc", dateCreatedFrom = start.ToString("yyyy-MM-ddTHH:mm:ssZ"), dateCreatedTo = end.ToString("yyyy-MM-ddTHH:mm:ssZ"), embed = "patient,client,provider", });response.EnsureSuccessStatusCode();<?php$start = (new DateTimeImmutable('today', new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z');$end = (new DateTimeImmutable('tomorrow', new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z');
$ch = curl_init('https://open-api.shepherd.vet/pav2/open-api-appointments');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' => 100, 'sort' => 'startDate|asc', 'dateCreatedFrom' => $start, 'dateCreatedTo' => $end, 'embed' => 'patient,client,provider', ]),]);$payload = json_decode(curl_exec($ch), true);curl_close($ch);const start = new Date();start.setUTCHours(0, 0, 0, 0);const end = new Date(start);end.setUTCDate(end.getUTCDate() + 1);
const response = await fetch('https://open-api.shepherd.vet/pav2/open-api-appointments', { 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: 100, sort: 'startDate|asc', dateCreatedFrom: start.toISOString().replace(/\.\d{3}Z$/, 'Z'), dateCreatedTo: end.toISOString().replace(/\.\d{3}Z$/, 'Z'), embed: 'patient,client,provider', }),});if (!response.ok) { throw new Error(`Shepherd API ${response.status}: ${await response.text()}`);}const payload = await response.json();Response
Section titled “Response”{ "item": [ { "id": "b96d21ae-435f-4ef1-8cf7-3efbc3a3649a", "clientId": "e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6", "appointmentPatients": [ { "patientId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789" } ], "providerId": "5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219", "startDate": "2026-05-22T14:30:00Z", "endDate": "2026-05-22T15:00:00Z", "appointmentTypeId": "8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4", "appointmentStatusId": "9f8e7d6c-5b4a-3210-a9f8-fedcba098765", "appointmentCancellationReasonId": null, "visitReason": "Annual wellness exam", "clinicId": "9890662f-52db-4a41-a5c1-b2e300d400b4", "dateCreated": "2026-05-22T09:11:00Z", "dateUpdated": "2026-05-22T09:11:00Z", "isDeleted": false } ], "totalRecords": 27, "page": 1, "recordsPerPage": 100, "sort": "startDate|asc", "searchQuery": null, "embed": "patient,client,provider", "links": []}Create
Section titled “Create”/pav2/open-api-appointments/write Creates a new appointment on the clinic calendar.
Required: startDate, endDate, and providerId. Everything else is optional, but the optional fields interact:
appointmentTypeIdcontrols the rest. Leave it null and the call blocks off time for the provider rather than booking a visit;clientIdandpatientIdscan only be set when a type is present.patientIdsis an array and requiresclientId. An appointment belongs to a client and can cover several of that client’s pets, so there is no singlepatientId.appointmentStatusIddefaults to upcoming when omitted.visitReasonbecomes the initial complaint on the resulting SOAP record.sendEmailNotification,sendSmsNotification, andsendFormEmailNotificationall default to false.
Example
Section titled “Example”curl -X POST https://open-api.shepherd.vet/pav2/open-api-appointments/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 '{ "startDate": "2026-05-22T14:30:00Z", "endDate": "2026-05-22T15:00:00Z", "providerId": "5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219", "appointmentTypeId": "8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4", "clientId": "e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6", "patientIds": ["a1b2c3d4-e5f6-7890-abcd-ef0123456789"], "visitReason": "Annual wellness exam" }'import osimport requests
resp = requests.post( "https://open-api.shepherd.vet/pav2/open-api-appointments/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={ "startDate": "2026-05-22T14:30:00Z", "endDate": "2026-05-22T15:00:00Z", "providerId": "5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219", "appointmentTypeId": "8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4", "clientId": "e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6", "patientIds": ["a1b2c3d4-e5f6-7890-abcd-ef0123456789"], "visitReason": "Annual wellness exam", }, timeout=30,)resp.raise_for_status()appointment = 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-appointments/write", new { startDate = "2026-05-22T14:30:00Z", endDate = "2026-05-22T15:00:00Z", providerId = "5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219", appointmentTypeId = "8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4", clientId = "e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6", patientIds = new[] { "a1b2c3d4-e5f6-7890-abcd-ef0123456789" }, visitReason = "Annual wellness exam", });response.EnsureSuccessStatusCode();<?php$ch = curl_init('https://open-api.shepherd.vet/pav2/open-api-appointments/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([ 'startDate' => '2026-05-22T14:30:00Z', 'endDate' => '2026-05-22T15:00:00Z', 'providerId' => '5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219', 'appointmentTypeId' => '8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4', 'clientId' => 'e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6', 'patientIds' => ['a1b2c3d4-e5f6-7890-abcd-ef0123456789'], 'visitReason' => 'Annual wellness exam', ]),]);$appointment = json_decode(curl_exec($ch), true);curl_close($ch);const response = await fetch('https://open-api.shepherd.vet/pav2/open-api-appointments/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({ startDate: '2026-05-22T14:30:00Z', endDate: '2026-05-22T15:00:00Z', providerId: '5a8d0cfc-c7c4-44a0-bdab-44f4e5f6b219', appointmentTypeId: '8fc9c6d1-a829-4b93-a5ea-1c83db96a1b4', clientId: 'e45a6e12-3a1c-4e7d-9fd2-3473a4d8a7a6', patientIds: ['a1b2c3d4-e5f6-7890-abcd-ef0123456789'], visitReason: 'Annual wellness exam', }),});if (!response.ok) { throw new Error(`Shepherd API ${response.status}: ${await response.text()}`);}const appointment = await response.json();Update
Section titled “Update”/pav2/open-api-appointments/{id}/update Updates an existing appointment. This is a PUT, so it is a full replacement: send the complete appointment 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 that keeps it safe.
Required on update: startDate, endDate, and providerId.
The remaining editable fields are clientId, patientIds, appointmentTypeId, appointmentStatusId, visitReason, appointmentCancellationReasonId, cancellationNote, sendEmailNotification, and sendSmsNotification. patientIds in particular is a collection: omit it and the appointment’s patient links are cleared, so read it back and resend it even when the patients have not changed.
Returns 200 OK with the updated appointment, not 201.
Sub-resources
Section titled “Sub-resources”Appointment-adjacent data lives on its own endpoints, each following the same list / write conventions as the parent resource.
| Endpoint | Purpose |
|---|---|
/pav2/open-api-appointment-notes/write | Free-form notes attached to an appointment. Write only; there is no list endpoint for appointment notes. |
/pav2/open-api-appointment-statuses | Status taxonomy (scheduled, checked-in, in-room, completed, no-show). |
/pav2/open-api-appointment-types | Type taxonomy (wellness, dental, surgery, recheck, etc.). |
/pav2/open-api-appointment-cancellation-reasons | Cancellation reason taxonomy used when an appointment is cancelled. |
See also
Section titled “See also”- Conventions: pagination, embed, sort, and the 1-month / 10,000-record limits.
- Patients: the pet the appointment is scheduled for.
- SOAP records: clinical notes written against an appointment.
- Errors: HTTP status codes and the two JSON error shapes.