DATEYE

Errors, Conventions & Reference

Health & status

Endpoint Auth Purpose Response
GET /api/v1/health Bearer Verify connectivity + key validity (updates last_used_at) { "status": "ok" }
GET /api/v1/status public Service health incl. DB ping, for monitoring { "status": "ok", "version": "…", "timestamp": "…" }
GET /api/v1/openapi.json public Machine-readable spec (OpenAPI 3.0.3), Access-Control-Allow-Origin: * OpenAPI JSON

Error codes (complete)

All errors use { "success": false, "error": { "code", "message" } }; validation errors add error.fields: [{ "field": "measurements[0].axialLength", "code": "too_big" }] (path + zod issue code only, never received values — PII protection).

Code HTTP Where
INVALID_API_KEY 401 all authenticated endpoints
VALIDATION_ERROR 400 import, imports/list, practice/mappings, setup/db-key …
INVALID_REQUEST 400 JSON not parseable
INVALID_CODE 400 setup/redeem
INVALID_HOSTNAME 400 setup/redeem
ALREADY_REDEEMED 409 setup/redeem
CODE_EXPIRED 410 setup/redeem
KEY_ALREADY_SET 409 setup/db-key POST (one-shot)
KEY_NOT_FOUND 404 setup/db-key GET
CONSENT_MISSING 403 import mode follow_up
CONSENT_WITHDRAWN 403 import mode follow_up
TOKEN_FOREIGN 403 import mode token (foreign practice)
NOT_FOUND 404 import (patient/practice not found)
RATE_LIMIT_EXCEEDED 429 all (with Retry-After)
INTERNAL_ERROR 500 all

Typical flows (sequences)

Onboarding

Portal: generate setup code (MK-XXXX-XXXX, expires_at, store code_hash)
Client → POST /setup/redeem { code }           → { apiKey, host, encryptionKey }
Client → GET  /health            (Bearer)      → { status: ok }
Client → POST /setup/db-key { encryptedDbKey } → { success: true }   (optional, one-shot)

First import → linking → follow-up import

Client → POST /import { mode: first_visit, devicePseudonym, hints, measurements[] }
         → { unmatched: N, skip_pseudonyms, … }        (lands in pending queue)
Portal:  human links pseudonym → patient                (mapping + persistentToken created)
Client → GET  /practice/mappings?since=…
         → { mappings: [ { devicePseudonym, mkClientId, revoked:false } ], nextSince }
Client → POST /import { mode: token, persistentToken, measurements[] }
         → { assigned: N, persistent_token }             (directly attributed)

Revocation/rejection

Portal:  patient deleted / consent withdrawn / pseudonym rejected
Client → GET /practice/mappings?since=…   → tombstone { revoked:true }  ⇒ stop sending
Client → GET /rejected-clients            → { rejected:[…] }            ⇒ skip these

What your backend must provide (checklist)

So that the unmodified DATEYE client works against your backend:

  • Tenant model: one “account” (practice) per setup code / API key; all data account_id-scoped.
  • Setup codes: generation in the portal, storage as SHA-256 hash, expires_at, status active/redeemed/invalidated, POST /setup/redeem incl. IP rate limit.
  • API keys: generation at redeem (<prefix>_live_ + 64 hex), storage as SHA-256 hash + display prefix, revoked_at, bearer-auth middleware (SHA-256 lookup).
  • encryptionKey: generate a per-account 32-byte key, store it encrypted at rest, serve it via redeem/config; implement setup/db-key (opaque blob, one-shot).
  • Pseudonym store: devicePseudonym (32 hex, account_id-scoped) as the patient key; mapping pseudonym → clientId with mapped_at / tombstone status.
  • POST /import: the 4 modes, .strict() validation, batch 1–50, dedup on the natural key, counter response incl. skip_pseudonyms, consent check on follow_up.
  • Pending queue + portal linking (manual), persistentToken issuance.
  • GET /practice/mappings (incremental, tombstones, deterministic cursor), GET /rejected-clients, GET /imports/list.
  • GET /health (auth), GET /status (public), error envelope + codes, 120/60s rate limit, Retry-After.
  • Privacy: never echo PII in error messages; strictly honor the pseudonym model (no PII over the API).

Versioning & Stability

All endpoints are prefixed with /api/v1/. Within v1, changes are additive and backward-compatible: new optional fields may appear in responses, new optional request fields may be accepted, and new error codes may be added. The client is expected to ignore unknown fields. Breaking changes (removed fields, changed semantics, incompatible request shapes) are only introduced under a new major version prefix (/api/v2), announced in advance. Deprecations are communicated via changelog and, where possible, via a response header before removal.

Security & Data Protection

  • TLS only: all endpoints are HTTPS-only; plain HTTP is rejected.
  • Privacy by design: the client never transmits cleartext PII — only pseudonyms (stable HMAC hashes), coarse hints (initials, birth month/year), and biometric measurements. The server cannot re-identify a patient from the data it receives. This design is aligned with GDPR data minimisation principles and is particularly important for health data under Art. 9 GDPR.
  • API key storage: keys are stored exclusively as SHA-256 hashes; the plaintext is shown once at issuance and never retrievable again.
  • Error messages: validation errors report only field paths and error codes — never the values received. PII is never echoed back in error responses.

The complete OpenAPI 3.0.3 definition is served at GET /api/v1/openapi.json and importable directly into Swagger, Postman, or any codegen tool.

Open the interactive API reference