API Reference
The SightSync REST API lets you push patient records, trigger AI recall calls, and pull appointment outcomes — directly from your practice management system.
Authentication
All requests must include a valid API key in the Authorization header as a Bearer token. API keys are generated in Dashboard → Settings → API Keys.
Authorization: Bearer nvc_live_xxxxxxxxxxxxxxxxxxxx
Base URL
https://app.sightsync.io/api/v1
All endpoints use HTTPS. HTTP requests are redirected. Your API key authenticates to your practice account — all data is isolated by practice.
Rate limits
Each API key is limited to 120 requests per minute across all endpoints combined.
Exceeding the limit returns 429 Too Many Requests. The response includes a Retry-After header with the number of seconds to wait, and a reset ISO timestamp. Recall triggers additionally enforce UK calling regulations (see Compliance).
Errors
All error responses are JSON with an error string field.
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid fields |
| 401 | Unauthorised — invalid or missing API key |
| 402 | Payment required — subscription not active |
| 404 | Not found — patient or resource does not exist |
| 409 | Conflict — patient has opted out |
| 422 | Unprocessable — e.g. invalid UK phone number |
| 425 | Too early — outside OFCOM-permitted calling hours |
| 429 | Too many requests — rate limit or call cap reached |
| 451 | Unavailable for legal reasons — TPS/CTPS registered |
| 500 | Internal error — please report to dev@sightsync.io |
Patients
Add or update a patient
/patientsUpsert a patient recordCreates a new patient or updates an existing one if the phone number already exists for this practice. Conflict key: practice_id + phone_number.
Body parameters
| first_name* | string | Patient's first name. |
| last_name | string | Patient's surname. |
| phone_number* | string | UK mobile or landline. Accepted formats: 07700900000, +447700900000, 020 7946 0000. |
| string | Patient email address (optional). | |
| last_eye_test_date | date | ISO 8601 date of last eye examination. Used to calculate recall due date. |
| risk_category | string | One of: standard, diabetic, glaucoma_suspect, myopia_child, contact_lens, other_clinical. Defaults to standard. |
| clinical_notes | string | Free-text clinical notes passed to the AI for context. |
POST /api/v1/patients
Authorization: Bearer nvc_live_...
{
"first_name": "Jane",
"last_name": "Smith",
"phone_number": "07700900001",
"last_eye_test_date": "2024-01-15",
"risk_category": "diabetic"
}
// 200 OK
{
"success": true,
"patient": {
"id": "pat_01HXYZ...",
"first_name": "Jane",
"last_name": "Smith",
"phone_number": "+447700900001",
"risk_category": "diabetic",
"last_eye_test_date": "2024-01-15",
"next_clinical_due_date": "2025-01-15",
"opted_out": false,
"created_at": "2026-03-11T09:00:00Z",
"updated_at": "2026-03-11T09:00:00Z"
}
}Get a single patient
/patients/{id}Fetch a single patient recordGET /api/v1/patients/pat_01HXYZ...
// 200 OK
{
"patient": {
"id": "pat_01HXYZ...",
"first_name": "Jane", "last_name": "Smith",
"phone_number": "+447700900001",
"risk_category": "diabetic",
"last_eye_test_date": "2024-01-15",
"opted_out": false,
"opted_out_at": null,
"created_at": "2026-03-01T09:00:00Z"
}
}Update a patient
/patients/{id}Update specific patient fieldsOnly fields included in the body are updated. All fields are optional.
Body parameters
| first_name | string | Patient's first name. |
| last_name | string | Patient's surname. |
| phone_number | string | UK mobile or landline. |
| string | Email address. | |
| last_eye_test_date | date | ISO 8601 date of last eye examination. |
| risk_category | string | standard | diabetic | glaucoma_suspect | myopia_child | contact_lens | other_clinical |
| clinical_notes | string | Free-text clinical notes. |
PATCH /api/v1/patients/pat_01HXYZ...
Authorization: Bearer nvc_live_...
{ "risk_category": "glaucoma_suspect", "last_eye_test_date": "2025-06-01" }
// 200 OK
{ "success": true, "patient": { ... } }Delete a patient (GDPR erasure)
/patients/{id}Erase all patient PII (right to erasure)Permanently anonymises all personal data for this patient — name, phone number, email, and date of birth are replaced with anonymised values. Call transcripts are cleared. The patient record itself is retained (with anonymised data) to preserve the audit trail. Opt-out status is retained indefinitely as required by law.
This action cannot be undone.
DELETE /api/v1/patients/pat_01HXYZ...
// 200 OK
{ "success": true, "message": "Patient PII permanently erased." }List patients
/patientsList patients due for recallQuery parameters
| limit | integer | Max results to return. Default 100, max 500. |
| offset | integer | Pagination offset. Default 0. |
| risk_category | string | Filter by risk category. |
| overdue_only | boolean | If true, only return patients whose recall is past due. |
GET /api/v1/patients?overdue_only=true&risk_category=diabetic&limit=50
// 200 OK
{
"patients": [ ... ],
"total": 23,
"limit": 50,
"offset": 0
}Recall
Trigger a recall call
/recallTrigger an AI recall call for a patientInitiates a compliant AI voice call to the specified patient. The call is subject to OFCOM hours (Mon–Fri 9am–6pm UK), TPS/CTPS screening, 24h call spacing, and the practice's monthly call quota.
Returns 202 Accepted once the call has been dispatched to the telephony provider. Use the call_id to track status via webhooks.
Body parameters
| patient_id* | string | The patient's ID (returned by POST /patients). |
POST /api/v1/recall
Authorization: Bearer nvc_live_...
{ "patient_id": "pat_01HXYZ..." }
// 202 Accepted
{
"success": true,
"call_id": "c_01HABC...",
"patient_id": "pat_01HXYZ...",
"status": "calling"
}
// 425 — Outside OFCOM hours
{
"error": "Outside permitted calling hours",
"reason": "Calls permitted Mon–Fri 9am–6pm UK time only",
"next_allowed_at": "2026-03-12T09:00:00Z"
}
// 451 — TPS registered
{ "error": "Patient number is registered on the TPS/CTPS — call blocked per PECR regulations" }
// 429 — Monthly call quota
{ "error": "Monthly call limit reached", "calls_used": 300, "monthly_limit": 300 }Batch recall
/recall/batchTrigger AI recall calls for up to 50 patients at onceRuns full compliance checks on every patient before triggering. Returns two arrays: triggered (calls placed) and skipped (with reason per patient). The entire batch does not fail if individual patients are TPS-registered or opted out — they are simply included in skipped.
Body parameters
| patient_ids* | string[] | Array of patient IDs. Maximum 50 per request. |
| dry_run | boolean | If true, runs all compliance checks but places no actual calls. Use to preview which patients would be skipped. Default: false. |
POST /api/v1/recall/batch
Authorization: Bearer nvc_live_...
{
"patient_ids": ["pat_01H...", "pat_02H...", "pat_03H..."],
"dry_run": false
}
// 202 Accepted
{
"triggered": [
{ "patient_id": "pat_01H...", "call_id": "c_01H..." },
{ "patient_id": "pat_02H...", "call_id": "c_02H..." }
],
"skipped": [
{ "patient_id": "pat_03H...", "reason": "TPS registered" }
],
"total": 3,
"triggered_count": 2,
"skipped_count": 1
}Calls
List calls
/callsList call logs for your practiceQuery parameters
| limit | integer | Max results. Default 50, max 200. |
| offset | integer | Pagination offset. Default 0. |
| patient_id | string | Filter by a specific patient. |
| status | string | Filter by call status: answered | voicemail | no_answer | booked | opted_out | failed |
| from | date | ISO 8601 — calls on or after this date. |
| to | date | ISO 8601 — calls up to and including this date. |
GET /api/v1/calls?status=booked&limit=10
// 200 OK
{
"calls": [
{
"id": "c_01H...",
"patient_id": "pat_01H...",
"call_status": "booked",
"duration": 74,
"appointment_booked": true,
"ai_summary": "Patient agreed to book — Thursday 2pm confirmed.",
"recording_url": "https://...",
"created_at": "2026-03-15T10:22:00Z",
"patient": { "first_name": "Jane", "last_name": "Smith" }
}
],
"total": 1,
"limit": 10,
"offset": 0
}Get a single call
/calls/{id}Get call status and full outcomePoll this endpoint after POST /recall to check if the call has completed. The is_complete field is true once the call reaches a terminal state.
GET /api/v1/calls/c_01HABC...
// 200 OK
{
"id": "c_01HABC...",
"patient_id": "pat_01HXYZ...",
"call_status": "booked",
"duration": 74,
"appointment_booked": true,
"appointment_booked_at": "2026-03-15T10:23:14Z",
"ai_summary": "Patient agreed to book Thursday 2pm.",
"call_outcome_notes": null,
"recording_url": "https://...",
"is_complete": true,
"created_at": "2026-03-15T10:22:00Z",
"patient": { "first_name": "Jane", "last_name": "Smith" }
}Appointments
List appointments
/appointmentsList appointments booked via AI recallQuery parameters
| limit | integer | Max results. Default 100, max 500. |
| offset | integer | Pagination offset. Default 0. |
| status | string | Filter by status: booked | confirmed | cancelled | no_show. |
| from_date | date | ISO 8601 — appointments on or after this date. |
| to_date | date | ISO 8601 — appointments up to and including this date. |
| patient_id | string | Filter by a specific patient. |
GET /api/v1/appointments?from_date=2026-03-01&status=booked
// 200 OK
{
"appointments": [
{
"id": "apt_01H...",
"patient_id": "pat_01H...",
"appointment_date": "2026-03-15",
"appointment_time": "10:30",
"status": "booked",
"patients": {
"first_name": "Jane",
"last_name": "Smith",
"phone_number": "+447700900001",
"risk_category": "diabetic"
},
"created_at": "2026-03-11T09:15:00Z"
}
],
"total": 1,
"limit": 100,
"offset": 0
}Record a manual appointment
/appointmentsRecord a manually booked appointmentUse this to push appointments booked outside SightSync (e.g. via your PMS) into the platform for reporting and webhook delivery.
Body parameters
| patient_id* | string | The patient's SightSync ID. |
| appointment_date* | date | ISO 8601 date of the appointment. |
| appointment_time | string | 24-hour time string, e.g. '14:30'. |
| status | string | Initial status: booked (default) | confirmed. |
| call_id | string | Link this appointment to a specific call, if applicable. |
POST /api/v1/appointments
Authorization: Bearer nvc_live_...
{
"patient_id": "pat_01HXYZ...",
"appointment_date": "2026-04-15",
"appointment_time": "14:30",
"status": "booked"
}
// 201 Created
{
"success": true,
"appointment": {
"id": "apt_01H...",
"patient_id": "pat_01HXYZ...",
"appointment_date": "2026-04-15",
"appointment_time": "14:30",
"status": "booked",
"created_at": "2026-03-19T11:00:00Z"
}
}Usage
Get usage stats
/usageCurrent billing period usageReturns quota consumption, overage, and a daily call breakdown for the current calendar month.
GET /api/v1/usage
// 200 OK
{
"period": { "start": "2026-03-01", "end": "2026-03-31" },
"plan": "growth",
"status": "active",
"calls": {
"used": 312,
"limit": 750,
"remaining": 438,
"overage": 0
},
"daily": [
{ "date": "2026-03-01", "total": 18, "completed": 15, "failed": 3 },
{ "date": "2026-03-02", "total": 24, "completed": 21, "failed": 3 }
]
}Compliance & regulations
The API automatically enforces UK telecommunications regulations on every recall trigger. You do not need to implement these checks yourself.
OFCOM permitted hours
Calls are only placed Monday–Friday, 9am–6pm UK time. Requests outside these hours return 425 with a next_allowed_at timestamp.
TPS / CTPS screening
Every number is checked against the Telephone Preference Service and Corporate TPS registers before a call is placed. Registered numbers return 451 and the patient is flagged in your dashboard.
24-hour call spacing
A minimum 24-hour gap is enforced between calls to the same patient. Requests that would violate this return 429 with a next_allowed_at ISO timestamp.
30-day call cap
No more than 3 calls are placed to any patient within a rolling 30-day window, per UK direct marketing guidance.
Opt-out handling
Patients who have opted out (via keypress during a call, SMS reply, or the opt-out portal) cannot be triggered. Attempts return 409.
Webhooks
Outbound webhooks deliver real-time events to your endpoint — no polling required. Register a webhook URL via the Dashboard → Developer → Webhooks tab or via the REST API below.
Events supported:
call.completed— call ended, includes transcript summary and outcomecall.failed— call could not be placed (busy, no-answer, carrier error)appointment.booked— patient confirmed an appointment date/timeappointment.confirmed— appointment confirmed via follow-upappointment.cancelled— appointment cancelledappointment.no_show— patient did not attendpatient.created— new patient record addedpatient.updated— patient record updatedpatient.deleted— patient PII erased (GDPR)
Every delivery is signed with X-SightSync-Signature: sha256=HMAC(secret, body). Verify it using your endpoint secret shown once at registration.
List webhook endpoints
/webhooksList all registered webhook endpointsGET /api/v1/webhooks
// 200 OK
{
"endpoints": [
{
"id": "wh_01H...",
"url": "https://your-pms.example/webhooks/sightsync",
"events": ["call.completed", "appointment.booked"],
"is_active": true,
"created_at": "2026-03-01T09:00:00Z",
"last_delivery_at": "2026-03-19T10:30:00Z",
"consecutive_failures": 0
}
]
}Register a webhook endpoint
/webhooksRegister a new webhook endpointOn success, the response includes the secret used to sign deliveries. Store it securely — it is shown only once.
Body parameters
| url* | string | HTTPS URL to deliver events to. Must respond 2xx within 10 seconds. |
| events* | string[] | Array of event types to subscribe to. Use ['*'] to subscribe to all events. |
POST /api/v1/webhooks
Authorization: Bearer nvc_live_...
{
"url": "https://your-pms.example/webhooks/sightsync",
"events": ["call.completed", "appointment.booked"]
}
// 201 Created
{
"id": "wh_01H...",
"url": "https://your-pms.example/webhooks/sightsync",
"events": ["call.completed", "appointment.booked"],
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"is_active": true,
"created_at": "2026-03-19T11:00:00Z"
}Remove a webhook endpoint
/webhooks/{id}Remove a registered webhook endpointDELETE /api/v1/webhooks/wh_01H...
// 200 OK
{ "success": true }GDPR data requests
Manage data subject requests (DSRs) — access requests and erasure requests — as required under UK GDPR. Erasure requests trigger the same anonymisation as DELETE /patients/{id}.
List data requests
/data-requestsList all GDPR data subject requestsGET /api/v1/data-requests
// 200 OK
{
"requests": [
{
"id": "dsr_01H...",
"patient_id": "pat_01H...",
"type": "erasure",
"status": "completed",
"submitted_at": "2026-03-10T09:00:00Z",
"completed_at": "2026-03-10T09:01:00Z"
}
]
}Submit a data request
/data-requestsSubmit a GDPR data subject requestBody parameters
| patient_id* | string | The patient's SightSync ID. |
| type* | string | Request type: access (export patient data) | erasure (right to be forgotten). |
POST /api/v1/data-requests
Authorization: Bearer nvc_live_...
{ "patient_id": "pat_01HXYZ...", "type": "erasure" }
// 202 Accepted
{
"id": "dsr_01H...",
"patient_id": "pat_01HXYZ...",
"type": "erasure",
"status": "processing"
}AI agent
The agent endpoint lets you describe a recall campaign in plain English. The AI interprets your goal, selects eligible patients, runs compliance checks, and triggers calls — without you needing to manage individual patient IDs.
Run an agentic recall campaign
/agentTrigger a plain-English AI recall campaignBody parameters
| goal* | string | Plain-English description of who to contact and why. E.g. 'Call all diabetic patients overdue for an eye test by more than 12 months'. |
| dry_run | boolean | If true, returns the patient selection and compliance preview without placing any calls. Default: false. |
| limit | integer | Maximum number of patients to contact in this run. Default: 50. |
POST /api/v1/agent
Authorization: Bearer nvc_live_...
{
"goal": "Call all diabetic patients overdue by more than 12 months",
"dry_run": false,
"limit": 30
}
// 202 Accepted
{
"success": true,
"campaign_id": "cmp_01H...",
"patients_selected": 14,
"calls_triggered": 12,
"skipped": [
{ "patient_id": "pat_03H...", "reason": "TPS registered" },
{ "patient_id": "pat_07H...", "reason": "Opted out" }
],
"ai_reasoning": "Selected 14 diabetic patients whose last_eye_test_date is more than 12 months ago."
}