API conventions
The cross-cutting contract — errors, pagination, idempotency, conditional reads, versioning, and rate limits — that every endpoint follows.
API conventions
These rules apply uniformly across the Platform API, so once you handle them once, you handle them everywhere.
Errors (RFC 7807)
Errors are application/problem+json with a stable, machine-readable
code you can branch on (the human detail may change; the code is the
contract).
{
"type": "https://curaeai.com/problems/scope-insufficient",
"title": "Scope insufficient",
"status": 403,
"detail": "This key lacks the patient:read scope.",
"code": "SCOPE_INSUFFICIENT"
}Common codes: SCOPE_INSUFFICIENT, WRONG_ENVIRONMENT, VERSION_RETIRED,
BAD_REQUEST, NOT_FOUND, RATE_LIMITED. With the SDK, failures throw a
PlatformApiError carrying problem and response metadata.
Pagination
List endpoints are cursor-based. A response carries an opaque
nextCursor; pass it back to get the next page. Treat the cursor as opaque
— there is no offset pagination.
let cursor: string | undefined;
do {
const page = await client.listWebhookEndpoints({ limit: 100, cursor });
process(page.data.data);
cursor = page.data.pagination.nextCursor ?? undefined;
} while (cursor !== undefined);limit is bounded server-side (1–100); out-of-range values return
400 BAD_REQUEST rather than being silently clamped, and a malformed
cursor likewise returns 400.
Idempotency
Mutations accept an Idempotency-Key header. Within 24 hours, the server
replays the original response for a repeated key instead of performing the
action twice — so a retried create can't make a duplicate.
await client.createWebhookEndpoint(body, { idempotencyKey: 'create-endpoint-001' });Use a stable, unique key per logical operation (a ULID/UUID, or a deterministic key derived from the operation).
Conditional reads (ETags)
Resources return a strong ETag. Send it back as If-None-Match to get a
304 Not Modified (and skip the body) when nothing changed.
const fresh = await client.getPatient(id);
const again = await client.getPatient(id, { ifNoneMatch: fresh.data._version });
if ('notModified' in again) {
// your cached copy is still authoritative
}Versioning
Send a dated version header on every request:
CuraeAI-Version: 2026-04-15Versions are dated, not semver. Breaking changes ship under a new date;
deprecations get a 12-month runway and carry Deprecation and Sunset
response headers. Pin the version in your client so a new default can't
change behavior under you. A retired version returns VERSION_RETIRED.
Rate limits
Responses carry X-RateLimit-Limit, -Remaining, -Reset, and
-Window. On exhaustion you get 429 with code RATE_LIMITED; back off
using -Reset. The SDK retries transient failures with exponential backoff
by default.
Next
- Webhooks — delivery, verification, retries.
- API reference — schemas, scopes, and status codes per endpoint.