PIMS Partner API

Bidirectional integration between your practice management system and Coggo, the AI veterinary scribe.

  1. PIMS → Coggo: send signed webhook events when appointments, clients or patients change. Coggo upserts the records and a scheduled visit appears for the vet.
  2. Coggo → PIMS: Coggo calls endpoints hosted by your PIMS for patient search, upcoming-appointment reconciliation, and to push signed SOAP notes back in json, text and html.

Conventions

All ids you send are your external ids — Coggo maintains the mapping. Events are idempotent on id; retries are always safe. The PIMS is the source of truth for client/patient demographics; Coggo owns clinical documents and your PIMS never mutates those.

Base URL

https://api.coggo.ai/v1/partner

Integration flow

1. Clinic creates the integration in
   Coggo -> Settings -> Integrations
   (Pro plan) and shares with you:
     - API key        cgk_live_...
     - signing secret cgs_...
2. GET /ping            verify credentials
3. POST /webhooks       appointments flow in
4. Implement 3 endpoints on your side
   -> search, refresh, SOAP push-back

Authentication

API key. Every call to Coggo carries Authorization: Bearer cgk_live_…. Issued once in Coggo settings; scoped and revocable.

Signatures (optional). The Bearer key alone authorizes your inbound webhooks over HTTPS. For extra protection you may also send the headers below — when present, Coggo verifies them. Coggo always signs its outbound pushes to you.

X-Coggo-IdstringoptionalUnique request/event id. Also the idempotency key (recommended even without a signature).
X-Coggo-TimestampintegeroptionalUnix seconds. If sent, requests older or newer than 300s are rejected.
X-Coggo-SignaturestringoptionalBase64 HMAC-SHA256 of id.timestamp.body using the shared secret. Verified only when present.

Simplest webhook (Bearer key only)

curl -X POST https://api.coggo.ai/v1/partner/webhooks \
  -H "Authorization: Bearer cgk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"id":"evt_1","type":"appointment.created","occurredAt":"...","data":{...}}'

Optional: add an HMAC signature (bash)

SECRET="cgs_..."
ID="evt_test1"
TS=$(date +%s)
BODY='{"id":"evt_test1", ... }'

SIG=$(printf '%s' "$ID.$TS.$BODY" \
  | openssl dgst -sha256 -hmac "$SECRET" -binary \
  | base64)

Verify credentials

curl https://api.coggo.ai/v1/partner/ping \
  -H "Authorization: Bearer cgk_live_..."

Verify credentials

GET /v1/partner/ping

Confirms the API key is valid and returns the integration's state. Use it as the first step of any setup and as a health check.

Response fields

okbooleanAlways true on success.
clinicIdstringThe Coggo clinic this key is scoped to.
providerstringProvider name set at creation, e.g. woovet.
scopesstring[]Granted scopes.
statusstringactive or paused.

Response · 200

{
  "ok": true,
  "clinicId": "1ee77e8d-...",
  "provider": "woovet",
  "scopes": ["read:patients",
             "write:appointments",
             "write:clients",
             "write:patients",
             "read:notes"],
  "status": "active"
}

Send an event

POST /v1/partner/webhooks

Requires the Bearer key; the signature headers are optional (sent them to add HMAC verification). Duplicate ids are acknowledged with "duplicate": true and skipped, so retry freely.

Event types

appointment.createdappointment.updatedappointment.cancelledclient.updatedpatient.updatedpatient.merged

Body fields

idstringrequiredYour unique event id (idempotency key).
typestringrequiredOne of the event types above.
occurredAtdate-timerequiredWhen it happened in your system.
data.appointmentobjectexternalId, startsAt (local clinic time) + timezone (IANA), durationMinutes, reason, externalStaffId, status. Required for appointment events.
data.patientobjectrequired for appt.Required on appointment.created/appointment.updated — the patient is upserted first, then the appointment links to it. Missing it returns 400 patient_required. Cancellations may omit it.
data.clientobjectexternalId + name, email, phone, address fields. Embed on appointment events so no callback is needed.
patient fieldsobjectexternalId, name (required); speciesKey (preferred, mapped — see Species mapping) or legacy species; breed, color (auto-added per clinic if new); sex, reproductiveStatus; dateOfBirth or age {value, unit}; weight {value, unit: kg|lb} (or legacy weightKg); externalClientId.
data.mergedIntoExternalIdstringFor patient.merged — the surviving patient's external id.

