DATEYE

Errors, Conventions & Reference

Health & status

EndpointAuthPurposeResponse
GET /api/v1/healthBearerVerify connectivity + key validity (updates last_used_at){ "status": "ok" }
GET /api/v1/statuspublicService health incl. DB ping, for monitoring{ "status": "ok", "version": "…", "timestamp": "…" }
GET /api/v1/openapi.jsonpublicMachine-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).

CodeHTTPWhere
INVALID_API_KEY401all authenticated endpoints
VALIDATION_ERROR400import, imports/list, practice/mappings, setup/db-key
INVALID_REQUEST400JSON not parseable
INVALID_CODE400setup/redeem
INVALID_HOSTNAME400setup/redeem
ALREADY_REDEEMED409setup/redeem
CODE_EXPIRED410setup/redeem
KEY_ALREADY_SET409setup/db-key POST (one-shot)
KEY_NOT_FOUND404setup/db-key GET
CONSENT_MISSING403import mode follow_up
CONSENT_WITHDRAWN403import mode follow_up
TOKEN_FOREIGN403import mode token (foreign practice)
NOT_FOUND404import (patient/practice not found)
RATE_LIMIT_EXCEEDED429all (with Retry-After)
INTERNAL_ERROR500all

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