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.
Prerequisites
Section titled “Prerequisites”- A runtime for one of curl, Python 3, .NET 8, PHP 8, or Node.js 20+.
- 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.
1. Set environment variables
Section titled “1. Set environment variables”The examples below read three environment variables:
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/. The examples below point at production; change the base URL if you are on sandbox.
2. Build the request
Section titled “2. Build the request”You’ll call POST /pav2/open-api-appointments and filter on a one-day window using dateCreatedFrom and dateCreatedTo. Every endpoint in the Shepherd API is a POST, even reads; 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. The 1-month range cap is documented in Conventions, well above what we need here.
3. Call the API
Section titled “3. Call the API”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, \"dateCreatedFrom\": \"${TODAY}T00:00:00Z\", \"dateCreatedTo\": \"${TODAY}T23:59:59Z\", \"sort\": \"dateCreated|asc\" }"import os, requestsfrom datetime import datetime, timezone
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
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, "dateCreatedFrom": f"{today}T00:00:00Z", "dateCreatedTo": f"{today}T23:59:59Z", "sort": "dateCreated|asc", }, timeout=30,)
if not resp.ok: raise RuntimeError(f"Shepherd {resp.status_code}: {resp.text}")
data = resp.json()print(f"{data['totalRecords']} appointment(s) today")for appt in data["item"]: print(appt.get("dateCreated"), appt.get("id"))using System.Net.Http.Json;using System.Text.Json;
var publicKey = Environment.GetEnvironmentVariable("SHEPHERD_PUBLIC_KEY")!;var privateKey = Environment.GetEnvironmentVariable("SHEPHERD_PRIVATE_KEY")!;var clinicId = Environment.GetEnvironmentVariable("SHEPHERD_CLINIC_ID")!;var today = DateTime.UtcNow.ToString("yyyy-MM-dd");
using var http = new HttpClient();http.DefaultRequestHeaders.Add("X-Integration-Public-Key", publicKey);http.DefaultRequestHeaders.Add("X-Integration-Private-Key", privateKey);http.DefaultRequestHeaders.Add("X-Clinic-Id", clinicId);
var body = JsonContent.Create(new{ page = 1, rpp = 100, dateCreatedFrom = $"{today}T00:00:00Z", dateCreatedTo = $"{today}T23:59:59Z", sort = "dateCreated|asc",});
var resp = await http.PostAsync( "https://open-api.shepherd.vet/pav2/open-api-appointments", body);var text = await resp.Content.ReadAsStringAsync();if (!resp.IsSuccessStatusCode) throw new HttpRequestException($"Shepherd {(int)resp.StatusCode}: {text}");
using var doc = JsonDocument.Parse(text);var root = doc.RootElement;Console.WriteLine($"{root.GetProperty("totalRecords").GetInt32()} appointment(s) today");foreach (var appt in root.GetProperty("item").EnumerateArray()){ Console.WriteLine($"{appt.GetProperty("dateCreated")} {appt.GetProperty("id")}");}<?php$today = gmdate('Y-m-d');
$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, 'dateCreatedFrom' => "{$today}T00:00:00Z", 'dateCreatedTo' => "{$today}T23:59:59Z", 'sort' => 'dateCreated|asc', ]),]);$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("Shepherd $status: $response");}
$data = json_decode($response, true);echo $data['totalRecords'] . " appointment(s) today\n";foreach ($data['item'] as $appt) { echo $appt['dateCreated'] . "\t" . $appt['id'] . "\n";}const today = new Date().toISOString().slice(0, 10);
const resp = 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, dateCreatedFrom: `${today}T00:00:00Z`, dateCreatedTo: `${today}T23:59:59Z`, sort: 'dateCreated|asc', }),});
if (!resp.ok) throw new Error(`Shepherd ${resp.status}: ${await resp.text()}`);const data = await resp.json();console.log(`${data.totalRecords} appointment(s) today`);for (const appt of data.item) { console.log(appt.dateCreated, appt.id);}4. Read the response
Section titled “4. Read the response”The response is a collection envelope (see Conventions):
{ "item": [ /* appointment records */ ], "totalRecords": 17, "page": 1, "recordsPerPage": 100, "sort": "dateCreated|asc", "searchQuery": null, "embed": null, "links": []}Iterate item[] to process each appointment. If totalRecords is larger than recordsPerPage, increment page and call again. The 10,000-item paging ceiling applies; for a single day’s appointments at any normal clinic you’ll never get close.
What to try next
Section titled “What to try next”- 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 body is JSON, but not always the same shape. See Errors.