Skip to main content

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.

StepEndpointNotes
Sign upPOST /api/auth/signupProvision an organisation; returns org_id. Open or invite-only (INVITE_CODE).
Verify emailGET /api/auth/verify-email?token=…Activates the account (dev fallback logs the link to DATA_DIR; prod sends email).
LoginPOST /api/auth/loginReturns 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).
LogoutPOST /api/auth/logoutRevokes 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

POST/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": "…" } }
GET/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 } }
POST/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-…" }
POST/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 }
GET/api/healthNone

Liveness/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" } }
GET/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
GET/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

See the Forensic Witness on your stack

Book a personalised demo and we'll map Decision Keep to your automated-decision obligations and show the evidence trail end to end.