Get started
Getting started
Get an API client from the clinic admin, exchange it for an OAuth 2.0 access token and make your first call.
The Clive partner API lets your system work with a Clive clinic group: keep members in sync, book appointments, redeem vouchers and receive events as they happen. It has four parts:
- REST API under
/public/v1: JSON over HTTPS, described by an OpenAPI 3.1 document. - Outbound webhooks: signed
POSTs from Clive to your endpoint when something happens at the clinic (Webhooks). - Inbound partner events: signed events your platform sends to Clive (Partner contract v1).
- FHIR R4: a read-only FHIR view of the same records (FHIR R4).
All of them use the same API client and the same OAuth 2.0 access tokens.
Who this is for. Engineering teams at partner platforms (membership programmes, insurers, booking and wellness apps) and at clinic groups connecting their own systems. The API is operated by Clive Labs Sdn Bhd under the Terms of Service; your Clive partnerships contact issues sandbox access and answers questions.
Environments
| Sandbox | Production | |
|---|---|---|
| API host | https://sandbox-api.clivehealth.app |
https://api.clivehealth.app |
| Data | Synthetic only | Real clinic data |
| Credentials | Issued in the sandbox | Issued separately in production |
Build and test against the sandbox first (Sandbox). Sandbox credentials never work in production, and production credentials never work in the sandbox.
In the examples on these pages $API is the API host:
export API=https://sandbox-api.clivehealth.appGet an API client
An API client is how your system identifies itself to one clinic group. A clinic admin creates it in the Clive web app:
- Open Settings › API clients and choose New client.
- Give it a name that says what it is for, for example "Acme booking sync (sandbox)".
- Tick only the scopes your integration needs (see the list below), then choose Create client.
- Copy the Client ID (
cli_…) and Client secret (sk_…). The secret is shown once. Store it in a secret manager, never in source control or in a browser or mobile app.
For the sandbox, your Clive partnerships contact creates the client in the sandbox clinic group and sends you the ID and secret. The client is limited to that one clinic group: every request it makes reads and writes that group's data only.
The admin can also:
- Rotate the secret. A new secret is shown once. The old one keeps working for 24 hours by default, so you can deploy the new one without downtime.
- Change the scopes or the rate limit (600 requests per minute by default).
- Revoke the client. Its tokens stop working at once, and the webhook endpoints it registered are disabled.
Scopes
Each operation needs one scope; the API reference lists which. A token carries the scopes granted to its client, or a subset you ask for.
| Scope | Allows | Operations |
|---|---|---|
members:read | Read member profiles | |
members:write | Create and update member profiles | |
consents:read | Read consents | Webhooks, FHIR and exports (see their pages) |
consents:write | Record and withdraw consents | Webhooks, FHIR and exports (see their pages) |
appointments:read | Read appointments | |
appointments:write | Book, reschedule and cancel appointments | |
availability:read | Read available slots | |
venues:read | Read branches, hours and closures | |
venues:write | Update branch hours and closures | Webhooks, FHIR and exports (see their pages) |
vouchers:read | Read vouchers and redemptions | |
vouchers:write | Issue and cancel vouchers | |
results:read | Read released results (consent permitting) | |
leads:read | Read enquiries | Webhooks, FHIR and exports (see their pages) |
leads:write | Create and update enquiries | Webhooks, FHIR and exports (see their pages) |
cases:read | Read cases | Webhooks, FHIR and exports (see their pages) |
cases:write | Create and update cases | Webhooks, FHIR and exports (see their pages) |
webhooks:manage | Manage webhook endpoints | Webhooks, FHIR and exports (see their pages) |
Get an access token
The API uses the OAuth 2.0 client credentials grant (RFC 6749 §4.4). Exchange your client ID and secret for an access token at POST /oauth/token:
export CLIENT_ID=cli_… # from Settings › API clients
export CLIENT_SECRET=sk_…
curl -sS -X POST "$API/oauth/token" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials{
"access_token": "eyJhbGciOi…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "members:read members:write vouchers:read vouchers:write"
}- Credentials go in HTTP Basic auth (as above) or in the body as
client_idandclient_secret. The body may be form-encoded or JSON. - Add
scope(space-separated) to get a token with fewer scopes than the client has, for example-d "scope=members:read". Asking for a scope the client was not granted fails withinvalid_scope. - A token lasts
expires_inseconds (an hour). Cache it and request a new one shortly before it expires, not once per request: the token endpoint allows 30 requests per minute per IP address. - Errors follow RFC 6749:
401 invalid_client(wrong ID or secret, or the client was revoked),400 invalid_scope,400 unsupported_grant_type.
To keep the token in a shell variable (with jq):
export TOKEN=$(curl -sS -X POST "$API/oauth/token" -u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials | jq -r .access_token)Make your first call
Send the token as a Bearer token. GET /public/v1/me returns the calling client, which is a good way to check the setup:
curl -sS "$API/public/v1/me" -H "Authorization: Bearer $TOKEN"{
"client_id": "cli_…",
"clinic_id": "2f0c6a8e-…",
"scopes": ["members:read", "members:write", "vouchers:read", "vouchers:write"]
}Every authenticated response carries your rate-limit budget in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Rate limits).
Conventions
- JSON field names are
snake_case. Timestamps are ISO 8601 (2026-10-24T02:15:09.123Z); dates areYYYY-MM-DD. - Ids are UUIDs. Your own ids (a member's id in your system, a booking reference) are stored alongside them as external ids.
- Responses may gain new fields at any time within v1. Ignore fields you don't know (Versioning).
- Lists are cursor-paginated and every
POSTneeds anIdempotency-Keyheader (Pagination and idempotency).
Errors
Every error response from /public/v1 has the same body:
{ "code": "VALIDATION_ERROR", "message": "The request has invalid fields", "details": { "fields": { "venue_id": ["Invalid uuid"] } } }Branch on code, never on message. The common codes:
| Status | code |
Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED, INVALID_CURSOR |
A POST without an Idempotency-Key, or a cursor that did not come from next_cursor |
| 401 | INVALID_TOKEN |
Missing, invalid or expired token, or the client was revoked |
| 403 | INSUFFICIENT_SCOPE |
The token lacks the scope this operation needs |
| 403 | CONSENT_REQUIRED |
The member has not given the consent this data needs (Consent) |
| 404 | NOT_FOUND |
No such record in this clinic group |
| 409 | IDEMPOTENCY_IN_PROGRESS, or a resource-specific conflict |
Retry later, or see the operation's description |
| 422 | VALIDATION_ERROR, IDEMPOTENCY_KEY_REUSED |
The request is invalid; details.fields names the fields |
| 429 | RATE_LIMITED |
Wait Retry-After seconds |
| 500 | INTERNAL_ERROR |
Retry with back-off; contact us if it persists |
Next steps
- First integration in 15 minutes: get a token, create a member, redeem a voucher and receive the
voucher.redeemedwebhook. - API reference: every operation, its scope, parameters and responses.
- Webhooks: the event catalogue and how to verify signatures.