API reference
Decision Keep exposes a small, predictable HTTP API for recording and verifying automated decisions. Everything is JSON; every response is tamper-evident and independently verifiable. Base URL is your instance (e.g. https://keep.your-co.com).
Authentication
Browser and server clients authenticate with a session cookie obtained from /api/auth/login (or enterprise SSO). Send it as the Cookie header. The public verification endpoint needs no auth. Service-account / API-key auth is on the enterprise roadmap.
| Step | Endpoint | Notes |
|---|---|---|
| Sign up | POST /api/auth/signup | Provision an organisation; returns org_id. Open or invite-only (INVITE_CODE). |
| Verify email | GET /api/auth/verify-email?token=… | Activates the account (dev fallback logs the link to DATA_DIR; prod sends email). |
| Login | POST /api/auth/login | Returns a session cookie (dk_session). 10 req/min/IP; lockout after repeated failures. |
| SSO (SAML 2.0 + OIDC) | GET/POST /api/auth/saml/*, /api/auth/sso/* | Enterprise SSO via SAML 2.0 or any OIDC IdP (requires sso license feature). |
| Logout | POST /api/auth/logout | Revokes the session. |
Rate limits
Per-IP, rolling windows (defence-in-depth for a sovereign single instance): record 30/min · verify 60/min · login 10/min · signup 5/min · waitlist 20/min. Exceeding a limit returns 429 with a Retry-After header.
Endpoints
/api/decisionsSession (decision:record)Records one decision. The payload is hashed and signed; only its hash is asserted publicly. 30 req/min/IP.
Request body
{ "system": "Underwriting", "agent": "credit-model", "version": "v3",
"payload": { "application_id": "APP-1", "amount": 50000 },
"regulatory_controls": ["EU AI Act Art.12"], "confidence_score": 0.92,
"human_verifier_id": "reviewer-7", "routing_outcome": "ASSISTED" }Success response
201 Created
{ "decision": { "receipt_id": "RCP-20260710-1A2B3C", "status": "valid", "chain_hash": "…", "content_hash": "…" } }/api/verify?receipt=RCP-…None (public)Trustless verification. no account, no trust in the instance. 60 req/min/IP.
Request body
.Success response
200 OK
{ "verified": true, "erased": false, "receipt_id": "RCP-…",
"signature": "…", "chain": { "continuous": true }, "tsa": { "present": true } }/api/decisions/{receipt}/eraseSession (decision:erase)Cryptographic erasure (GDPR Art.17 / CCPA). The chain anchor is preserved; content is redacted and a signed proof is appended.
Request body
.Success response
200 OK
{ "ok": true, "erased": true, "receipt_id": "RCP-…" }/api/cron/erasureBearer <CRON_SECRET>Scheduled retention erasure. Call from cron / systemd / K8s CronJob / GitHub Action. Never expose the secret.
Request body
.Success response
200 OK
{ "erased": 3, "previous_run": "2026-07-09T03:17:00Z", "missed_run_detected": false }/api/healthNoneLiveness/readiness probe for monitors and healthchecks.io.
Request body
.Success response
200 OK
{ "status": "ok", "backend": "file|sqlite", "data_dir_writable": true,
"tsa_enabled": false, "license": { "edition": "community" } }/api/metricsNone (scrape target)Prometheus metrics: record/verify/erase/login rates, security-event rate, ledger size, uptime.
Request body
.Success response
200 OK (Prometheus text/exposition)
decisionkeep_decisions_recorded_total{outcome="success"} 12
decisionkeep_decisions_verified_total{outcome="verified"} 40/api/ledger/exportSession (owner/admin)Full ledger export for offline audit. Pair with `npm run verify:ledger`.
Request body
.Success response
200 OK (application/json)
{ "org": {…}, "decisions": [ … ], "public_key_pem": "-----BEGIN PUBLIC KEY-----…" }Quickstart
cURL
# 1. log in (captures the session cookie)
curl -c cookies.txt -X POST https://keep.your-co.com/api/auth/login \
-H 'content-type: application/json' \
-d '{"email":"you@co.com","password":"…"}'
# 2. record
curl -b cookies.txt -X POST https://keep.your-co.com/api/decisions \
-H 'content-type: application/json' \
-d '{"system":"Underwriting","agent":"credit-model","version":"v3",\
"payload":{"application_id":"APP-1","amount":50000}}'Node / TypeScript
const res = await fetch(BASE + "/api/decisions", {
method: "POST",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({
system: "Underwriting", agent: "credit-model", version: "v3",
payload: { application_id: "APP-1", amount: 50000 },
}),
});
const { decision } = await res.json();
// decision.receipt_id -> verify at /api/verify?receipt=…Errors
Errors use standard HTTP status codes with a JSON { error } body. Common cases: 400 invalid input, 401 unauthenticated, 403 forbidden / cross-origin, 404 receipt not found (verify returns { verified:false, reason }), 413 payload too large, 429 rate limited.
Verify offline (no trust required)
Export the ledger and verify it independently of the running instance:
# structured + cryptographic check, fully offline
npm run verify:ledger /path/to/DATA_DIR --json