DATEYE

Onboarding & Authentication

Redeem setup code — POST /api/v1/setup/redeem

Unauthenticated (IP rate limit). Exchanges a one-time setup code for credentials.

Setup-code format: ^(MK|MD)-[A-Z2-9]{4}-[A-Z2-9]{4}$ Alphabet 23456789ABCDEFGHJKMNPQRSTUVWXYZ (31 chars, excludes 0/1/I/L/O — low ambiguity). The prefix is free to choose (MK = prod, MD = dev in the reference implementation). The server stores only the SHA-256 hash of the code (code_hash), never the plaintext.

Request:

{ "code": "MK-7X3F-9K2P", "hostname": "PRACTICE-PC-01" }

hostname is optional (printable ASCII, ≤255).

Response 200:

{
  "success": true,
  "data": {
    "apiKey": "mk_live_1a2b…(64 hex)",
    "host": "https://myopia.kids",
    "encryptionKey": "e3f1…(64 hex)"
  }
}
  • apiKey is returned in plaintext only here, once (never retrievable again).
  • encryptionKey = per-account key used only for the DB-passphrase backup (see below), not for import data.

Errors:

CodeHTTPMeaning
INVALID_CODE400Bad format or unknown code
INVALID_HOSTNAME400hostname not printable ASCII / >255
ALREADY_REDEEMED409Code already redeemed
CODE_EXPIRED410Code expired (expires_at < now)
RATE_LIMIT_EXCEEDED429>10 attempts/h/IP (with Retry-After)
INVALID_REQUEST400JSON not parseable
INTERNAL_ERROR500Server error

API key & authentication

  • Format: mk_live_ + 64 hex chars (32 random bytes). The prefix is cosmetic — pick your own for your product (e.g. mc_live_).
  • Server-side storage: Only the SHA-256 hash (key_hash, hex) plus a truncated display prefix (key_prefix). The plaintext key is never stored.
  • Validating an incoming request: Extract the bearer token → compute SHA-256SELECT … FROM api_keys WHERE key_hash = $1 AND revoked_at IS NULL. No match → 401 INVALID_API_KEY. A match yields the account_id (tenant) for the rest of the request.
  • Side effect: On every authenticated request, update last_used_at (and the telemetry headers) fire-and-forget — it must not block the response.

Fetch encryption key — GET /api/v1/config

Authenticated. Returns the same encryptionKey again (e.g. after a reinstall) plus the active update channel.

Response 200:

{ "success": true, "data": { "encryptionKey": "e3f1…", "updateChannel": "stable" } }

updateChannel = stable | canary (from the key’s canary_consent flag; the header X-DATEYE-Canary-Consent overrides it for the current response).

DB-passphrase backup (using the encryptionKey)

The DATEYE client encrypts local patient data in its own DB with a passphrase. For recovery after device loss, it can deposit this passphrase with the server client-side encrypted. The server stores only an opaque blob and cannot decrypt it.

Encryption format (AES-256-GCM):

iv_hex : authTag_hex : ciphertext_hex
  • Algorithm aes-256-gcm, key = the 32 bytes from encryptionKey (hex-decoded)
  • iv = 16 random bytes, authTag = 16 bytes (GCM), all parts hex, separated by :
  • This is exactly the format the client produces/expects itself.

POST /api/v1/setup/db-key — store blob (one-shot)

{ "encryptedDbKey": "iv_hex:authTag_hex:ciphertext_hex" }

Stores only if no blob exists yet (… WHERE dateye_db_key IS NULL). A second call → 409 KEY_ALREADY_SET (signal to the client: GET first and compare locally). Response { "success": true }.

GET /api/v1/setup/db-key — retrieve blob

{ "success": true, "data": { "encryptedDbKey": "iv_hex:authTag_hex:ciphertext_hex" } }

No blob stored → 404 KEY_NOT_FOUND. The client decrypts locally with encryptionKey.

There is historically a second path /api/v1/config/db-key where the server en/decrypts and stores overwritably. For the OEM integration the /setup/db-key path (opaque blob, no server decrypt) is the authoritative, privacy-conforming one. /config/db-key can be omitted.