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:

Shell
export API=https://sandbox-api.clivehealth.app

Get 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:

  1. Open Settings › API clients and choose New client.
  2. Give it a name that says what it is for, for example "Acme booking sync (sandbox)".
  3. Tick only the scopes your integration needs (see the list below), then choose Create client.
  4. 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.

ScopeAllowsOperations
members:readRead member profiles
members:writeCreate and update member profiles
consents:readRead consentsWebhooks, FHIR and exports (see their pages)
consents:writeRecord and withdraw consentsWebhooks, FHIR and exports (see their pages)
appointments:readRead appointments
appointments:writeBook, reschedule and cancel appointments
availability:readRead available slots
venues:readRead branches, hours and closures
venues:writeUpdate branch hours and closuresWebhooks, FHIR and exports (see their pages)
vouchers:readRead vouchers and redemptions
vouchers:writeIssue and cancel vouchers
results:readRead released results (consent permitting)
leads:readRead enquiriesWebhooks, FHIR and exports (see their pages)
leads:writeCreate and update enquiriesWebhooks, FHIR and exports (see their pages)
cases:readRead casesWebhooks, FHIR and exports (see their pages)
cases:writeCreate and update casesWebhooks, FHIR and exports (see their pages)
webhooks:manageManage webhook endpointsWebhooks, 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:

Shell
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
JSON
{
  "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_id and client_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 with invalid_scope.
  • A token lasts expires_in seconds (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):

Shell
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:

Shell
curl -sS "$API/public/v1/me" -H "Authorization: Bearer $TOKEN"
JSON
{
  "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 are YYYY-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 POST needs an Idempotency-Key header (Pagination and idempotency).

Errors

Every error response from /public/v1 has the same body:

JSON
{ "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