{
  "version": "v0.16.0",
  "source": "Architecture/APIs/Platform API.md §1",
  "status": "fixed",
  "envelope_types": [
    "auth",
    "billing",
    "rate_limit",
    "validation",
    "upstream",
    "internal"
  ],
  "next_action_verbs": [
    "login_to_link",
    "verify_account",
    "accept_tos",
    "refetch_tos",
    "refetch_root",
    "top_up",
    "request_scope",
    "contact_support",
    "switch_credential"
  ],
  "rules": {
    "next_action_required_on": "every 402, 403 and 409 code, and key_blocked (401)",
    "action_url_required_on": [
      "payment_required",
      "verification_required",
      "key_blocked"
    ],
    "action_url_rule": "RATIFIED — the Bridge sitting, 2026-08-24 (point 9 and Ruling 2). action_url carries the STABLE payment landing address on the two money/verification walls and the support destination on key_blocked. Never a per-payment provider link. budget_cap_reached is deliberately absent: a cap is raised by its owner, and no address exists for that yet.",
    "retry_after_required_on": [
      "rate_limited"
    ],
    "billing": "only 2xx data responses are billed; upstream_error is never charged",
    "credentials": "RATIFIED — the credentials sitting, 2026-09-04. Two strings, both minted by the platform at signup: the SECRET (prefix bfa_, one per account, no id, no name, listed by no route) runs the account on the api host; the DATA KEY (bf_live_…, key-format.json) fetches data on the data plane and nothing else. The wrong string on a host answers wrong_credential; hosts tell the two apart by the reserved first letters before any lookup. No route reads a client-made Idempotency-Key any more: the payment routes keep one open page per account and per kind, and Core mints the marker the Bridge's internal wire requires from the intent id."
  },
  "codes": {
    "invalid_key": {
      "http": 401,
      "type": "auth",
      "reaction": "don't retry; re-check credential source; if none — restart signup"
    },
    "key_expired": {
      "http": 401,
      "type": "auth",
      "reaction": "rotate, or escalate to owner"
    },
    "key_revoked": {
      "http": 401,
      "type": "auth",
      "reaction": "stop; escalate (possible incident) — also what a replaced account secret answers (2026-09-04)"
    },
    "key_blocked": {
      "http": 401,
      "type": "auth",
      "reaction": "stop; abuse suspicion — the owner contacts support with request_id, at the address in action_url",
      "next_action": "contact_support",
      "next_action_required": true
    },
    "session_expired": {
      "http": 401,
      "type": "auth",
      "reaction": "log in again from the console — a mailed link; sessions are browser credentials, agents never hold one"
    },
    "scope_denied": {
      "http": 403,
      "type": "auth",
      "reaction": "don't retry — this surface is the platform operator's; no customer key is ever granted it",
      "next_action_required": true
    },
    "verification_required": {
      "http": 403,
      "type": "auth",
      "reaction": "escalate to human — a checkpoint, not a failure",
      "next_action_required": true
    },
    "account_suspended": {
      "http": 403,
      "type": "auth",
      "reaction": "stop entirely; owner contacts support",
      "next_action_required": true
    },
    "wrong_credential": {
      "http": 403,
      "type": "auth",
      "reaction": "present the other string — the api host takes the secret, the data plane takes a data key; never retry with the same string, never sign up again",
      "next_action": "switch_credential",
      "next_action_required": true
    },
    "payment_required": {
      "http": 402,
      "type": "billing",
      "reaction": "escalate to human (top-up / upgrade), or degrade gracefully",
      "next_action_required": true
    },
    "budget_cap_reached": {
      "http": 402,
      "type": "billing",
      "reaction": "stop spending; wait for reset or a human raise",
      "next_action_required": true,
      "status": "RESERVED — nothing may raise this code yet, and the reason is a hole in this closed list rather than missing work."
    },
    "rate_limited": {
      "http": 429,
      "type": "rate_limit",
      "reaction": "wait exactly retry_after, then resume",
      "retry_after_required": true
    },
    "email_exists": {
      "http": 409,
      "type": "validation",
      "reaction": "switch to account-linking (login_to_link); never retry signup",
      "next_action_required": true
    },
    "invalid_params": {
      "http": 400,
      "type": "validation",
      "reaction": "fix the call; never blind-retry"
    },
    "not_found": {
      "http": 404,
      "type": "validation",
      "reaction": "re-read /v1/products; don't retry unchanged (pre-launch static surface: refetch_root)"
    },
    "upstream_error": {
      "http": 502,
      "type": "upstream",
      "reaction": "retry with backoff — the call was not charged"
    },
    "internal_error": {
      "http": 500,
      "type": "internal",
      "reaction": "backoff + request_id for support"
    }
  }
}
