DocsAPI Reference

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
Keep your API key secret. Never expose it in client-side code, public repositories, or URLs. Rotate it immediately in Settings if you believe it has been compromised.

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.

StatusMeaning
400Bad request — missing or invalid fields
401Unauthorised — invalid or missing API key
402Payment required — subscription not active
404Not found — patient or resource does not exist
409Conflict — patient has opted out
422Unprocessable — e.g. invalid UK phone number
425Too early — outside OFCOM-permitted calling hours
429Too many requests — rate limit or call cap reached
451Unavailable for legal reasons — TPS/CTPS registered
500Internal error — please report to dev@sightsync.io

Patients

Add or update a patient

POST/patientsUpsert a patient record

Creates 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*stringPatient's first name.
last_namestringPatient's surname.
phone_number*stringUK mobile or landline. Accepted formats: 07700900000, +447700900000, 020 7946 0000.
emailstringPatient email address (optional).
last_eye_test_datedateISO 8601 date of last eye examination. Used to calculate recall due date.
risk_categorystringOne of: standard, diabetic, glaucoma_suspect, myopia_child, contact_lens, other_clinical. Defaults to standard.
clinical_notesstringFree-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

GET/patients/{id}Fetch a single patient record
GET /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

PATCH/patients/{id}Update specific patient fields

Only fields included in the body are updated. All fields are optional.

Body parameters

first_namestringPatient's first name.
last_namestringPatient's surname.
phone_numberstringUK mobile or landline.
emailstringEmail address.
last_eye_test_datedateISO 8601 date of last eye examination.
risk_categorystringstandard | diabetic | glaucoma_suspect | myopia_child | contact_lens | other_clinical
clinical_notesstringFree-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)

DELETE/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

GET/patientsList patients due for recall

Query parameters

limitintegerMax results to return. Default 100, max 500.
offsetintegerPagination offset. Default 0.
risk_categorystringFilter by risk category.
overdue_onlybooleanIf 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

POST/recallTrigger an AI recall call for a patient

Initiates 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*stringThe 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

POST/recall/batchTrigger AI recall calls for up to 50 patients at once

Runs 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_runbooleanIf 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

GET/callsList call logs for your practice

Query parameters

limitintegerMax results. Default 50, max 200.
offsetintegerPagination offset. Default 0.
patient_idstringFilter by a specific patient.
statusstringFilter by call status: answered | voicemail | no_answer | booked | opted_out | failed
fromdateISO 8601 — calls on or after this date.
todateISO 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

GET/calls/{id}Get call status and full outcome

Poll 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

GET/appointmentsList appointments booked via AI recall

Query parameters

limitintegerMax results. Default 100, max 500.
offsetintegerPagination offset. Default 0.
statusstringFilter by status: booked | confirmed | cancelled | no_show.
from_datedateISO 8601 — appointments on or after this date.
to_datedateISO 8601 — appointments up to and including this date.
patient_idstringFilter 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

POST/appointmentsRecord a manually booked appointment

Use this to push appointments booked outside SightSync (e.g. via your PMS) into the platform for reporting and webhook delivery.

Body parameters

patient_id*stringThe patient's SightSync ID.
appointment_date*dateISO 8601 date of the appointment.
appointment_timestring24-hour time string, e.g. '14:30'.
statusstringInitial status: booked (default) | confirmed.
call_idstringLink 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

GET/usageCurrent billing period usage

Returns 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 outcome
  • call.failed — call could not be placed (busy, no-answer, carrier error)
  • appointment.booked — patient confirmed an appointment date/time
  • appointment.confirmed — appointment confirmed via follow-up
  • appointment.cancelled — appointment cancelled
  • appointment.no_show — patient did not attend
  • patient.created — new patient record added
  • patient.updated — patient record updated
  • patient.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.

Maximum 10 endpoints per practice. Endpoints are auto-disabled after 10 consecutive failures and re-enabled on the next successful delivery. Full delivery logs available in the Developer Hub.

List webhook endpoints

GET/webhooksList all registered webhook endpoints
GET /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

POST/webhooksRegister a new webhook endpoint

On success, the response includes the secret used to sign deliveries. Store it securely — it is shown only once.

Body parameters

url*stringHTTPS 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

DELETE/webhooks/{id}Remove a registered webhook endpoint
DELETE /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

GET/data-requestsList all GDPR data subject requests
GET /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

POST/data-requestsSubmit a GDPR data subject request

Body parameters

patient_id*stringThe patient's SightSync ID.
type*stringRequest 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

POST/agentTrigger a plain-English AI recall campaign

Body parameters

goal*stringPlain-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_runbooleanIf true, returns the patient selection and compliance preview without placing any calls. Default: false.
limitintegerMaximum 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."
}