CuraeAI Developers
Ship

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-15

Versions 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.

On this page