Authentication
All API requests require an API key sent via the X-API-Key header. API keys are scoped to your organization and carry specific permissions.
Generate an API Key
- Log in to Klinivo as an Owner or Admin
- Navigate to Settings → Integrations → API Keys
- Click Create API Key
- Select the permissions your integration needs
- Copy the key immediately — it will not be shown again
Important: Store your API key securely. Never expose it in client-side code or public repositories.
Your First API Call
Test your API key with the health endpoint:
curl -H "X-API-Key: kv_your_key_here" \
https://api.klinivo.co/api/v1/health A successful response:
{
"status": "ok",
"keyPrefix": "kv_abc12345",
"organizationId": "org-uuid",
"permissions": [
"READ_PATIENTS",
"READ_APPOINTMENTS"
]
} Permissions
API keys use granular permissions. Only request what your integration needs:
| Permission | Access |
|---|---|
| READ_PATIENTS | List, search, view patients |
| WRITE_PATIENTS | Create and update patients |
| READ_APPOINTMENTS | List, view appointments and schedules |
| WRITE_APPOINTMENTS | Create, cancel, confirm, reschedule appointments |
| READ_PROVIDERS | List providers and locations |
| READ_CONSULTATIONS | View SOAP notes and prescriptions |
| MANAGE_WEBHOOKS | Create and manage webhook subscriptions |
Rate Limiting
API requests are rate-limited per key. Check response headers to monitor your usage:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Max requests per minute |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when window resets |
When rate-limited, you will receive a 429 response with a Retry-After header.
Idempotent Requests
For write operations (POST, PATCH), include an Idempotency-Key header to safely retry requests without duplicating actions:
curl -X POST \
-H "X-API-Key: kv_your_key_here" \
-H "Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
-d '{"patientId": 1, "providerId": 2, ...}' \
https://api.klinivo.co/api/v1/appointments Keys expire after 24 hours. Use UUIDs or similar unique identifiers.
Webhooks
Receive real-time notifications when events occur in your organization.
Setup
- Navigate to Settings → Integrations → Webhooks
- Add your endpoint URL and select the events you want to receive
- Copy the signing secret for signature verification
Available Events
| Event | Trigger |
|---|---|
| patient.created | New patient registered |
| appointment.scheduled | Appointment booked |
| appointment.confirmed | Appointment confirmed |
| appointment.completed | Consultation ended |
| appointment.cancelled | Appointment cancelled |
| appointment.no_show | Patient did not arrive |
| consultation.ended | Provider finished consultation |
| soap_note.created | SOAP note saved |
| intake.started | Patient started intake form |
| intake.completed | Patient completed intake form |
Verifying Signatures
Every webhook delivery includes an X-Klinivo-Signature header in the format t=timestamp,v1=signature. Verify it with HMAC-SHA256:
const crypto = require('crypto');
function verifySignature(payload, header, secret) {
const [tPart, v1Part] = header.split(',');
const timestamp = tPart.split('=')[1];
const signature = v1Part.split('=')[1];
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
} import hmac, hashlib
def verify_signature(payload: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
timestamp = parts['t']
signature = parts['v1']
expected = hmac.new(
secret.encode(),
f"{timestamp}.".encode() + payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected) Retry Policy
Failed deliveries are retried with exponential backoff: 1, 2, 4, 8, and 16 minutes. After 5 failed attempts, the delivery is moved to the dead letter queue. Check delivery status via the webhook management API.
Error Handling
The API returns standard HTTP status codes with JSON error bodies:
{
"error": "invalid_api_key",
"message": "Invalid or expired API key"
} | Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 204 | Success, no content |
| 400 | Invalid request body or parameters |
| 401 | Invalid or missing API key |
| 403 | API key lacks required permission |
| 404 | Resource not found |
| 429 | Rate limit exceeded |