Skip to content

Your first integration

By the end of this tutorial you’ll have made an authenticated request to the Shepherd API and listed today’s appointments for a single clinic. Plan on about ten minutes.

  • A runtime for one of curl, Python 3.11+, .NET 10, PHP 8.3+, or Node.js 24+.
  • Sandbox or production credentials from your Shepherd account manager. If you don’t have them, see Authentication.
  • A clinic UUID your integration has been granted access to.

The examples below read three environment variables:

Terminal window
export SHEPHERD_PUBLIC_KEY="pk_..."
export SHEPHERD_PRIVATE_KEY="sk_..."
export SHEPHERD_CLINIC_ID="00000000-0000-0000-0000-000000000000"

Use sandbox keys with https://demo-open-api.shepherd.vet/pav2/, or production keys with https://open-api.shepherd.vet/pav2/. Develop against the sandbox. The examples below point at production; change the base URL if you are on sandbox, and never mix keys and hosts from different environments.

You’ll call POST /pav2/open-api-appointments and filter on a one-day window using dateFrom and dateTo, which match appointments by their scheduled time. (The similar-looking dateCreatedFrom and dateCreatedTo match when the booking was made, which is a different question.) Reads in the Shepherd API are POSTs; the filter goes in the JSON body.

For “today” in UTC, the window runs from 00:00:00Z to 23:59:59Z. If your clinic operates in a different timezone, adjust the bounds; the API stores timestamps in UTC. Keep date windows to one calendar month or less (see Conventions); one day is well inside that.

Terminal window
TODAY=$(date -u +%Y-%m-%d)
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,
\"dateFrom\": \"${TODAY}T00:00:00Z\",
\"dateTo\": \"${TODAY}T23:59:59Z\",
\"sort\": \"startDate|asc\"
}"

The response is a collection envelope (see Conventions):

{
"item": [ /* appointment records */ ],
"totalRecords": 17,
"page": 1,
"recordsPerPage": 100,
"sort": "startDate|asc",
"searchQuery": null,
"embed": null,
"links": []
}

Iterate item[] to process each appointment. If totalRecords is larger than page * recordsPerPage, increment page and call again; stop when you have read totalRecords records or a page comes back empty. Appointments that were deleted are included; send "isDeleted": false if you only want live ones.

  • Inline related records (clients, patients, providers) with embed. See Conventions.
  • Read the Appointments resource reference for the full request and response shape.
  • When something goes wrong, the error body is not always the same shape, and not every failure should be retried. See Errors.