What Coggo does

Upserts client → patient → creates a scheduled visit. Before creating, Coggo tries to link to existing records (unique email/phone for clients, unique name for patients) to avoid duplicates.

Request · appointment.created

{
  "id": "evt_8f2c41",
  "type": "appointment.created",
  "occurredAt": "2026-06-12T09:30:00Z",
  "data": {
    "appointment": {
      "externalId": "apt_1029",
      "startsAt": "2026-06-12T14:00:00",
      "timezone": "America/New_York",
      "reason": "Lameness re-check",
      "externalStaffId": "stf_44"
    },
    "client": {
      "externalId": "cli_553",
      "firstName": "Peggy",
      "lastName": "Bartl",
      "email": "[email protected]",
      "phone": "+15134041567"
    },
    "patient": {
      "externalId": "pat_771",
      "name": "Sky",
      "speciesKey": "1",
      "breed": "Golden Retriever",
      "color": "Golden",
      "sex": "female",
      "reproductiveStatus": "spayed",
      "dateOfBirth": "2023-06-08",
      "weight": { "value": 11, "unit": "kg" }
    }
  }
}

Response · 200

{ "received": true }

// replayed event:
{ "received": true, "duplicate": true }

Species mapping you implement

GET {your base URL}/species

Return your PIMS species catalog so the clinic can map each PIMS species to a Coggo species once, during setup. After mapping, your patient payloads send speciesKey (the key below) and Coggo resolves the Coggo species automatically — no name-matching guesswork.

A new integration starts in pending mapping and only activates once every species is mapped. Coggo calls this at setup and when the clinic clicks "Refresh species". If a PIMS species has no Coggo equivalent, the clinic can add it as a new species for their account.

Response · 200

species[].keystringrequiredYour PIMS species id / foreign key. Echo this back as patient.speciesKey.
species[].labelstringHuman-readable name shown in the clinic's mapping screen.

Response · 200

{
  "species": [
    { "key": "1", "label": "Canine" },
    { "key": "2", "label": "Feline" },
    { "key": "7", "label": "Equine" }
  ]
}

Then on patients

"patient": {
  "externalId": "P-1042",
  "name": "Kiva",
  "speciesKey": "1",
  "breed": "Labrador",
  "color": "Black",
  "weight": { "value": 13.2, "unit": "kg" },
  "age": { "value": 6, "unit": "years" }
}

Upcoming appointments you implement

GET {your base URL}/appointments?days=7

Powers the refresh button in Coggo (on-demand reconciliation — there is no cron). Return all appointments starting within the next days days, each with its embedded client and patient. Coggo runs them through the same idempotent pipeline as webhooks, healing anything a missed webhook left behind. Max 100 processed per refresh; refresh is debounced to once per minute.

Query parameters

daysintegerLook-ahead window. Default 7, max 30.

Response · 200

{
  "appointments": [
    {
      "appointment": {
        "externalId": "apt_1029",
        "startsAt": "2026-06-12T14:00:00",
        "timezone": "America/New_York",
        "durationMinutes": 30,
        "reason": "Lameness re-check",
        "externalStaffId": "stf_44",
        "status": "scheduled"
      },
      "client": {
        "externalId": "cli_553",
        "firstName": "Peggy",
        "lastName": "Bartl",
        "email": "[email protected]",
        "phone": "+15134041567",
        "addressLine1": "5038 Appaloosa Circle",
        "addressLine2": "",
        "city": "Columbus",
        "stateRegion": "OH",
        "postalCode": "43004"
      },
      "patient": {
        "externalId": "pat_771",
        "name": "Sky",
        "speciesKey": "1",
        "breed": "Golden Retriever",
        "color": "Golden",
        "sex": "female",
        "reproductiveStatus": "spayed",
        "dateOfBirth": "2023-06-08",
        "weight": { "value": 11, "unit": "kg" },
        "externalClientId": "cli_553"
      }
    }
  ]
}

