ROBIN VAULT

Backend API Reference

The backend is a minimal Fastify service with exactly three endpoints. Request/response bodies are validated against strict JSON Schemas.

Strict by default

Schema validation runs with additionalProperties: false. An accidental stray field — like { accountId, pin } — is rejected with a loud 400, not silently stripped. This is defense in depth against a client bug that might otherwise leak an extra field without anyone noticing.

GET /healthz

Liveness probe. Carries no accountId and reveals nothing about wallet state — not rate limited.

StatusBody
200{ "status": "ok" }

POST /v1/provision

Idempotently gets-or-creates the salt for an account. Safe to call on every unlock, not just first-time setup.

Body: { "accountId": "<64-char lowercase hex>" } — pattern ^[0-9a-f]{64}$.

StatusBody
200{ "accountId", "salt" (base64), "created": boolean }
400{ "error": "invalid_account_id" }
429{ "error": "rate_limited", "retryAfterSeconds" } + Retry-After header

GET /v1/salt/:accountId

Fetch-only variant of the above, for a stricter unlock-only flow.

StatusBody
200{ "salt": "<base64>" }
404{ "error": "not_found" } — never provisioned
429{ "error": "rate_limited", "retryAfterSeconds" } + Retry-After header

Shared rate-limit bucket

POST /v1/provision and GET /v1/salt/:accountId share exactly one rate-limit bucket per accountId. Hammering one endpoint locks out the other too — see Rate Limiting Policy for the exact formula.

Data minimization

The server persists only a hashed accountId, a random salt, and rate-limit counters. It never stores the raw UID, the PIN, the mnemonic, the seed, any private key, or any wallet address.