Skip to main content
Deserke

One API. Twelve product categories. No surprises between them.

REST resources as plural nouns, kebab-case paths, camelCase JSON fields. Events are named in the past tense: an event describes something that already happened. Every resource follows the same shape whether it's a patient observation or a claims adjudication — cross the API surface without relearning conventions.

Request / response

Cursor pagination, not offset — offsets drift under concurrent writes on a clinical timeline, cursors don't. Idempotency-Key is required on every mutating request; replay a request with the same key and you get the original response back, not a duplicate write.

GET /v1/patients/pat_7f3a/observations?limit=50&after=obs_9f19
Authorization: Bearer sk_live_...

{
  "data": [
    {
      "id": "obs_9f2a",
      "recordedAt": "2026-07-18T14:03:00Z",
      "value": 38.2,
      "unit": "celsius",
      "method": "derived",
      "source": { "deviceId": "dev_44c1", "readingIds": ["rd_881a", "rd_881b"] }
    }
  ],
  "pagination": { "nextCursor": "obs_9f2a", "hasMore": true }
}

method is always present on anything Deserke's own systems produced — same three values as the product surface: derived, inferred, or unknown. If you're building on top of a Deserke-computed value, you can tell which kind of claim you're inheriting without reading a separate doc page.

Errors

Every error is a typed object, not a string to pattern-match against. requestId is on every response, error or not — send it with a support ticket and we can pull the exact trace, not "around what time did this happen."

HTTP/1.1 422 Unprocessable Entity

{
  "error": {
    "type": "validation_error",
    "code": "unit_mismatch",
    "message": "observation.unit 'fahrenheit' does not match device dev_44c1's calibrated unit 'celsius'",
    "param": "unit",
    "requestId": "req_3n8x0v"
  }
}

Webhooks

Signed with HMAC-SHA256 over the raw body — verify before you parse, not after. Delivery retries with exponential backoff for 24 hours; a 2xx within 10 seconds is the only thing that stops it.

const signature = req.headers["deserke-signature"]; // "t=1737..., v1=5257a8..."
const [tPart, v1Part] = signature.split(", ");
const timestamp = tPart.split("=")[1];
const expected = hmacSha256(webhookSecret, `${timestamp}.${rawBody}`);

if (!timingSafeEqual(expected, v1Part.split("=")[1])) throw new Error("invalid signature");
// event.type: "observation.recorded", "claim.adjudicated", "device.calibration_expired", ...

Versioning & rate limits

Version is in the path (/v1/), not a header — additive fields ship inside a version without notice, removed or renamed fields never do. Breaking changes land as a new version with a minimum 12-month deprecation window on the last one. X-RateLimit-Remaining and X-RateLimit-Reset are on every response so you can back off before you get a 429, not after.

Read the API docs