Receive SOAP notes you implement

POST {your registered push endpoint}

Coggo POSTs a signed envelope when a vet pushes a finalized SOAP note. Verify the signature headers exactly as Coggo verifies yours. Any 2xx marks the push sent; anything else is retried 5× with backoff and surfaces in the clinic's integration dashboard.

Envelope fields

meta.externalPatientIdstringYour patient id (from the id mapping). Null if the patient was created in Coggo and never linked.
meta.externalAppointmentIdstringYour appointment id when the note belongs to a synced visit.
meta.noteId / meta.versionstring / intTreat (noteId, version) as your idempotency key — amended notes re-push with version+1.
formats.jsonobjectStructured SOAP, schemaVersion 1.0. Each section (subjective / objective / assessment / plan) is { title, fields[] }; read each field's label and value (a field with no data has value "Not mentioned" or ""). Plus templateExtension for structured templates (grids, charts).
formats.json.vitalsobjectPresent only when at least one vital was captured (the node is omitted otherwise). Flat keys (weight, temperature {value, unit, raw}, heartRate, …) + confidence. An empty string means that vital was not captured.
formats.textobjectPlain-text SOAP split per section (subjective / objective / assessment / plan) so each can be its own EMR node. Only sections with data are present; narrative-only notes return narrative.
formats.htmlstringCompact HTML fragment (no doctype/head/body) for embedding as an EMR record node.

Consume whichever format fits your system — most PIMSes store html for display and json for data.

Request you'll receive

{
  "id": "push_a1b2c3",
  "occurredAt": "2026-06-12T15:04:05Z",
  "meta": {
    "externalPatientId": "pat_771",
    "externalAppointmentId": "apt_1029",
    "vet": { "name": "Tom Malloy",
             "externalStaffId": "stf_44" },
    "noteId": "d3f1...",
    "version": 1,
    "signedAt": "2026-06-12T15:03:40Z",
    "language": "en"
  },
  "formats": {
    "json": {
      "schemaVersion": "1.0",
      "objective": {
        "title": "Objective / Physical Exam",
        "fields": [
          { "name": "temperature", "label": "Temperature (Rectal)",
            "type": "number_with_unit", "unit": "°C", "value": 36.89 },
          { "name": "heart_rate", "label": "Heart Rate",
            "type": "number_with_unit", "unit": "bpm", "value": "Not mentioned" }
        ]
      },
      "subjective": { "title": "Subjective / History", "fields": [ ... ] },
      "assessment": { "title": "Assessment / Diagnosis", "fields": [ ... ] },
      "plan":       { "title": "Plan", "fields": [ ... ] },
      // present only when vitals were captured:
      "vitals": {
        "temperature": { "value": 36.89, "unit": "celsius", "raw": 98.4 },
        "weight": "", "heartRate": "", "confidence": "low"
      }
    },
    "text": {
      "subjective": "Chief Complaint: Wellness / Checkup",
      "objective": "Temperature (Rectal): 36.89 °C"
    },
    "html": "<!doctype html>..."
  }
}

Your response

HTTP/1.1 200 OK   // any 2xx = recorded as sent

Errors & limits

400Validation failed — error names the exact field.
401Invalid API key, or (when signature headers are sent) an invalid/expired signature.
402integration_paused — the integration is paused or the clinic's plan no longer includes the Partner API.
429Over 120 requests/min for this key. Honor Retry-After.

All errors share one shape. Webhook processing failures still return 200 — the event is stored and retryable from the clinic's dashboard.

Error shape

{
  "error": "integration_paused",
  "detail": "optional human-readable context"
}