{"openapi":"3.0.3","info":{"title":"CERTEN Gateway API","version":"1.0.0","description":"Unified enterprise API for the CERTEN platform"},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication"},"bearerAuth":{"type":"http","scheme":"bearer","description":"OAuth2 Bearer token"}},"schemas":{}},"paths":{"/reference":{"get":{"summary":"Human-readable API reference (HTML)","description":"The rendered API reference. Serves HTML, not JSON — it is a page to open in a browser, not an endpoint to call from code. Public, like /docs.","security":[],"responses":{"200":{"description":"An HTML page.","content":{"application/json":{"schema":{"type":"string","description":"An HTML page."}}}}}}},"/metrics":{"get":{"summary":"Prometheus metrics","description":"Public Prometheus metrics endpoint — requires no auth (a scraper has no auth context). Returns Prometheus exposition format as text/plain, NOT JSON. In production this should be gated at the reverse proxy (network ACL or scraper-only bearer token). Returns HTTP 503 when the prom-client library is not installed.","security":[],"responses":{"200":{"description":"Prometheus exposition text","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Prometheus exposition text","type":"string"}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Metrics unavailable — prom-client not installed.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Metrics unavailable — prom-client not installed.","type":"string"}}}}}}},"/v1/health":{"get":{"summary":"Liveness probe","tags":["Health"],"description":"Public liveness probe — requires no auth. Runs a single database connectivity check and reports overall status only (no infra topology). Returns HTTP 200 when healthy and HTTP 503 when the database is unreachable.","security":[],"responses":{"200":{"description":"Gateway healthy.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Gateway healthy.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string","example":"healthy"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Gateway unhealthy — database unreachable.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Gateway unhealthy — database unreachable.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string","example":"unhealthy"}}}}}}}}},"/v1/health/ready":{"get":{"summary":"Public readiness probe","tags":["Health"],"description":"Readiness — can the gateway actually serve? Checks the database, api-bridge, the proof-service, Accumulate, the onboarding sponsor balance, and the entitlement epoch. Returns 200 when ready and 503 otherwise, with coarse reason tokens. No authentication: this is what an external uptime monitor should poll. For component-level detail use /v1/health/detail.","security":[],"responses":{"200":{"description":"Ready.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Ready.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string","example":"ready"},"reasons":{"type":"array","items":{"type":"string"}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Not ready — `reasons` names what is wrong.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Not ready — `reasons` names what is wrong.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string","example":"not_ready"},"reasons":{"type":"array","items":{"type":"string"}}}}}}}}}},"/v1/health/detail":{"get":{"summary":"Detailed component health","tags":["Health"],"description":"Operator-facing detailed health report. Requires an API key with the health:read, admin:read, or admin:write scope. Probes the database, api-bridge, proofs-service, and Accumulate network and reports per-component up/down status with latencies. Overall status is healthy, degraded, or unhealthy. Returns HTTP 503 when unhealthy (database down), otherwise HTTP 200.","security":[{"apiKey":[]}],"x-required-scopes":["health:read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Component health detail (healthy or degraded).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Component health detail (healthy or degraded).","type":"object","additionalProperties":true,"properties":{"status":{"type":"string","example":"healthy"},"service":{"type":"string"},"version":{"type":"string"},"checks":{"type":"object","additionalProperties":true,"properties":{"database":{"type":"object","additionalProperties":true},"api_bridge":{"type":"object","additionalProperties":true},"proofs_service":{"type":"object","additionalProperties":true},"accumulate":{"type":"object","additionalProperties":true}}},"timestamp":{"type":"string"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Gateway unhealthy — database down; same shape as 200.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Gateway unhealthy — database down; same shape as 200.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string","example":"unhealthy"},"service":{"type":"string"},"version":{"type":"string"},"checks":{"type":"object","additionalProperties":true},"timestamp":{"type":"string"}}}}}}}}},"/v1/identity":{"post":{"summary":"Provision a full identity in one call","tags":["Identity"],"description":"Compound method that provisions the entire CERTEN/Accumulate identity stack in a single call: it creates the Accumulate ADI (acc://<name>.acme), its key book, and the key page (book/1) keyed to the caller's public_key_hash so they can sign, then buys credits on that key page, and — when `chains` are supplied — derives and deploys the corresponding multi-chain accounts. With signing_mode \"external\" (the default) the caller supplies and retains their own keys via public_key_hash. With signing_mode \"provider\" the gateway generates and manages keys through the configured signing provider and returns a one-shot mnemonic retrieval URL (the mnemonic is never returned inline). Because each step waits for the previous transaction to anchor on-chain (Accumulate is eventually-consistent), provisioning typically completes in 60-90 seconds. The call itself returns a 202 immediately; see the polling block on the 202 response for how to wait. Requires the identity:write scope. If an idempotency_key is supplied the create is de-duplicated.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":3,"maxLength":63},"public_key_hash":{"type":"string","pattern":"^[a-fA-F0-9]{64}$","description":"sha256 of the RAW 32-byte public key, hex. REQUIRED for signing_mode \"external\" (the default)."},"public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$","description":"The public key itself, 64-char hex. REQUIRED for signing_mode \"external\" (the default) — the hash alone cannot sign, because the signing preimage is computed for a specific key. An identity created without it can never sign, cannot be repaired except by supplying the matching key via PATCH, and still consumes the org's identity quota. Omit both key fields only when signing_mode is \"provider\", where the gateway generates and holds the key."},"credits":{"type":"number","minimum":0,"maximum":1000000000,"description":"Accumulate credits to fund the new key page with. Credits are dollar-pegged (1 credit = $0.01) and an intent write costs 0.1 credit. Defaults to 500 (~5,000 write-backs); raise it for high-volume identities."},"chains":{"type":"array","items":{"type":"string","maxLength":64}},"signing_mode":{"type":"string","enum":["external","provider"]},"signing_provider":{"type":"object","additionalProperties":true},"idempotency_key":{"type":"string","maxLength":255}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"header","name":"idempotency-key","required":false,"description":"Optional. Repeat a request safely: an identical retry with the SAME key returns the stored response instead of performing the work twice, and the replay is marked with an `X-Idempotency-Replay` header. Reusing a key with a DIFFERENT body is rejected (IDEMPOTENCY_KEY_MISMATCH); retrying while the first is still running returns IDEMPOTENCY_KEY_IN_FLIGHT, which means wait and retry the same key — never a new one."}],"security":[{"apiKey":[]}],"x-idempotent":true,"x-retry-safety":"idempotent-with-key","x-retry-note":"Billable and creates on-chain state. Without a key, a retry inside the ~90s provisioning window creates a second identity and charges again — the name-collision check cannot fire until the first has anchored.","x-required-scopes":["identity:write"],"x-scope-mode":"any","responses":{"202":{"description":"Identity provisioning ACCEPTED — nothing is finished yet. On-chain provisioning (ADI + /data + key handoff + credits + chain accounts) runs asynchronously and typically completes in 60-90 seconds; the response returns immediately with status \"creating\" and a `status_url`. **Use the `polling` block rather than inventing a cadence**: wait `first_poll_after_seconds` before the FIRST request (provisioning cannot finish sooner than a chain of anchored Accumulate transactions, so earlier polls only burn requests), then poll `status_url` every `interval_seconds` until `status` is one of `terminal_states`. For provider signing mode the one-shot mnemonic_retrieval URL is included here and nowhere else.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Identity provisioning ACCEPTED — nothing is finished yet. On-chain provisioning (ADI + /data + key handoff + credits + chain accounts) runs asynchronously and typically completes in 60-90 seconds; the response returns immediately with status \"creating\" and a `status_url`. **Use the `polling` block rather than inventing a cadence**: wait `first_poll_after_seconds` before the FIRST request (provisioning cannot finish sooner than a chain of anchored Accumulate transactions, so earlier polls only burn requests), then poll `status_url` every `interval_seconds` until `status` is one of `terminal_states`. For provider signing mode the one-shot mnemonic_retrieval URL is included here and nowhere else.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"adi_url":{"type":"string"},"book_url":{"type":"string"},"key_page_url":{"type":"string"},"status":{"type":"string"},"chain_accounts":{"type":"array","items":{"type":"object","additionalProperties":true}},"credit_balance":{"type":"number"},"created_at":{},"status_url":{"type":"string"},"polling":{"type":"object","additionalProperties":true,"description":"How to wait for this, published so no client has to guess.","properties":{"first_poll_after_seconds":{"type":"integer","description":"Wait this long before the FIRST poll. Nothing can have changed sooner."},"interval_seconds":{"type":"integer","description":"Minimum seconds between polls."},"estimated_ready_in_seconds":{"type":"integer","description":"Typical total. Not a guarantee — poll until a terminal state."},"terminal_states":{"type":"array","items":{"type":"string"},"description":"Stop polling at any of these. Anything else means still in flight."}}},"signing_mode":{"type":"string"},"signing_provider":{"type":"object","additionalProperties":true},"mnemonic_retrieval":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string"},"expires_in":{"type":"number"}}},"warning":{"type":"string"}}}}}},"400":{"description":"Invalid request (e.g. malformed name, missing public_key_hash for external mode, or missing signing_provider for provider mode).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid request (e.g. malformed name, missing public_key_hash for external mode, or missing signing_provider for provider mode).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid authentication credentials.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid authentication credentials.","type":"object","additionalProperties":true}}}},"402":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Always `PAYMENT_REQUIRED`."},"quote":{"type":"object","additionalProperties":true,"description":"The binding price for this work. Pass `quote_id` on the retry to hold it."},"balance":{"type":"object","additionalProperties":true,"properties":{"available_usd":{"type":"string"},"held_usd":{"type":"string"},"spendable_usd":{"type":"string"},"shortfall_usd":{"type":"string","description":"How much is missing. Send at least this."}}},"resolve":{"type":["null","object"],"additionalProperties":true,"description":"A live way to pay, built at the moment of refusal. Null if no chain accepts deposits.","properties":{"payment_intent":{"type":"string","description":"Reference for GET /v1/billing/deposits/{reference}."},"chain":{"type":"string"},"to_address":{"type":"string"},"amount_usd":{"type":"string","description":"Send EXACTLY this. Attribution matches the exact amount."},"expires_at":{},"reused_existing":{"type":"boolean","description":"True when an already-open intent covered the shortfall. A retry loop reuses one intent rather than opening a new one per refusal."},"portal_url":{"type":"string"},"cli_command":{"type":"string"},"note":{"type":"string"}}},"how_to_pay":{"type":"object","additionalProperties":true,"description":"The same steps as endpoints, for a caller not using `resolve`."},"quote_expires_at":{"description":"Retry with the quote before this instant or it must be re-priced."}}}}}},"403":{"description":"Authenticated principal lacks the identity:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated principal lacks the identity:write scope.","type":"object","additionalProperties":true}}}},"409":{"description":"An identity with this name already exists on the network.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"An identity with this name already exists on the network.","type":"object","additionalProperties":true}}}},"429":{"description":"Request quota exceeded.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Request quota exceeded.","type":"object","additionalProperties":true}}}},"502":{"description":"The identity was submitted but failed to create or commit on the Accumulate network in time.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The identity was submitted but failed to create or commit on the Accumulate network in time.","type":"object","additionalProperties":true}}}}}}},"/v1/identity/{id}":{"get":{"summary":"Get an identity with optional enrichments","tags":["Identity"],"description":"Fetches a stored identity by id (org-scoped) and, depending on the comma-separated `include` query param (defaults to governance,balances,pending), enriches the response by querying the network for on-chain governance structure, live per-chain wallet balances, and the cached pending-action counts. Enrichment is best-effort: a failure on any sub-fetch is logged and that section is omitted rather than failing the whole request. Requires identity:read or identity:write scope.","parameters":[{"schema":{"type":"string"},"examples":{"":{"value":""},"balances":{"value":"balances"},"governance,balances,pending":{"value":"governance,balances,pending"}},"in":"query","name":"include","required":false,"description":"Comma-separated enrichments: `governance`, `balances`, `pending`. Defaults to ALL THREE, and each one costs a live query — governance and balances hit the network, balances once PER LINKED CHAIN. Pass `include=` (empty) when polling: `status` and `can_sign` are computed before any enrichment and are always returned, so a poll loop can skip the lot. A 90-second provisioning wait at a 3-second interval is roughly thirty of these, and the enrichments are re-fetched every time."},{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["identity:read","identity:write"],"x-scope-mode":"any","responses":{"200":{"description":"The identity plus any requested enrichments (governance, balances, pending).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The identity plus any requested enrichments (governance, balances, pending).","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"adi_url":{"type":"string"},"book_url":{"type":["null","string"]},"key_page_url":{"type":["null","string"]},"status":{"type":"string","description":"provisioning | active | error. Creation is async — poll until terminal."},"can_sign":{"type":["null","boolean"],"description":"False when an external-mode identity has no stored public key — it can never sign, and every POST /v1/sign for it will fail. Null means the on-chain key page could not be read: treat it as \"do not proceed\", never as a soft yes. Check this before building on the identity. Repairable with PATCH /v1/identity/{id} { public_key } if the hash matches."},"error_message":{"type":["null","string"],"description":"Why provisioning failed, when status is \"error\"."},"credit_balance":{"type":"number"},"chain_accounts":{"type":"array","items":{"type":"object","additionalProperties":true}},"created_at":{},"signing_mode":{"type":"string"},"signing_provider":{"type":"object","additionalProperties":true},"governance":{"type":"object","additionalProperties":true},"balances":{"type":"array","items":{"type":"object","additionalProperties":true}},"pending":{"type":"object","additionalProperties":true}}}}}},"401":{"description":"Missing or invalid authentication credentials.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid authentication credentials.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated principal lacks the required identity scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated principal lacks the required identity scope.","type":"object","additionalProperties":true}}}},"404":{"description":"No identity with this id exists for the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"No identity with this id exists for the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"patch":{"summary":"Update an identity's chains and webhook","tags":["Identity"],"description":"Compound update for a stored identity (org-scoped). Links new multi-chain accounts (deriving and deploying each chain account, skipping chains already linked), unlinks chains, and/or sets the webhook URL — all in one call. Chain deploys are fired best-effort and reflected with a \"deploying\"/\"failed\" status. The webhook_url is shape-validated at the route layer and DNS-validated before write. Returns the freshly re-fetched identity (same shape as GET). Requires identity:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"link_chains":{"type":"array","items":{"type":"string","maxLength":64}},"unlink_chains":{"type":"array","items":{"type":"string","maxLength":64}},"webhook_url":{"type":"string","maxLength":2048},"public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$"}}}}}},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Sets fields to the values given.","x-required-scopes":["identity:write"],"x-scope-mode":"any","responses":{"200":{"description":"The updated identity, re-fetched with default enrichments (same shape as GET /v1/identity/:id).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The updated identity, re-fetched with default enrichments (same shape as GET /v1/identity/:id).","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"adi_url":{"type":"string"},"book_url":{"type":["null","string"]},"key_page_url":{"type":["null","string"]},"status":{"type":"string","description":"provisioning | active | error. Creation is async — poll until terminal."},"can_sign":{"type":["null","boolean"],"description":"False when an external-mode identity has no stored public key — it can never sign, and every POST /v1/sign for it will fail. Null means the on-chain key page could not be read: treat it as \"do not proceed\", never as a soft yes. Check this before building on the identity. Repairable with PATCH /v1/identity/{id} { public_key } if the hash matches."},"error_message":{"type":["null","string"],"description":"Why provisioning failed, when status is \"error\"."},"credit_balance":{"type":"number"},"chain_accounts":{"type":"array","items":{"type":"object","additionalProperties":true}},"created_at":{},"signing_mode":{"type":"string"},"signing_provider":{"type":"object","additionalProperties":true},"governance":{"type":"object","additionalProperties":true},"balances":{"type":"array","items":{"type":"object","additionalProperties":true}},"pending":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"Empty body or an invalid webhook_url.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Empty body or an invalid webhook_url.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid authentication credentials.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid authentication credentials.","type":"object","additionalProperties":true}}}},"402":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Always `PAYMENT_REQUIRED`."},"quote":{"type":"object","additionalProperties":true,"description":"The binding price for this work. Pass `quote_id` on the retry to hold it."},"balance":{"type":"object","additionalProperties":true,"properties":{"available_usd":{"type":"string"},"held_usd":{"type":"string"},"spendable_usd":{"type":"string"},"shortfall_usd":{"type":"string","description":"How much is missing. Send at least this."}}},"resolve":{"type":["null","object"],"additionalProperties":true,"description":"A live way to pay, built at the moment of refusal. Null if no chain accepts deposits.","properties":{"payment_intent":{"type":"string","description":"Reference for GET /v1/billing/deposits/{reference}."},"chain":{"type":"string"},"to_address":{"type":"string"},"amount_usd":{"type":"string","description":"Send EXACTLY this. Attribution matches the exact amount."},"expires_at":{},"reused_existing":{"type":"boolean","description":"True when an already-open intent covered the shortfall. A retry loop reuses one intent rather than opening a new one per refusal."},"portal_url":{"type":"string"},"cli_command":{"type":"string"},"note":{"type":"string"}}},"how_to_pay":{"type":"object","additionalProperties":true,"description":"The same steps as endpoints, for a caller not using `resolve`."},"quote_expires_at":{"description":"Retry with the quote before this instant or it must be re-priced."}}}}}},"403":{"description":"Authenticated principal lacks the identity:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated principal lacks the identity:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"No identity with this id exists for the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"No identity with this id exists for the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"delete":{"summary":"Retire an identity","tags":["Identity"],"description":"Retire an identity this organization no longer manages, freeing the identity slot it occupies against the org quota. This is a soft delete inside CERTEN: the on-chain ADI, its key book and its key page are NOT touched and continue to exist on Accumulate — the organization simply stops tracking the identity here, and it no longer appears in the portfolio or the pending inbox. Use it to clean up failed creations (status \"error\") and identities created in testing. Requires the identity:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Already-retired stays retired.","x-required-scopes":["identity:write"],"x-scope-mode":"any","responses":{"200":{"description":"The identity was retired. Its quota slot is now free; the on-chain ADI is unaffected.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The identity was retired. Its quota slot is now free; the on-chain ADI is unaffected.","type":"object","additionalProperties":true,"properties":{"deleted":{"type":"boolean"},"id":{"type":"string"},"adi_url":{"type":"string"},"note":{"type":"string"}}}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the identity:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the identity:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Identity not found for this organization (or already retired).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Identity not found for this organization (or already retired).","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/identity/{id}/mnemonic/{token}":{"get":{"summary":"Retrieve a generated mnemonic once","tags":["Identity"],"description":"One-shot retrieval of the mnemonic generated when an identity was created with signing_mode \"provider\". The token is validated against an HMAC of the server-side master secret and consumed atomically, so the mnemonic can be fetched exactly once and never again. Requires identity:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true},{"schema":{"type":"string","minLength":16,"maxLength":128},"in":"path","name":"token","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["identity:write"],"x-scope-mode":"any","responses":{"200":{"description":"The plaintext mnemonic plus a warning that it cannot be retrieved again.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The plaintext mnemonic plus a warning that it cannot be retrieved again.","type":"object","additionalProperties":true,"properties":{"mnemonic":{"type":"string"},"warning":{"type":"string"}}}}}},"401":{"description":"Missing or invalid authentication credentials.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid authentication credentials.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated principal lacks the identity:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated principal lacks the identity:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Token is invalid, expired, or has already been consumed.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Token is invalid, expired, or has already been consumed.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portfolio":{"get":{"summary":"Get a portfolio of identities and balances","tags":["Portfolio"],"description":"Compound read that builds a portfolio view across the caller's identities. For each identity it fetches live wallet balances for every linked chain (in parallel, best-effort — failures surface as \"unavailable\") and the cached pending-action count, then aggregates a total chain count. Pass the optional `identity` query param (an ADI URL or identity id, org-scoped) to scope the result to a single identity; omit it to return all identities for the org. Requires portfolio:read, identity:read, or identity:write scope.","parameters":[{"schema":{"type":"string"},"in":"query","name":"identity","required":false,"description":"Scope to one identity: an ADI URL or identity id, org-scoped. Omit for all."}],"security":[{"apiKey":[]}],"x-required-scopes":["portfolio:read","identity:read","identity:write"],"x-scope-mode":"any","responses":{"200":{"description":"Portfolio entries with per-chain balances and pending counts, plus an aggregate chain total.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Portfolio entries with per-chain balances and pending counts, plus an aggregate chain total.","type":"object","additionalProperties":true,"properties":{"identities":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"adi_url":{"type":"string"},"status":{"type":"string"},"credit_balance":{"type":"number"},"chains":{"type":"array","items":{"type":"object","additionalProperties":true}},"pending_actions":{"type":"number"}}}},"total_chains":{"type":"number"}}}}}},"401":{"description":"Missing or invalid authentication credentials.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid authentication credentials.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated principal lacks a required portfolio/identity scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated principal lacks a required portfolio/identity scope.","type":"object","additionalProperties":true}}}},"404":{"description":"The `identity` filter matched no identity in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The `identity` filter matched no identity in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/org":{"post":{"summary":"Create organization","tags":["Admin"],"description":"Provisions a new organization. Because the new org row can never match the caller's own org id, the request escalates to an admin context to bypass per-request row-level-security for the INSERT. Requires the admin:write scope and supports Idempotency-Key for safe retries.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"plan":{"type":"string","enum":["starter","pro","enterprise"]},"webhook_url":{"type":"string","maxLength":2048}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"header","name":"idempotency-key","required":false,"description":"Optional. Repeat a request safely: an identical retry with the SAME key returns the stored response instead of performing the work twice, and the replay is marked with an `X-Idempotency-Replay` header. Reusing a key with a DIFFERENT body is rejected (IDEMPOTENCY_KEY_MISMATCH); retrying while the first is still running returns IDEMPOTENCY_KEY_IN_FLIGHT, which means wait and retry the same key — never a new one."}],"security":[{"apiKey":[]}],"x-idempotent":true,"x-retry-safety":"idempotent-with-key","x-retry-note":"Creates an organization.","x-required-scopes":["admin:write"],"x-scope-mode":"any","responses":{"201":{"description":"Organization created.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Organization created.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"plan":{"type":"string"},"created_at":{"type":"string"}}}}}},"400":{"description":"Invalid body or webhook_url failed outbound validation.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid body or webhook_url failed outbound validation.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/org/{id}/webhook":{"post":{"summary":"Configure org webhook","tags":["Admin"],"description":"Sets the legacy single webhook_url and signing secret on an organization. The id MUST equal the caller's own org unless the key holds the wildcard (*) permission; cross-org writes are otherwise rejected 403. A secret is generated when omitted, and the secret is returned ONCE and cannot be retrieved again. Requires the admin:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["webhook_url"],"properties":{"webhook_url":{"type":"string","maxLength":2048},"webhook_secret":{"type":"string","minLength":16,"maxLength":255}}}}},"required":true},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Sets the webhook URL.","x-required-scopes":["admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Webhook configured; secret returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Webhook configured; secret returned once.","type":"object","additionalProperties":true,"properties":{"webhook_url":{"type":"string"},"webhook_secret":{"type":"string"},"warning":{"type":"string"}}}}}},"400":{"description":"Invalid webhook_url or secret shorter than 16 characters.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid webhook_url or secret shorter than 16 characters.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"Attempted to configure another org without wildcard scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Attempted to configure another org without wildcard scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Organization not found.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Organization not found.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/api-keys":{"post":{"summary":"Mint API key","tags":["Admin"],"description":"Mints a new API key for an organization. The body org_id MUST equal the caller's own org; cross-tenant minting is rejected 403. The raw key is returned ONCE and cannot be retrieved again. Requires the admin:write scope and supports Idempotency-Key.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name","org_id"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"org_id":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"permissions":{"type":"array","items":{"type":"string","maxLength":64}},"rate_limit_rpm":{"type":"number","minimum":1,"maximum":100000},"expires_at":{"type":"string"}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"header","name":"idempotency-key","required":false,"description":"Optional. Repeat a request safely: an identical retry with the SAME key returns the stored response instead of performing the work twice, and the replay is marked with an `X-Idempotency-Replay` header. Reusing a key with a DIFFERENT body is rejected (IDEMPOTENCY_KEY_MISMATCH); retrying while the first is still running returns IDEMPOTENCY_KEY_IN_FLIGHT, which means wait and retry the same key — never a new one."}],"security":[{"apiKey":[]}],"x-idempotent":true,"x-retry-safety":"idempotent-with-key","x-retry-note":"Mints a key. Without a key a retry mints a second one, and the secret is shown once.","responses":{"201":{"description":"API key created; raw key returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key created; raw key returned once.","type":"object","additionalProperties":true,"properties":{"api_key":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string"},"rate_limit_rpm":{"type":["null","number"]},"permissions":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string"}}},"warning":{"type":"string"}}}}}},"400":{"description":"Invalid request body.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid request body.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"org_id does not match the caller's org, or missing admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"org_id does not match the caller's org, or missing admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Organization not found.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Organization not found.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"get":{"summary":"List API keys","tags":["Admin"],"description":"Lists all API keys belonging to the caller's organization. Raw key material is never returned — only the prefix and metadata. Requires the admin:read or admin:write scope.","security":[{"apiKey":[]}],"x-required-scopes":["admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"API keys for the org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API keys for the org.","type":"object","additionalProperties":true,"properties":{"api_keys":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string"},"org_id":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"rate_limit_rpm":{"type":["null","number"]},"is_active":{"type":"boolean"},"created_at":{"type":"string"},"expires_at":{"type":["null","string"]},"last_used_at":{"type":["null","string"]}}}}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks an admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks an admin scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/api-keys/{id}/rotate":{"post":{"summary":"Rotate API key","tags":["Admin"],"description":"Mints a replacement key inheriting the old key's permissions, rate limit, and expiry, then deactivates the old key. The new raw key is returned ONCE and cannot be retrieved again. Scoped to the caller's own org and requires the admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Same as the portal rotation: a retry invalidates the secret just issued.","x-required-scopes":["admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Key rotated; new raw key returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Key rotated; new raw key returned once.","type":"object","additionalProperties":true,"properties":{"previous_id":{"type":"string"},"new_key":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"key":{"type":"string"},"prefix":{"type":"string"},"name":{"type":"string"},"rate_limit_rpm":{"type":["null","number"]},"permissions":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string"}}},"warning":{"type":"string"}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"API key not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/audit-log":{"get":{"summary":"List audit log entries","tags":["Admin"],"description":"Returns the organization's audit log, optionally filtered by action, resource_type, and from/to timestamps. Results are paginated (limit clamps to 1000, default 100) and a total count is included for paginate-to-completion loops. Requires the admin:read or admin:write scope.","parameters":[{"schema":{"type":"string"},"in":"query","name":"resource_type","required":false,"description":"Filter by the kind of resource acted on."},{"schema":{"type":"string"},"in":"query","name":"action","required":false,"description":"Filter by action."},{"schema":{"type":"string"},"in":"query","name":"from","required":false,"description":"ISO timestamp, inclusive lower bound."},{"schema":{"type":"string"},"in":"query","name":"to","required":false,"description":"ISO timestamp, inclusive upper bound."},{"schema":{"type":"string"},"in":"query","name":"limit","required":false,"description":"Page size. Default 100, max 1000; clamped to bounds."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip. Default 0."}],"security":[{"apiKey":[]}],"x-required-scopes":["admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Audit log entries with pagination metadata.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Audit log entries with pagination metadata.","type":"object","additionalProperties":true,"properties":{"entries":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true,"properties":{"limit":{"type":"number"},"offset":{"type":"number"},"total":{"type":"number"},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid pagination parameters.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid pagination parameters.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks an admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks an admin scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/api-keys/{id}":{"delete":{"summary":"Revoke API key","tags":["Admin"],"description":"Deactivates (revokes) an API key in the caller's organization. The key is immediately unusable. Returns 204 with no body on success. Requires the admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Already-revoked stays revoked.","x-required-scopes":["admin:write"],"x-scope-mode":"any","responses":{"204":{"description":"API key revoked; no content.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"API key not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/usage":{"get":{"summary":"Get usage summary","tags":["Admin"],"description":"Returns an aggregated usage summary for the caller's org over a time window. The window defaults to the trailing 30 days when from/to are omitted; both are parsed as dates. Requires the admin:read or admin:write scope.","parameters":[{"schema":{"type":"string"},"in":"query","name":"from","required":false,"description":"Window start. Defaults to 30 days ago."},{"schema":{"type":"string"},"in":"query","name":"to","required":false,"description":"Window end. Defaults to now."}],"security":[{"apiKey":[]}],"x-required-scopes":["admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Usage summary for the requested period.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Usage summary for the requested period.","type":"object","additionalProperties":true,"properties":{"period":{"type":"object","additionalProperties":true,"properties":{"from":{"type":"string"},"to":{"type":"string"}}},"total_requests":{"type":"number"},"successful_requests":{"type":"number"},"by_endpoint":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"endpoint":{"type":"string"},"count":{"type":"number"}}}},"daily":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"date":{"type":"string"},"count":{"type":"number"}}}}}}}}},"400":{"description":"Invalid date format for from/to.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid date format for from/to.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks an admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks an admin scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/deliveries":{"get":{"summary":"List webhook deliveries","tags":["Webhooks"],"description":"Lists webhook delivery records for the caller's org (the delivery dead-letter queue view), optionally filtered by status. Results are paginated (limit clamps to 500, default 50) and include a total count. Requires webhook:read — admin:read and admin:write also satisfy it, for keys issued before webhook:read existed.","parameters":[{"schema":{"type":"string"},"in":"query","name":"status","required":false,"description":"Filter by delivery status."},{"schema":{"type":"string"},"in":"query","name":"limit","required":false,"description":"Page size. Default 50, max 500; clamped to bounds."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip. Default 0."}],"security":[{"apiKey":[]}],"x-required-scopes":["webhook:read","webhook:write","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Webhook deliveries with pagination metadata.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Webhook deliveries with pagination metadata.","type":"object","additionalProperties":true,"properties":{"deliveries":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true,"properties":{"limit":{"type":"number"},"offset":{"type":"number"},"total":{"type":"number"},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid pagination parameters.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid pagination parameters.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the webhook:read scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the webhook:read scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/deliveries/{id}":{"get":{"summary":"Get webhook delivery","tags":["Webhooks"],"description":"Returns a single webhook delivery, including the exact signed_canonical_payload bytes the gateway HMAC'd plus the signature metadata so a customer-side verifier can reproduce the signature. Scoped to the caller's org. Requires the admin:read or admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["webhook:read","webhook:write","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Webhook delivery detail.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Webhook delivery detail.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"org_id":{"type":"string"},"event_type":{"type":"string"},"status":{"type":"string"},"attempts":{"type":"number"},"response_status":{"type":["null","number"]},"signature_version":{"type":["null","string"]},"signature_hex":{"type":["null","string"]},"signed_at_unix":{"type":["null","number"]},"signed_canonical_payload":{"type":["null","string"]},"delivery_id_header":{"type":["null","string"]},"last_error":{"type":["null","string"]},"dlq_at":{"type":["null","string"]},"created_at":{"type":"string"}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks an admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks an admin scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Delivery not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Delivery not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/deliveries/{id}/redeliver":{"post":{"summary":"Redeliver webhook","tags":["Webhooks"],"description":"Requeues a webhook delivery and fires it immediately; if the attempt fails the retry poller resumes it. Scoped to the caller's org. Requires the admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Unsafe BY DESIGN — delivering again is the entire purpose. Listed so it is a decision rather than an omission.","x-required-scopes":["webhook:write","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Delivery requeued.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Delivery requeued.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"status":{"type":"string"}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Delivery not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Delivery not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/endpoints":{"post":{"summary":"Register webhook endpoint","tags":["Webhooks"],"description":"Registers a webhook endpoint for the caller's org (multiple endpoints per org are supported). Unless skip_verification is true, a verification ping carrying a challenge token is sent and the endpoint is marked verified on a 2xx response. A secret is generated when omitted, and the secret is returned ONCE and cannot be retrieved again. Requires the admin:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","maxLength":2048},"secret":{"type":"string","minLength":16,"maxLength":255},"event_types":{"type":"array","items":{"type":"string","maxLength":64}},"description":{"type":"string","maxLength":255},"skip_verification":{"type":"boolean"}}}}},"required":true},"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call registers another endpoint, so the same event fires twice.","x-required-scopes":["webhook:write","admin:write"],"x-scope-mode":"any","responses":{"201":{"description":"Endpoint registered; secret returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Endpoint registered; secret returned once.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"event_types":{"type":["null","array"],"items":{"type":"string"}},"description":{"type":["null","string"]},"is_active":{"type":"boolean"},"verified":{"type":"boolean"},"verification_error":{"type":["null","string"]},"created_at":{"type":"string"},"secret":{"type":"string"},"warning":{"type":"string"}}}}}},"400":{"description":"Invalid body or url failed outbound validation.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid body or url failed outbound validation.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"get":{"summary":"List webhook endpoints","tags":["Webhooks"],"description":"Lists the webhook endpoints registered for the caller's org. Secrets are never returned — only metadata and health fields (verification and failure counts). Requires the admin:read or admin:write scope.","security":[{"apiKey":[]}],"x-required-scopes":["webhook:read","webhook:write","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Webhook endpoints for the org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Webhook endpoints for the org.","type":"object","additionalProperties":true,"properties":{"endpoints":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"event_types":{"type":["null","array"],"items":{"type":"string"}},"description":{"type":["null","string"]},"is_active":{"type":"boolean"},"verified_at":{"type":["null","string"]},"last_success_at":{"type":["null","string"]},"consecutive_failures":{"type":"number"},"created_at":{"type":"string"}}}}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks an admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks an admin scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/endpoints/{id}/verify":{"post":{"summary":"Re-verify webhook endpoint","tags":["Webhooks"],"description":"Re-runs the verification ping against an existing webhook endpoint and marks it verified on a 2xx response. Returns 200 in both the success and failure cases with a verified flag and an error message. Scoped to the caller's org. Requires the admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Sends a test ping; changes no stored state.","x-required-scopes":["webhook:write","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Verification result (success or failure).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Verification result (success or failure).","type":"object","additionalProperties":true,"properties":{"verified":{"type":"boolean"},"error":{"type":["null","string"]}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Endpoint not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Endpoint not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/endpoints/{id}":{"patch":{"summary":"Update webhook endpoint","tags":["Webhooks"],"description":"Updates an existing webhook endpoint's url, event_types, description, or is_active flag. Changing the url re-validates it against outbound rules and invalidates the prior verification (verified_at is cleared). Scoped to the caller's org. Requires the admin:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","maxLength":2048},"event_types":{"type":"array","items":{"type":"string"},"maxItems":64},"description":{"type":["string","null"],"maxLength":500},"is_active":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Sets fields to the values given.","x-required-scopes":["webhook:write","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Endpoint updated.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Endpoint updated.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"event_types":{"type":["null","array"],"items":{"type":"string"}},"description":{"type":["null","string"]},"is_active":{"type":"boolean"},"verified_at":{"type":["null","string"]},"consecutive_failures":{"type":"number"}}}}}},"400":{"description":"Invalid body or url failed outbound validation.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid body or url failed outbound validation.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Endpoint not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Endpoint not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"delete":{"summary":"Delete webhook endpoint","tags":["Webhooks"],"description":"Deactivates (deletes) a webhook endpoint in the caller's org so it stops receiving deliveries. Returns 204 with no body on success. Requires the admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Deletion is a state.","x-required-scopes":["webhook:write","admin:write"],"x-scope-mode":"any","responses":{"204":{"description":"Endpoint deactivated; no content.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Endpoint not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Endpoint not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/webhooks/endpoints/{id}/rotate-secret":{"post":{"summary":"Rotate webhook secret","tags":["Webhooks"],"description":"Rotates the signing secret for a webhook endpoint. A new secret is generated when one is not supplied, and the new secret is returned ONCE and cannot be retrieved again. Scoped to the caller's org. Requires the admin:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"secret":{"type":"string","minLength":16,"maxLength":255}}}}}},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"A retry invalidates the signing secret just issued.","x-required-scopes":["webhook:write","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Secret rotated; returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Secret rotated; returned once.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"secret":{"type":"string"},"secret_rotated_at":{"type":["null","string"]},"warning":{"type":"string"}}}}}},"400":{"description":"Supplied secret shorter than 16 characters.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Supplied secret shorter than 16 characters.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Endpoint not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Endpoint not found in the caller's org.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/signing-providers":{"get":{"summary":"List signing providers","tags":["Admin"],"description":"Lists the signing providers configured for the caller's org, each annotated with its signing-log activity count and last-success timestamp. Requires the admin:read or admin:write scope.","security":[{"apiKey":[]}],"x-required-scopes":["admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Signing providers with activity stats.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Signing providers with activity stats.","type":"object","additionalProperties":true,"properties":{"providers":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"provider_type":{"type":"string"},"status":{"type":"string"},"signing_log_count":{"type":"number"},"last_signed_at":{"type":["null","string"]},"created_at":{"type":"string"},"revoked_at":{"type":["null","string"]}}}}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks an admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks an admin scope.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/signing-providers/{id}":{"delete":{"summary":"Revoke signing provider","tags":["Admin"],"description":"Revokes a signing provider in the caller's org and reports how many identities were affected by the revocation. Returns 200 with the revoked provider status; a missing or already-revoked provider yields 404. Requires the admin:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Deletion is a state.","x-required-scopes":["admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Provider revoked.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Provider revoked.","type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"status":{"type":"string"},"affected_identities":{"type":"number"}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"403":{"description":"API key lacks the admin:write scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"API key lacks the admin:write scope.","type":"object","additionalProperties":true}}}},"404":{"description":"Signing provider not found or already revoked.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Signing provider not found or already revoked.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transaction":{"post":{"summary":"Create an Accumulate transaction intent","tags":["Transaction"],"description":"Prepares an Accumulate transaction intent following the propose -> sign -> submit pattern. For external-mode identities the response returns `signing_data.hash_to_sign` plus a `submit_url`; the caller signs client-side and posts the signature to POST /v1/transaction/{id}/signature. For provider-mode identities the gateway auto-signs and submits in one step, returning the submitted `tx_hash`. Supports an Idempotency-Key (header or `idempotency_key` body field): a prior non-failed intent for the same key is returned unchanged. Requires the `transaction:write` scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["identity_id","intent"],"properties":{"identity_id":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"quote_id":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"intent":{"type":"object","additionalProperties":true,"description":"The transaction intent. Two shapes are accepted: (a) a single native transfer ({ fromChain, toChain, amount, fromAddress, toAddress, tokenSymbol }); or (b) a multi-leg / contract-call intent ({ adiUrl, legs: [...] }). For an arbitrary AUTHORIZED contract call, use the multi-leg shape with a leg carrying a `contractCall` spec — the validators execute exactly that call, gated on the multi-validator proof, with the same rigor as a value transfer.","properties":{"adiUrl":{"type":"string","description":"Signer ADI for a multi-leg intent, e.g. acc://your-org.acme"},"additionalAuthorities":{"type":["array","null"],"items":{"type":"string"},"description":"Same as the top-level `additional_authorities`. If both are sent they must agree."},"expiresAt":{"type":["string","null"],"description":"Same as the top-level `expires_at`. If both are sent they must agree."},"legs":{"type":"array","description":"Execution legs; include `contractCall` on a leg to execute an arbitrary function.","items":{"type":"object","additionalProperties":true,"properties":{"legId":{"type":"string"},"chain":{"type":"string","description":"e.g. ethereum-sepolia"},"chainId":{"type":"number"},"fromAddress":{"type":"string","description":"The identity's abstract account (on-chain msg.sender)"},"toAddress":{"type":"string","description":"Recipient (native) or the target contract (call)"},"amount":{"type":"string","description":"Native value in WHOLE UNITS, not base units — \"1\" means 1 ETH, \"0.5\" means half. api-bridge multiplies by the chain's decimals. Use \"0\" for a pure contract call. This field said \"base units (wei)\" until 2026-08-11, which was the exact inverse: an integrator following it and sending \"1\" for one wei moves a whole ETH, and on a funded account that succeeds silently."},"contractCall":{"type":"object","description":"RB-0 arbitrary contract call. The bridge ABI-encodes functionSignature+args; the validators execute target.call{value}(data), proof-gated to this EXACT (target,value,calldata).","required":["target","functionSignature"],"properties":{"target":{"type":"string","description":"Contract address to call"},"value":{"type":"string","description":"Native wei forwarded with the call (default \"0\")"},"functionSignature":{"type":"string","description":"Human-readable, e.g. \"buy(bytes32)\" or \"note(bytes32,string)\""},"args":{"type":"array","description":"ABI args in signature order (bytes/bytes32 as 0x-hex, ints as string|number)"},"expectedEvents":{"type":"array","description":"RB-4: events the call MUST emit for validators to attest success (proof-of-effect, not mere non-revert).","items":{"type":"object","properties":{"contract":{"type":"string","description":"Emitting contract (usually the target)"},"topic0":{"type":"string","description":"keccak256 of the event signature, e.g. keccak256(\"Paid(bytes32,address)\")"},"dataHash":{"type":"string","description":"Optional keccak256 of expected non-indexed event data"}}}}}}}}}}},"contract_addresses":{"type":"object","additionalProperties":true,"description":"Per-deployment contract addresses (anchor, anchorV2, abstractAccount, entryPoint, factory). Defaults are applied when omitted."},"proof_class":{"type":"string","enum":["on_demand","on_cadence"],"default":"on_demand","description":"How the proof cycle is scheduled. `on_demand` (default) starts immediately and completes in roughly 60-110 seconds — use it for anything a user is waiting on. `on_cadence` batches this proof with others, which costs less per proof and takes longer; use it for bulk settlement where latency does not matter. The choice affects only WHEN the proof is produced, never what it proves or how strong it is."},"subject":{"type":"object","required":["adi"],"additionalProperties":false,"description":"The end user this transaction is about, carried to your policy engine as `subject`. It is an ASSERTION by you, not a proof by the user — nothing on chain binds it — so an engine treats it as an input to a decision, never as an authorization.","properties":{"adi":{"type":"string","pattern":"^acc://","description":"The user's Accumulate ADI, e.g. acc://alice.acme. The identity, and what enrollment bound."},"key_book":{"type":"string","pattern":"^acc://","description":"Optional hint, never the identity. A book can live under an ADI without governing it, and an ADI can be governed by several — so a book is one authority among N. It stops speaking for an identity when `UpdateAccountAuth` removes or disables it, not when its keys rotate; read the ADI authority set at verification time."},"id":{"type":"string","maxLength":256,"description":"Your own opaque reference for this person."}}},"idempotency_key":{"type":"string","maxLength":255},"additional_authorities":{"type":["array","null"],"items":{"type":"string"},"description":"Key books that must ALSO sign this one transaction, written into the Accumulate transaction header (`authorities`). At most 8; each an `acc://` key book URL; lowercased and deduplicated; never the principal ADI's own book (`<adi>/book`). Accumulate holds the transaction until every one signs. NOTE: refused with 422 `HEADER_AUTHORITY_NOT_EXECUTABLE` on deployments running the default `INTENT_HEADER_AUTHORITIES=reject`, because CERTEN validators do not yet count header-authority signatures (such an intent is delivered but never executes). Use an account-level authority on `<adi>/data` (POST /v1/governance add_authority) for a party that must approve execution."},"expires_at":{"type":["string","null"],"description":"RFC 3339 date-time with a timezone (e.g. `2026-09-14T12:00:00Z`), written into the transaction header as `expire.atTime` in whole seconds. Must be between INTENT_EXPIRY_MIN_S (default 60 s) and INTENT_EXPIRY_MAX_S (default 7 days) from now. If every required authority has not signed by then, the transaction can no longer complete and the intent ends `failed` with `reason_code: expired` (no fee, no gas). Omit, or send null, for no expiry."},"signer_key_page":{"type":"string","maxLength":512,"description":"Which key PAGE signs this intent, e.g. \"acc://org.acme/book/2\". Optional; defaults to the identity's key page. Must belong to the same key book. A book can hold several pages at different priorities — lower index is higher priority — and Accumulate authorizes at BOOK level, so any page can act with its own threshold. Use this to separate routine operations on a lower-priority page from escalation on a higher-priority one."},"signer_public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$","description":"Which seat on the identity's key page will sign this intent, as 64-char hex. Optional; defaults to the identity's bound key. Use it on an M-of-N panel so any seat can open a transaction — for example an agent proposing a resolution that a human then finalizes. Must be the bound key or a current member of the key page, and cannot be combined with a provider-signed identity. The returned hash_to_sign is bound to this key, so the signature posted to /signature must come from it."}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"header","name":"idempotency-key","required":false,"description":"Optional. Repeat a request safely: an identical retry with the SAME key returns the stored response instead of performing the work twice, and the replay is marked with an `X-Idempotency-Replay` header. Reusing a key with a DIFFERENT body is rejected (IDEMPOTENCY_KEY_MISMATCH); retrying while the first is still running returns IDEMPOTENCY_KEY_IN_FLIGHT, which means wait and retry the same key — never a new one."}],"security":[{"apiKey":[]}],"x-idempotent":true,"x-retry-safety":"idempotent-with-key","x-retry-note":"Billable. A replay returns the stored response without pricing or reserving again.","x-required-scopes":["transaction:write"],"x-scope-mode":"any","responses":{"201":{"description":"Intent created. External mode returns signing_data + submit_url (status `signing_required`); provider mode returns the submitted tx_hash (status `submitted`); an idempotent replay echoes the original intent with `idempotent: true`.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Intent created. External mode returns signing_data + submit_url (status `signing_required`); provider mode returns the submitted tx_hash (status `submitted`); an idempotent replay echoes the original intent with `idempotent: true`.","type":"object","additionalProperties":true,"properties":{"intent_id":{"type":"string"},"status":{"type":"string"},"signing_mode":{"type":"string","enum":["external","provider"]},"signing_data":{"type":"object","additionalProperties":true,"properties":{"request_id":{"type":"string"},"transaction_hash":{"type":"string"},"hash_to_sign":{"type":"string"}}},"submit_url":{"type":["null","string"]},"tx_hash":{"type":["null","string"]},"proof_id":{"type":["null","string"]},"additional_authorities":{"type":["null","array"],"items":{"type":"string"},"description":"Transaction-header additional authorities the intent was created with; null when none."},"expires_at":{"type":["null","string"],"description":"Transaction-header deadline (RFC 3339, whole seconds); null when the intent never expires."},"idempotent":{"type":"boolean"}}}}}},"400":{"description":"Validation failed (missing/invalid identity_id or intent, or identity not active/missing key configuration). Header conditions carry specific codes: ADDITIONAL_AUTHORITIES_INVALID, ADDITIONAL_AUTHORITY_IS_PRINCIPAL_BOOK, EXPIRES_AT_INVALID, EXPIRES_AT_OUT_OF_RANGE.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Validation failed (missing/invalid identity_id or intent, or identity not active/missing key configuration). Header conditions carry specific codes: ADDITIONAL_AUTHORITIES_INVALID, ADDITIONAL_AUTHORITY_IS_PRINCIPAL_BOOK, EXPIRES_AT_INVALID, EXPIRES_AT_OUT_OF_RANGE.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"402":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Always `PAYMENT_REQUIRED`."},"quote":{"type":"object","additionalProperties":true,"description":"The binding price for this work. Pass `quote_id` on the retry to hold it."},"balance":{"type":"object","additionalProperties":true,"properties":{"available_usd":{"type":"string"},"held_usd":{"type":"string"},"spendable_usd":{"type":"string"},"shortfall_usd":{"type":"string","description":"How much is missing. Send at least this."}}},"resolve":{"type":["null","object"],"additionalProperties":true,"description":"A live way to pay, built at the moment of refusal. Null if no chain accepts deposits.","properties":{"payment_intent":{"type":"string","description":"Reference for GET /v1/billing/deposits/{reference}."},"chain":{"type":"string"},"to_address":{"type":"string"},"amount_usd":{"type":"string","description":"Send EXACTLY this. Attribution matches the exact amount."},"expires_at":{},"reused_existing":{"type":"boolean","description":"True when an already-open intent covered the shortfall. A retry loop reuses one intent rather than opening a new one per refusal."},"portal_url":{"type":"string"},"cli_command":{"type":"string"},"note":{"type":"string"}}},"how_to_pay":{"type":"object","additionalProperties":true,"description":"The same steps as endpoints, for a caller not using `resolve`."},"quote_expires_at":{"description":"Retry with the quote before this instant or it must be re-priced."}}}}}},"404":{"description":"Identity not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Identity not found for this organization.","type":"object","additionalProperties":true}}}},"422":{"description":"`HEADER_AUTHORITY_NOT_EXECUTABLE`: `additional_authorities` was supplied on a deployment that refuses them (INTENT_HEADER_AUTHORITIES=reject, the default) because CERTEN validators do not yet count header-authority signatures. Use an account-level authority instead.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"`HEADER_AUTHORITY_NOT_EXECUTABLE`: `additional_authorities` was supplied on a deployment that refuses them (INTENT_HEADER_AUTHORITIES=reject, the default) because CERTEN validators do not yet count header-authority signatures. Use an account-level authority instead.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Downstream api-bridge failed to prepare or submit the intent.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Downstream api-bridge failed to prepare or submit the intent.","type":"object","additionalProperties":true}}}}}}},"/v1/transaction/{id}/signature":{"post":{"summary":"Submit signature for a transaction intent","tags":["Transaction"],"description":"Submits the caller-computed signature for an external-mode transaction intent — the single initiating signature over the `hash_to_sign` returned by POST /v1/transaction. The submitted `public_key` must match the identity's bound key. On success the gateway relays the signed intent to api-bridge and atomically transitions the intent from `signing_required` to `submitted`. Requires the `transaction:write` scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["signature","public_key"],"properties":{"signature":{"type":"string","pattern":"^[a-fA-F0-9]{128}$"},"public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$"}}}}},"required":true},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"A second submission loses the race and returns 409 rather than submitting twice.","x-required-scopes":["transaction:write"],"x-scope-mode":"any","responses":{"200":{"description":"Signature accepted and the signed transaction was submitted to Accumulate.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Signature accepted and the signed transaction was submitted to Accumulate.","type":"object","additionalProperties":true,"properties":{"intent_id":{"type":"string"},"status":{"type":"string"},"tx_hash":{"type":["null","string"]}}}}}},"400":{"description":"Invalid signature/public_key, intent not in `signing_required` state, public_key mismatch, or missing signing data.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid signature/public_key, intent not in `signing_required` state, public_key mismatch, or missing signing data.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"404":{"description":"Transaction intent or its underlying identity not found.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Transaction intent or its underlying identity not found.","type":"object","additionalProperties":true}}}},"409":{"description":"Intent was already submitted by a concurrent request (`CONFLICT`), or its `expires_at` passed before it was signed (`INTENT_EXPIRED`; the intent is now failed/expired).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Intent was already submitted by a concurrent request (`CONFLICT`), or its `expires_at` passed before it was signed (`INTENT_EXPIRED`; the intent is now failed/expired).","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Downstream api-bridge failed to submit the signed transaction.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Downstream api-bridge failed to submit the signed transaction.","type":"object","additionalProperties":true}}}}}}},"/v1/transaction/{id}":{"get":{"summary":"Get a transaction intent status","tags":["Transaction"],"description":"Returns the current status of a transaction intent. Once the transaction is delivered and proven (status `completed` or `proven`), the response auto-joins the proof bundle (layers, governance, and attestations) fetched from proof-service under a `proof` object. Requires the `transaction:read` or `transaction:write` scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["transaction:read","transaction:write"],"x-scope-mode":"any","responses":{"200":{"description":"The transaction intent, including a joined `proof` object when delivered and proven.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The transaction intent, including a joined `proof` object when delivered and proven.","type":"object","additionalProperties":true,"properties":{"intent_id":{"type":"string"},"identity_id":{"type":"string"},"status":{"type":"string"},"intent_type":{"type":"string"},"completion_basis":{"type":["null","string"],"enum":["proof_artifact","execution_observed","chain_receipt",null],"description":"What completion rested on. `proof_artifact`: the validators produced an indexed proof (`proof_id`). `execution_observed`: the gateway observed the destination-chain settlement; for a contract call it also read the settlement receipt and found every committed `expectedEvents` entry. A contract call whose committed events are absent never completes: it fails with `reason_code` `expectation_unmet` (or `expectation_unverifiable` when the receipt could not be read in time). Receipts are read on EVM chains only: a contract call whose legs are all on non-EVM chains completes on observed execution with `completion_evidence.expected_events.verified: false` (`reason: non_evm_receipts_not_supported`), and in a mixed intent only the EVM legs are checked. `chain_receipt` appears only on legacy rows. Null until the intent completes."},"accum_tx_hash":{"type":["null","string"]},"proof_id":{"type":["null","string"],"description":"The CERTEN proof artifact for this transaction. Feed it to POST /v1/proof/{id}/share to mint a link a counterparty can resolve without a CERTEN account. Null until the proof is anchored, which happens shortly AFTER the intent completes — an intent completes on measured on-chain execution, and the artifact is written when its batch anchors. This endpoint resolves it from the proof service on read, so it fills in on its own; poll briefly rather than treating the first null as final."},"proof_bundle_url":{"type":["null","string"]},"error_message":{"type":["null","string"]},"reason_code":{"type":["null","string"],"description":"Why the intent ended where it did, as a stable value rather than prose. `target_reverted` is the one to branch on: CERTEN validated, proved, anchored and submitted, and the DESTINATION CONTRACT rejected the call. That is a business outcome — it is billed, and retrying the identical call reverts again. Other values (for example `failed_mid`, `entitlement_stale`) mean CERTEN did not complete. Null when the intent has not ended, or ended without a recorded reason. `expired` means the `expires_at` deadline passed before every required authority signed; nothing executed and nothing is charged. A deadline-derived `expired` on a submitted transaction is re-checked for INTENT_EXPIRY_RECHECK_HOURS and reopened if Accumulate reports it delivered after all."},"additional_authorities":{"type":["null","array"],"items":{"type":"string"},"description":"Transaction-header additional authorities the intent was created with; null when none."},"expires_at":{"type":["null","string"],"description":"Transaction-header deadline (RFC 3339, whole seconds); null when the intent never expires."},"created_at":{},"updated_at":{},"completed_at":{},"proof":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"bundle_url":{"type":["null","string"]},"layers":{},"governance":{},"attestations":{}}}}}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"404":{"description":"Transaction intent not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Transaction intent not found for this organization.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transactions":{"get":{"summary":"List transaction intents","tags":["Transaction"],"description":"Returns a paginated list of the organization's transaction intents, newest first. Use `limit` (default 50, max 500) and `offset` (default 0) query parameters to page; both are clamped to their bounds. Requires the `transaction:read` or `transaction:write` scope.","parameters":[{"schema":{"type":"string"},"in":"query","name":"limit","required":false,"description":"Page size. Default 50, max 500; clamped to bounds."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip. Default 0; clamped to bounds."}],"security":[{"apiKey":[]}],"x-required-scopes":["transaction:read","transaction:write"],"x-scope-mode":"any","responses":{"200":{"description":"A page of transaction intents with the echoed pagination cursor.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"A page of transaction intents with the echoed pagination cursor.","type":"object","additionalProperties":true,"properties":{"transactions":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"intent_id":{"type":"string"},"identity_id":{"type":"string"},"status":{"type":"string"},"intent_type":{"type":"string"},"accum_tx_hash":{"type":["null","string"]},"proof_id":{"type":["null","string"]},"error_message":{"type":["null","string"]},"reason_code":{"type":["null","string"],"description":"Why the intent ended where it did. `target_reverted` means the destination contract rejected the call, which is a business outcome rather than a CERTEN failure. See GET /v1/transaction/{id}."},"additional_authorities":{"type":["null","array"],"items":{"type":"string"},"description":"Transaction-header additional authorities the intent was created with; null when none."},"expires_at":{"type":["null","string"],"description":"Transaction-header deadline (RFC 3339, whole seconds); null when the intent never expires."},"created_at":{},"updated_at":{},"completed_at":{}}}},"limit":{"type":"integer"},"offset":{"type":"integer"},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"400":{"description":"Pagination parameters must be non-negative numbers.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Pagination parameters must be non-negative numbers.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/proof/tx/{txHash}/receipt":{"get":{"summary":"Get the on-chain merkle receipt for a transaction","tags":["Proof"],"description":"Retrieve the Accumulate-native cryptographic receipt (merkle inclusion proof) for a transaction hash, read directly from the network. Works for ANY delivered transaction — including key-page/governance multi-signature authorization transactions that the Certen proof-service does not index. The receipt proves the transaction hash is anchored under a BVN/DN merkle root via the returned merkle path. Requires the proof:read scope.","parameters":[{"schema":{"type":"string"},"in":"path","name":"txHash","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"200":{"description":"Chain receipt for the transaction.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Chain receipt for the transaction.","type":"object","additionalProperties":true,"properties":{"tx_hash":{"type":"string"},"status":{"type":"string"},"principal":{"type":"string","nullable":true},"tx_type":{"type":"string","nullable":true},"anchored":{"type":"boolean"},"chain_index":{"type":"integer","nullable":true},"block_time":{"type":"string","nullable":true},"receipt":{"type":"object","additionalProperties":true,"nullable":true}}}}}},"404":{"description":"Transaction not found on the network.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Transaction not found on the network.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Failed to query the Accumulate network.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Failed to query the Accumulate network.","type":"object","additionalProperties":true}}}}}}},"/v1/proof/{id}":{"get":{"summary":"Get proof by proof ID","tags":["Proof"],"description":"Retrieve the cryptographic proof artifact for a proof ID, proxied from the proof-service. The proof payload is defined by the downstream service. Requires the proof:read scope.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"200":{"description":"Proof artifact (shape defined by the proof-service).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Proof artifact (shape defined by the proof-service).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Proof not found.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Proof not found.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Failed to fetch proof from the proof-service.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Failed to fetch proof from the proof-service.","type":"object","additionalProperties":true}}}}}}},"/v1/proof/{id}/bundle":{"get":{"summary":"Download proof bundle","tags":["Proof"],"description":"Retrieve the proof bundle for a proof ID, proxied from the proof-service. When the downstream returns a binary bundle the response is streamed as application/octet-stream (Content-Disposition attachment); otherwise the bundle is returned as JSON. No success response schema is declared so the raw binary body is passed through unmodified. Requires the proof:read scope.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Proof bundle not found.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Proof bundle not found.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Failed to fetch proof bundle from the proof-service.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Failed to fetch proof bundle from the proof-service.","type":"object","additionalProperties":true}}}}}}},"/v1/proof/{id}/custody":{"get":{"summary":"Get proof custody chain","tags":["Proof"],"description":"Retrieve the custody chain for a proof ID, proxied from the proof-service. The custody payload is defined by the downstream service. Requires the proof:read scope.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"200":{"description":"Custody chain (shape defined by the proof-service).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Custody chain (shape defined by the proof-service).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Custody chain not found.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Custody chain not found.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Failed to fetch custody chain from the proof-service.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Failed to fetch custody chain from the proof-service.","type":"object","additionalProperties":true}}}}}}},"/v1/proof/tx/{txHash}":{"get":{"summary":"Get proof by transaction hash","tags":["Proof"],"description":"Retrieve the cryptographic proof artifact for a given Accumulate transaction hash, proxied from the proof-service. The proof payload is defined by the downstream service. Requires the proof:read scope.","parameters":[{"schema":{"type":"string"},"in":"path","name":"txHash","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"200":{"description":"Proof artifact for the transaction hash (shape defined by the proof-service).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Proof artifact for the transaction hash (shape defined by the proof-service).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Proof not found for the given transaction hash.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Proof not found for the given transaction hash.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Failed to fetch proof by transaction hash from the proof-service.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Failed to fetch proof by transaction hash from the proof-service.","type":"object","additionalProperties":true}}}}}}},"/v1/proof/{id}/share":{"post":{"summary":"Create a shareable link for a proof","tags":["Proof"],"description":"Mint a time-boxed, revocable link that lets someone WITHOUT a Certen account fetch this proof bundle. The raw token is returned ONCE and is not recoverable afterwards — store the url or mint a new share. Requires the proof:read scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string","maxLength":200,"description":"Who this was shared with, and why. Shown when listing shares."},"expires_in_hours":{"type":"integer","minimum":1,"description":"How long the link stays valid. Defaults to 168h (7 days) and is capped at 2160h (90 days); both are deployment-configurable."},"max_views":{"type":"integer","minimum":1,"description":"Optional redemption ceiling. Omit for unlimited until expiry."}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call mints another live share link. A link is a credential — a retry leaves one you did not intend to hand out.","x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"201":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/proof/shares":{"get":{"summary":"List proof share links created by your organization","tags":["Proof"],"description":"Newest first. Never returns tokens — only their prefixes. Requires the proof:read scope.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false}],"security":[{"apiKey":[]}],"x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"shares":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/proof/shares/{shareId}":{"delete":{"summary":"Revoke a proof share link","tags":["Proof"],"description":"Takes effect immediately. Requires the proof:read scope.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"shareId","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Revoked stays revoked.","x-required-scopes":["proof:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/proof/shared/{token}":{"get":{"summary":"Fetch a shared proof bundle","tags":["Proof"],"description":"Redeem a share link and receive the full proof bundle. NO AUTHENTICATION — this is the endpoint a counterparty uses, and requiring a Certen credential to verify a Certen proof would defeat the purpose. Authorization is possession of the token.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"410":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/chains":{"get":{"summary":"List the chains CERTEN is deployed on, with contract addresses","description":"The contract registry: anchor, account factory, and BLS/ZK verifier addresses per network, with explorer links and on-chain verification status. PUBLIC — no API key required. `verified: true` means the address was confirmed to carry contract bytecode via eth_getCode; non-EVM entries are transcribed from validator configuration and are not independently verified.","parameters":[{"schema":{"type":"string"},"in":"query","name":"family","required":false,"description":"Filter by family: evm, solana, move, near, ton, tron."},{"schema":{"type":"boolean"},"in":"query","name":"verified_only","required":false,"description":"Only networks whose contracts are all on-chain verified."}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/chains/{id}":{"get":{"summary":"Get one chain and its contract addresses","description":"Accepts either the registry id (`ethereum-sepolia`) or the numeric EVM chain id (`11155111`). PUBLIC — no API key required.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/scopes":{"get":{"summary":"List every permission a key can be granted","description":"The full scope vocabulary, what each permits, and which operations require it. Read this before minting a key: the names are not guessable, and the safe-looking response to uncertainty — granting `*` — hands a key far more than it needs. `operations` is derived from the live route table, so it cannot drift from what is enforced. Public: choosing permissions should not require already holding a credential.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"scopes":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string"},"description":{"type":"string"},"audience":{"type":"string","enum":["customer","operator"],"description":"Operator scopes are for the fee console and platform staff."},"operations":{"type":"array","items":{"type":"string"},"description":"Operations requiring this scope, as `METHOD /path`."}}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/errors":{"get":{"summary":"Every error code this API can return","description":"The full error vocabulary: what each code means, whether retrying an identical request can ever succeed, and whether it is something the caller can act on at all. Published because the codes were previously discoverable only by provoking them. `retryable` answers \"will repeating this exact request eventually work?\" — not \"whose fault is it\": a 503 from an unavailable rate oracle is retryable, a 402 is not, because retrying without paying changes nothing. Public: a client should be able to build its error handling before it holds a credential.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"errors":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string"},"status":{"type":"integer"},"meaning":{"type":"string"},"retryable":{"type":"boolean"},"audience":{"type":"string","enum":["caller","platform"],"description":"`platform` means nothing the caller can change."},"action":{"type":"string"}}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/registration-tokens":{"post":{"summary":"Mint a registration token","tags":["Registration"],"description":"Mints a one-time token that lets a machine create a NEW organization and its first API key without a browser session. The token is returned once and cannot be retrieved again. What the new organization will be — its plan, and what its first key may do — is fixed here by the minter, not chosen by the redeemer. Operator and wildcard scopes cannot be granted this way. Requires the org:invite scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"org_name":{"type":"string","minLength":1,"maxLength":255,"description":"Name for the organization this creates. The redeemer may override it."},"plan":{"type":"string","enum":["starter","pro","enterprise"],"description":"Defaults to starter."},"permissions":{"type":"array","items":{"type":"string"},"description":"Scopes for the redeemed org's first key. Defaults to a read/write set for identity, transactions, proofs and billing reads."},"expires_in":{"type":"integer","minimum":60,"description":"Seconds until it expires. Default 86400, clamped to the configured ceiling."},"note":{"type":"string","maxLength":500,"description":"For your own records — who this was issued to."}}}}}},"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call mints a DIFFERENT token. A retry leaves a second live token that can create a second organization.","x-required-scopes":["org:invite","admin:write"],"x-scope-mode":"any","responses":{"201":{"description":"Token minted. The `token` field appears here and nowhere else.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Token minted. The `token` field appears here and nowhere else.","type":"object","additionalProperties":true,"properties":{"token":{"type":"string"},"id":{"type":"string"},"expires_at":{},"permissions":{"type":"array","items":{"type":"string"}},"warning":{"type":"string"}}}}}},"400":{"description":"Ungrantable scopes, or an empty permission list.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Ungrantable scopes, or an empty permission list."}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Missing or invalid API key."}}}},"403":{"description":"API key lacks the org:invite scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"API key lacks the org:invite scope."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"get":{"summary":"List registration tokens this organization minted","tags":["Registration"],"description":"Lists tokens minted by the caller's organization, newest first, with the state of each (active, redeemed, revoked, expired) and the organization it became. Never returns a token itself. Requires org:invite.","security":[{"apiKey":[]}],"x-required-scopes":["org:invite","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/registration-tokens/{id}":{"delete":{"summary":"Revoke an unredeemed registration token","tags":["Registration"],"description":"Stops a token being redeemed. Idempotent — revoking an already-revoked token succeeds, because the intent is already satisfied. A token that has ALREADY been redeemed cannot be revoked: the organization exists and cannot be un-created, so this returns 409 rather than implying otherwise. Requires org:invite.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Revocation is a state, not an event.","x-required-scopes":["org:invite","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Token revoked and can no longer be redeemed.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Token revoked and can no longer be redeemed."}}}},"404":{"description":"No such token in this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"No such token in this organization."}}}},"409":{"description":"Already redeemed — the organization exists.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Already redeemed — the organization exists."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/registration-tokens/redeem":{"post":{"summary":"Redeem a registration token for a new organization","tags":["Registration"],"description":"Exchanges a one-time registration token for a NEW organization and its first API key. Needs no credential — obtaining one is the point. The API key is returned once and cannot be retrieved again. Single-use: a second redemption of the same token is refused. Unknown, expired, revoked and already-redeemed tokens are all reported identically, so the endpoint cannot be used to probe which guesses were close.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":16,"maxLength":200},"org_name":{"type":"string","minLength":1,"maxLength":255,"description":"Overrides the name the minter pinned."}}}}},"required":true},"security":[],"x-retry-safety":"guarded-by-state","x-retry-note":"Single-use. A retry after success is refused, because the token was consumed when the organization was created.","responses":{"201":{"description":"Organization created. `api_key` appears here and nowhere else.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Organization created. `api_key` appears here and nowhere else.","type":"object","additionalProperties":true,"properties":{"org":{"type":"object","additionalProperties":true,"headers":{"x-ratelimit-limit":{"type":"integer"},"x-ratelimit-remaining":{"type":"integer"},"x-ratelimit-reset":{"type":"integer"}}},"api_key":{"type":"string"},"key_prefix":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"warning":{"type":"string"}}}}}},"404":{"description":"Token unknown, expired, revoked, or already redeemed.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Token unknown, expired, revoked, or already redeemed."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/signup/challenge":{"post":{"summary":"Get a nonce to sign","tags":["Registration"],"description":"Issues a single-use, short-lived nonce for keypair-proof signup. Sign the RAW BYTES of the returned hex nonce with your Ed25519 private key and present the detached signature to POST /v1/signup. Needs no credential — obtaining one is the point. Supplying public_key binds the challenge to that key, so nobody else can answer it.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"public_key":{"type":"string","description":"Your 32-byte Ed25519 public key as 64 hex characters. Recommended: it binds the challenge to you."}}}}}},"security":[],"x-retry-safety":"unsafe","x-retry-note":"Each call issues a DIFFERENT nonce. Harmless to repeat — the extra nonces simply expire unused — but the answer you must sign is the one from the LAST call.","responses":{"201":{"description":"A nonce to sign.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"A nonce to sign.","type":"object","additionalProperties":true,"properties":{"nonce":{"type":"string"},"expires_in":{"type":"integer"},"algorithm":{"type":"string"},"instructions":{"type":"string"}}}}}},"400":{"description":"public_key is not a 32-byte hex Ed25519 key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"public_key is not a 32-byte hex Ed25519 key."}}}},"403":{"description":"Self-service signup is not enabled on this gateway.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Self-service signup is not enabled on this gateway."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/signup":{"post":{"summary":"Create an organization by proving you hold a key","tags":["Registration"],"description":"Verifies an Ed25519 signature over a nonce from /v1/signup/challenge and provisions a NEW organization with its first API key. No browser, no email, no operator approval. The API key is returned once and cannot be retrieved again. One key provisions one organization, ever — a second attempt with the same key is refused.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["public_key","nonce","signature"],"properties":{"public_key":{"type":"string","description":"32-byte Ed25519 public key, 64 hex characters."},"nonce":{"type":"string","description":"The nonce from /v1/signup/challenge."},"signature":{"type":"string","description":"Detached Ed25519 signature over the nonce BYTES, as hex."},"org_name":{"type":"string","minLength":1,"maxLength":255}}}}},"required":true},"security":[],"x-retry-safety":"guarded-by-state","x-retry-note":"The nonce is consumed on the first attempt whether or not the signature verified, and one key provisions one organization. A retry needs a fresh challenge.","responses":{"201":{"description":"Organization created. `api_key` appears here and nowhere else.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Organization created. `api_key` appears here and nowhere else.","type":"object","additionalProperties":true,"properties":{"org":{"type":"object","additionalProperties":true},"api_key":{"type":"string"},"key_prefix":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"warning":{"type":"string"}}}}}},"400":{"description":"Bad signature, or a nonce that is unknown, expired or already used.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Bad signature, or a nonce that is unknown, expired or already used."}}}},"403":{"description":"Self-service signup is not enabled on this gateway.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Self-service signup is not enabled on this gateway."}}}},"409":{"description":"That key already provisioned an organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"That key already provisioned an organization."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/governance":{"post":{"summary":"Submit a governance operation","tags":["Governance"],"description":"Submit one or more governance operations against an identity's key page, its account authorities, or its key book. Authority operations take an optional account_url (the ADI or an account under it, e.g. its key book acc://<adi>/book) — Accumulate authorizes each account by its own authority set, so an authority on the ADI governs its data and token accounts, while one on the key book also governs changes to the key page. Key-page operations (add_key/remove_key, set_threshold for the M-of-N acceptThreshold, add_delegate/remove_delegate), authority operations (add_authority/remove_authority) and key-page creation (create_key_page) are three separate kinds and cannot be mixed in a single request. create_key_page takes public_key_hash and adds a page to the identity's book: a book holds pages at descending priority (lower index is higher priority) and a page may only be modified by one of equal or higher priority, so a new lower-priority page can carry automated seats while the existing page keeps the authority to rewrite it. Accumulate authorizes at BOOK level, so any page can act for the book's accounts with its own threshold — name the page on a transaction using signer_key_page. For external-mode identities the response returns signing_data (including hash_to_sign) and a submit_url for the two-step flow; for provider-mode identities the operation is auto-signed and submitted, returning a completed status with tx_hash. Build a multi-sig identity by adding keys (add_key) then raising the threshold (set_threshold) to reach M-of-N. Requires the governance:write scope and is idempotent via the Idempotency-Key header.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["identity","operations"],"properties":{"identity":{"type":"string","maxLength":512},"operations":{"type":"array","minItems":1,"maxItems":32,"items":{"type":"object"}},"key_page_url":{"type":"string","maxLength":512,"description":"Which key page these operations MODIFY, e.g. \"acc://org.acme/book/2\". Optional; defaults to the identity's own page. Combined with signer_key_page, this is how a higher-priority page rewrites a lower-priority one — rotating a compromised automated seat, for instance. Must belong to the same key book, and Accumulate rejects the operation on chain if the signing page is lower priority than the target."},"signer_key_page":{"type":"string","maxLength":512,"description":"Which key page SIGNS, e.g. \"acc://org.acme/book/2\". Optional; defaults to the identity's own page. Must belong to the same key book and must currently hold signer_public_key. The gateway does not judge whether the signing page outranks the target: Accumulate enforces that on chain, and an attempt that ought to fail is allowed to fail there, which is where the guarantee actually lives."},"signer_public_key":{"type":"string","maxLength":128,"description":"Public key that will sign, as hex. Optional; defaults to the identity's key. Must be a current key on signer_key_page. hash_to_sign commits to this key, so it is fixed at preparation time and a signature from any other key is invalid."}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"header","name":"idempotency-key","required":false,"description":"Optional. Repeat a request safely: an identical retry with the SAME key returns the stored response instead of performing the work twice, and the replay is marked with an `X-Idempotency-Replay` header. Reusing a key with a DIFFERENT body is rejected (IDEMPOTENCY_KEY_MISMATCH); retrying while the first is still running returns IDEMPOTENCY_KEY_IN_FLIGHT, which means wait and retry the same key — never a new one."}],"security":[{"apiKey":[]}],"x-idempotent":true,"x-retry-safety":"idempotent-with-key","x-retry-note":"Billable governance change. A replay returns the stored response.","x-required-scopes":["governance:write"],"x-scope-mode":"any","responses":{"201":{"description":"Governance operation created. External mode returns signing_data and submit_url; provider mode returns a completed operation with tx_hash.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Governance operation created. External mode returns signing_data and submit_url; provider mode returns a completed operation with tx_hash.","type":"object","additionalProperties":true,"properties":{"governance_op_id":{"type":"string"},"status":{"type":"string"},"tx_hash":{"type":"string"},"signing_mode":{"type":"string"},"signing_data":{"type":"object","additionalProperties":true},"submit_url":{"type":"string"}}}}}},"400":{"description":"Invalid request (missing fields, inactive identity, or mixed keypage/authority operations).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid request (missing fields, inactive identity, or mixed keypage/authority operations).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"402":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Payment required. NOTHING WAS CHARGED AND NO WORK WAS STARTED. The body carries everything needed to settle and retry: `quote` is the binding price, `balance.shortfall_usd` is how much is missing, and `resolve` is a live payment target — send EXACTLY `resolve.amount_usd` to `resolve.to_address` on `resolve.chain`, since attribution matches on the exact amount and a different figure will not credit automatically. Then repeat this request with `quote_id` set to `quote.quote_id` before `quote_expires_at`. `resolve` is null only when no chain is currently accepting deposits; the refusal itself is still correct.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Always `PAYMENT_REQUIRED`."},"quote":{"type":"object","additionalProperties":true,"description":"The binding price for this work. Pass `quote_id` on the retry to hold it."},"balance":{"type":"object","additionalProperties":true,"properties":{"available_usd":{"type":"string"},"held_usd":{"type":"string"},"spendable_usd":{"type":"string"},"shortfall_usd":{"type":"string","description":"How much is missing. Send at least this."}}},"resolve":{"type":["null","object"],"additionalProperties":true,"description":"A live way to pay, built at the moment of refusal. Null if no chain accepts deposits.","properties":{"payment_intent":{"type":"string","description":"Reference for GET /v1/billing/deposits/{reference}."},"chain":{"type":"string"},"to_address":{"type":"string"},"amount_usd":{"type":"string","description":"Send EXACTLY this. Attribution matches the exact amount."},"expires_at":{},"reused_existing":{"type":"boolean","description":"True when an already-open intent covered the shortfall. A retry loop reuses one intent rather than opening a new one per refusal."},"portal_url":{"type":"string"},"cli_command":{"type":"string"},"note":{"type":"string"}}},"how_to_pay":{"type":"object","additionalProperties":true,"description":"The same steps as endpoints, for a caller not using `resolve`."},"quote_expires_at":{"description":"Retry with the quote before this instant or it must be re-priced."}}}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Identity not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Identity not found for this organization.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/governance/{id}/signature":{"post":{"summary":"Submit signature for governance op","tags":["Governance"],"description":"Submit the detached signature for an external-mode governance operation that is awaiting signing. The operation must be in signing_required status; the signature is forwarded to the network and, on success, the operation transitions to completed with the resulting tx_hash. Requires the governance:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["signature","public_key"],"properties":{"signature":{"type":"string","pattern":"^[a-fA-F0-9]{128}$"},"public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$"}}}}},"required":true},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"The operation moves out of its pending state once signed.","x-required-scopes":["governance:write"],"x-scope-mode":"any","responses":{"200":{"description":"Signature accepted and governance operation submitted to the network.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Signature accepted and governance operation submitted to the network.","type":"object","additionalProperties":true,"properties":{"governance_op_id":{"type":"string"},"status":{"type":"string"},"tx_hash":{"type":"string"}}}}}},"400":{"description":"Invalid signature/public_key, or operation is not in signing_required status.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid signature/public_key, or operation is not in signing_required status.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Governance operation not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Governance operation not found for this organization.","type":"object","additionalProperties":true}}}},"409":{"description":"Governance operation was already completed by a concurrent request.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Governance operation was already completed by a concurrent request.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/governance/{id}":{"get":{"summary":"Get governance operation status","tags":["Governance"],"description":"Retrieve the current status and details of a governance operation, including its operation type, original request payload, signing data, resulting Accumulate transaction hash, and timestamps. Requires the governance:read or governance:write scope.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["governance:read","governance:write"],"x-scope-mode":"any","responses":{"200":{"description":"Governance operation status and details.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Governance operation status and details.","type":"object","additionalProperties":true,"properties":{"governance_op_id":{"type":"string"},"identity_id":{"type":"string"},"operation_type":{"type":"string"},"status":{"type":"string"},"request_payload":{"type":"object","additionalProperties":true},"signing_data":{"type":"object","additionalProperties":true,"nullable":true},"accum_tx_hash":{"type":"string","nullable":true},"created_at":{"type":"string"},"completed_at":{"type":"string","nullable":true}}}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Governance operation not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Governance operation not found for this organization.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/pending":{"get":{"summary":"List pending actions inbox","tags":["Pending"],"description":"Return the pending-actions inbox: every Accumulate transaction awaiting a signature from one of this organization's identities. Multi-sig items remain pending until their signing threshold is met. The response includes the enriched actions[] page, aggregate stats{}, and pagination{}. Filter by identity (adi_url), status, or category, and page with limit/offset. Signers poll this endpoint to see what needs signing, then sign items via POST /v1/sign. Requires the pending:read, identity:read, or identity:write scope.","parameters":[{"schema":{"type":"string"},"in":"query","name":"identity","required":false,"description":"Scope to one identity: an ADI URL or identity id, org-scoped."},{"schema":{"type":"string"},"in":"query","name":"status","required":false,"description":"Filter by pending-action status."},{"schema":{"type":"string"},"in":"query","name":"category","required":false,"description":"Filter by category, e.g. governance."},{"schema":{"type":"string"},"in":"query","name":"limit","required":false,"description":"Page size. Default 100, max 500; clamped to bounds."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip. Default 0; clamped to bounds."}],"security":[{"apiKey":[]}],"x-required-scopes":["pending:read","identity:read","identity:write"],"x-scope-mode":"any","responses":{"200":{"description":"Pending actions page with aggregate stats and pagination.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Pending actions page with aggregate stats and pagination.","type":"object","additionalProperties":true,"properties":{"actions":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"identity_id":{"type":"string","nullable":true},"identity_url":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"tx_hash":{"type":"string","nullable":true},"tx_id":{"type":"string","nullable":true},"principal":{"type":"string","nullable":true},"transaction_type":{"type":"string","nullable":true},"collected_signatures":{"type":"integer","nullable":true},"total_authorities":{"type":"integer","nullable":true},"required_signatures":{"type":"integer","nullable":true},"approved_authorities":{"type":"integer","nullable":true},"user_has_signed":{"type":"boolean","nullable":true},"memo":{"type":"string","nullable":true,"description":"What the transaction states it is FOR. Often the only thing separating a legitimate request from an unexplained one when the principal is an account the signer does not own. WRITTEN BY WHOEVER BUILT THE TRANSACTION — for an authority transaction that is not the signer — so treat it as untrusted text: render as plain text, never as markup, and never as though CERTEN endorsed the request. Truncated to 512 characters. Null when the transaction carries no memo."},"expires_at":{"type":"string","nullable":true},"discovered_at":{"type":"string","nullable":true},"created_at":{"type":"string","nullable":true}}}},"stats":{"type":"object","additionalProperties":true,"properties":{"total":{"type":"integer"},"has_more":{"type":"boolean"},"urgent":{"type":"integer"},"governance":{"type":"integer"},"transactions":{"type":"integer"},"awaiting_others":{"type":"integer"}}},"pagination":{"type":"object","additionalProperties":true,"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid pagination parameters (limit/offset must be non-negative numbers).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid pagination parameters (limit/offset must be non-negative numbers).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key / bearer token.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key / bearer token.","type":"object","additionalProperties":true}}}},"403":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Authenticated, but the key lacks the required scope. (This is what an insufficient scope actually returns; it was previously documented as 401.)","type":"object","additionalProperties":true}}}},"404":{"description":"Identity filter provided but not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Identity filter provided but not found for this organization.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/sign":{"post":{"summary":"Sign or vote on a multi-signature transaction","tags":["Sign"],"description":"Multi-signature signing entrypoint. Accumulate multi-sig: a transaction stays PENDING until the key page's acceptThreshold of signers have signed, at which point Accumulate executes it automatically. `type: \"pending_action\"` signs an inbox item (discovered via GET /v1/pending) by its pending-action id (`target_id`). `type: \"pending_tx\"` signs a pending transaction directly BY HASH (`target_id` = the pending tx hash, plus `identity`, `signer_url`, and `public_key`) — used to add an additional key-page signer's vote without waiting for inbox discovery. `type: \"transaction\"` is rejected (use POST /v1/transaction/{id}/signature instead). The signing data is computed for the ACTUAL signer's key, so any key on the page can cast a vote. Provider-mode identities auto-sign and submit the vote; external-mode identities receive signing_data + a submit_url for the 2-step flow. Requires the `sign:write` scope and supports an Idempotency-Key.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["type","target_id"],"properties":{"type":{"type":"string","enum":["pending_action","pending_tx","transaction"]},"target_id":{"type":"string","maxLength":256},"identity":{"type":"string","maxLength":512},"signer_url":{"type":"string","maxLength":512},"vote":{"type":"string","enum":["approve","reject","abstain"],"default":"approve"},"signature":{"type":"string","pattern":"^[a-fA-F0-9]{128}$"},"public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$"}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"header","name":"idempotency-key","required":false,"description":"Optional. Repeat a request safely: an identical retry with the SAME key returns the stored response instead of performing the work twice, and the replay is marked with an `X-Idempotency-Replay` header. Reusing a key with a DIFFERENT body is rejected (IDEMPOTENCY_KEY_MISMATCH); retrying while the first is still running returns IDEMPOTENCY_KEY_IN_FLIGHT, which means wait and retry the same key — never a new one."}],"security":[{"apiKey":[]}],"x-idempotent":true,"x-retry-safety":"idempotent-with-key","x-retry-note":"Creates a signing request. A replay returns the original.","x-required-scopes":["sign:write"],"x-scope-mode":"any","responses":{"201":{"description":"Sign request created. Provider mode returns `{status: \"signed\", tx_hash, signature_count, signing_mode: \"provider\"}`; external mode returns `{sign_request_id, status: \"signing_required\", signing_data, submit_url, expires_at}`.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Sign request created. Provider mode returns `{status: \"signed\", tx_hash, signature_count, signing_mode: \"provider\"}`; external mode returns `{sign_request_id, status: \"signing_required\", signing_data, submit_url, expires_at}`.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string"},"tx_hash":{"type":"string"},"signature_count":{"type":"integer"},"signing_mode":{"type":"string","enum":["provider"]},"sign_request_id":{"type":"string"},"signing_data":{"type":"object","additionalProperties":true,"properties":{"data_for_signature":{"type":"string"},"transaction_hash":{"type":"string"},"signer_url":{"type":"string"},"signer_version":{"type":"integer"},"timestamp":{"type":"integer"}}},"submit_url":{"type":"string"},"expires_at":{}}}}}},"400":{"description":"Invalid type, missing target_id/identity/public_key/signer_url, or `transaction` type (use /v1/transaction/{id}/signature).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid type, missing target_id/identity/public_key/signer_url, or `transaction` type (use /v1/transaction/{id}/signature).","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"404":{"description":"Pending action or identity not found for this organization.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Pending action or identity not found for this organization.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Downstream api-bridge failed to fetch signing data or submit the vote.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Downstream api-bridge failed to fetch signing data or submit the vote.","type":"object","additionalProperties":true}}}}}}},"/v1/sign/{id}/signature":{"post":{"summary":"Submit signature for an external sign request","tags":["Sign"],"description":"Submits the caller-computed signature for an external-mode sign request created by POST /v1/sign. The request is claimed atomically (a duplicate or expired submit returns 404), then the vote is relayed to Accumulate via api-bridge. Once enough key-page signers reach acceptThreshold, Accumulate executes the transaction. Requires the `sign:write` scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["signature","public_key"],"properties":{"signature":{"type":"string","pattern":"^[a-fA-F0-9]{128}$"},"public_key":{"type":"string","pattern":"^[a-fA-F0-9]{64}$"}}}}},"required":true},"parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"The request moves out of its pending state once signed.","x-required-scopes":["sign:write"],"x-scope-mode":"any","responses":{"200":{"description":"Signature accepted and the vote was relayed to Accumulate.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Signature accepted and the vote was relayed to Accumulate.","type":"object","additionalProperties":true,"properties":{"status":{"type":"string"},"tx_hash":{"type":"string"},"signature_count":{"type":"integer"}}}}}},"400":{"description":"Invalid signature or public_key format.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Invalid signature or public_key format.","type":"object","additionalProperties":true}}}},"401":{"description":"Missing or invalid API key.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Missing or invalid API key.","type":"object","additionalProperties":true}}}},"404":{"description":"Sign request not found, already consumed, or expired.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Sign request not found, already consumed, or expired.","type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"502":{"description":"Downstream api-bridge failed to submit the vote.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Downstream api-bridge failed to submit the vote.","type":"object","additionalProperties":true}}}}}}},"/v1/oauth/token":{"post":{"summary":"OAuth2 token endpoint","tags":["OAuth"],"description":"OAuth2 token endpoint. Requires NO API-key auth — credentials are supplied in the request body. Supports grant_type \"client_credentials\" (client_id + client_secret → new access_token + refresh_token) and \"refresh_token\" (rotates the pair; replaying a spent refresh token revokes the entire descendant chain as a theft-detection security feature). Returns access_token, token_type, expires_in (3600s), refresh_token, refresh_expires_in (30d) and scope.","security":[],"x-retry-safety":"unsafe","x-retry-note":"Each call issues a new token. Expected for a token endpoint.","responses":{"200":{"description":"Token issued (or rotated) successfully.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Token issued (or rotated) successfully.","type":"object","additionalProperties":true,"properties":{"access_token":{"type":"string"},"token_type":{"type":"string","example":"Bearer"},"expires_in":{"type":"integer","description":"Access-token lifetime in seconds (3600)."},"refresh_token":{"type":"string"},"refresh_expires_in":{"type":"integer","description":"Refresh-token lifetime in seconds (30d)."},"scope":{"type":"string","description":"Space-delimited granted scopes."}}}}}},"400":{"description":"invalid_request — missing/invalid grant_type or required credentials.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"invalid_request — missing/invalid grant_type or required credentials.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}},"401":{"description":"invalid_client / invalid_grant — bad client credentials, or a refresh token that is invalid, revoked, or replayed.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"invalid_client / invalid_grant — bad client credentials, or a refresh token that is invalid, revoked, or replayed.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/oauth/revoke":{"post":{"summary":"Revoke an OAuth2 token","tags":["OAuth"],"description":"Revoke an OAuth2 access or refresh token (RFC 7009). Requires NO API-key auth — the token is supplied in the body. Accepts either an access token or a refresh token; revoking a refresh token kills its entire descendant chain. Per-IP rate limited. Per spec, always returns 200 with an empty body and never leaks whether the token existed.","security":[],"x-retry-safety":"idempotent","x-retry-note":"Revoking a revoked token is a no-op, per RFC 7009.","responses":{"200":{"description":"Revocation processed (empty body; no info leak).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Revocation processed (empty body; no info leak).","type":"object","additionalProperties":true,"properties":{}}}}},"400":{"description":"invalid_request — token field is missing.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"invalid_request — token field is missing.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/oauth-clients":{"post":{"summary":"Create an OAuth2 client","tags":["OAuth"],"description":"Create an OAuth2 client and return its generated client_id and one-time client_secret. Requires an API key with the oauth:write or admin:write scope. Non-admin callers may only create clients within their own org. The client_secret is shown once and cannot be retrieved again.","security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call creates another client with its own secret.","x-required-scopes":["oauth:write","admin:write"],"x-scope-mode":"any","responses":{"201":{"description":"Client created; client_secret returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Client created; client_secret returned once.","type":"object","additionalProperties":true,"properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"org_id":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"warning":{"type":"string"}}}}}},"403":{"description":"Forbidden — attempted to create a client for another org without admin scope.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Forbidden — attempted to create a client for another org without admin scope.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"message":{"type":"string"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"get":{"summary":"List OAuth2 clients for the org","tags":["OAuth"],"description":"List the OAuth2 clients belonging to the authenticated caller's org. Requires oauth:read — oauth:write, admin:read and admin:write also satisfy it, for keys issued before oauth:read existed. Never returns client secrets.","security":[{"apiKey":[]}],"x-required-scopes":["oauth:read","oauth:write","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"OAuth2 clients for the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"OAuth2 clients for the caller's org.","type":"object","additionalProperties":true,"properties":{"clients":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"client_id":{"type":"string"},"org_id":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"is_active":{"type":"boolean"},"created_at":{},"last_rotated_at":{}}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/oauth-clients/{id}":{"delete":{"summary":"Deactivate an OAuth2 client","tags":["OAuth"],"description":"Deactivate an OAuth2 client and cascade-revoke all of its outstanding tokens. Requires an API key with the oauth:write or admin:write scope. Returns 204 No Content on success.","security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Deactivation is a state, not an event.","x-required-scopes":["oauth:write","admin:write"],"x-scope-mode":"any","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"204":{"description":"Client deactivated and its tokens revoked (no body).","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}}},"404":{"description":"OAuth2 client not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"OAuth2 client not found in the caller's org.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"message":{"type":"string"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/oauth-clients/{id}/rotate-secret":{"post":{"summary":"Rotate an OAuth2 client secret","tags":["OAuth"],"description":"Rotate the client_secret for an OAuth2 client and return the new one-time secret. Requires an API key with the oauth:write or admin:write scope. The previous secret stays valid during an optional grace window (grace_seconds, default 300, max 7 days). The new client_secret is shown once and cannot be retrieved again.","security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"A retry invalidates the secret just issued.","x-required-scopes":["oauth:write","admin:write"],"x-scope-mode":"any","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Secret rotated; new client_secret returned once.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Secret rotated; new client_secret returned once.","type":"object","additionalProperties":true,"properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"grace_seconds":{"type":"integer"},"previous_secret_expires_at":{},"warning":{"type":"string"}}}}}},"404":{"description":"OAuth2 client not found in the caller's org.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"OAuth2 client not found in the caller's org.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"message":{"type":"string"}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/me":{"get":{"summary":"Who this credential is, and what it may do","description":"Answers \"who am I\" for EITHER an API key or a signed-in portal session. Returns the organization, the granted scopes, and — depending on which credential was used — the key or the user.\n\nThis accepted only a portal session until 2026-08, so a machine holding an API key had no way to learn its own organization or its own permissions. That is the first thing anyone needs after minting a key, and the only route to it was inferring from unrelated endpoints. Send `X-API-Key` or a Firebase ID token; if both are present the API key wins, because an automated caller should be judged on the credential it meant to send.","security":[{"apiKey":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Who this credential is and what it may do. `user`/`orgs` are populated for a session, `key` for an API key; the rest is common to both so one parser reads either.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Who this credential is and what it may do. `user`/`orgs` are populated for a session, `key` for an API key; the rest is common to both so one parser reads either.","properties":{"auth_method":{"type":"string","enum":["api_key","oauth","session"],"description":"Which credential answered. `key` is populated for the first two, `user` for the last."},"org":{"type":"object","additionalProperties":true,"description":"The organization this credential acts for.","properties":{"id":{"type":"string"},"name":{"type":["null","string"]}}},"scopes":{"type":"array","items":{"type":"string"},"description":"What this credential may do. `*` is the wildcard. See GET /v1/scopes."},"key":{"type":["null","object"],"additionalProperties":true,"description":"The API key, when one was used. Null for a portal session.","properties":{"id":{"type":"string"},"rate_limit_rpm":{"type":["null","integer"]}}},"user":{"type":["null","object"],"additionalProperties":true,"description":"The signed-in human, when a session was used. Null for an API key.","properties":{"id":{"type":"string"},"email":{"type":["null","string"]},"source":{"type":"string"}}},"active_org_id":{"type":["null","string"]},"role":{"type":["null","string"]},"orgs":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"org_id":{"type":"string"},"name":{"type":["null","string"]},"role":{"type":"string"}}}}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/keys":{"get":{"summary":"List your organization API keys","description":"Lists the API keys for the active organization. Only prefixes are returned — raw keys are never retrievable. Requires a Firebase ID token.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"API keys for the active organization. Secrets are never included.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"API keys for the active organization. Secrets are never included.","properties":{"api_keys":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"name":{"type":["null","string"]},"key_prefix":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"rate_limit_rpm":{"type":["null","integer"]},"is_active":{"type":"boolean"},"created_at":{},"last_used_at":{}}}}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"post":{"summary":"Create an API key for your organization","description":"Mints a scoped machine API key (ck_live_…) for the active organization; the raw key is returned ONCE. Scopes are restricted to an allowlist. Requires owner/admin role.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":128},"scopes":{"type":"array","items":{"type":"string","maxLength":64}},"rate_limit_rpm":{"type":"number","minimum":1,"maximum":100000}}}}},"required":true},"security":[{"bearerAuth":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call mints a key whose secret is shown once. A retry leaves an unrecorded live key.","responses":{"201":{"description":"The new key. `api_key.key` is the secret and is shown exactly once — `warning` says so, which is why the key is nested rather than flattened: the caller must read both together.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The new key. `api_key.key` is the secret and is shown exactly once — `warning` says so, which is why the key is nested rather than flattened: the caller must read both together.","properties":{"api_key":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"key":{"type":"string","description":"The secret. Shown once, never retrievable again."},"prefix":{"type":"string"},"name":{"type":["null","string"]},"permissions":{"type":"array","items":{"type":"string"}}}},"warning":{"type":"string"}}}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/keys/{id}/rotate":{"post":{"summary":"Rotate an API key","description":"Revokes the given key and issues a replacement with the same name and scopes; the new raw key is returned once. Requires owner/admin role.","security":[{"bearerAuth":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each rotation invalidates the secret the previous one just issued. A retry can strand a caller holding a key that was valid moments ago.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"201":{"description":"The new key. `api_key.key` is the secret and is shown exactly once — `warning` says so, which is why the key is nested rather than flattened: the caller must read both together.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The new key. `api_key.key` is the secret and is shown exactly once — `warning` says so, which is why the key is nested rather than flattened: the caller must read both together.","properties":{"api_key":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"key":{"type":"string","description":"The secret. Shown once, never retrievable again."},"prefix":{"type":"string"},"name":{"type":["null","string"]},"permissions":{"type":"array","items":{"type":"string"}}}},"warning":{"type":"string"}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/keys/{id}":{"delete":{"summary":"Revoke an API key","description":"Immediately deactivates an API key for the active organization. Requires owner/admin role.","security":[{"bearerAuth":[]}],"x-retry-safety":"idempotent","x-retry-note":"Already-revoked stays revoked.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The key, now inactive.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The key, now inactive.","properties":{"id":{"type":"string"},"is_active":{"type":"boolean"}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/members":{"get":{"summary":"List organization members","description":"Lists members of the active organization. Requires a Firebase ID token.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Two collections: who is in the organization, and who has been asked.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Two collections: who is in the organization, and who has been asked.","properties":{"members":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"user_id":{"type":"string"},"email":{"type":["null","string"]},"role":{"type":"string"}}}},"pending_invites":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string"},"role":{"type":"string"},"expires_at":{}}}}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/invites":{"post":{"summary":"Invite a teammate","description":"Invites a user (by email) to the active organization; they join automatically on their first login. Requires owner/admin role.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","maxLength":320},"role":{"type":"string","enum":["admin","member"]}}}}},"required":true},"security":[{"bearerAuth":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"An outstanding invite for the same address is reused rather than duplicated.","responses":{"201":{"description":"The invitation, at the top level — matching every other singular write on this API.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The invitation, at the top level — matching every other singular write on this API.","properties":{"email":{"type":"string"},"role":{"type":"string"},"expires_at":{}}}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/funding":{"get":{"summary":"Funding overview for the active organization","description":"Account standing, registered payer addresses, open payment intents, and the chains that accept deposits. One call so the funding screen renders without a waterfall. Requires a Firebase ID token.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Everything the funding panel renders, in one call.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Everything the funding panel renders, in one call.","properties":{"standing":{"type":"object","additionalProperties":true,"description":"Balance, credit and status — what the funding panel leads with.","properties":{}},"addresses":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"The registered wallet, at the top level, identically to POST /v1/billing/deposit-addresses.","properties":{"id":{"type":"string"},"chain":{"type":"string"},"address":{"type":"string"},"label":{"type":["null","string"]},"is_active":{"type":"boolean"},"verified_at":{},"created_at":{}},"headers":{"x-ratelimit-limit":{"type":"integer"},"x-ratelimit-remaining":{"type":"integer"},"x-ratelimit-reset":{"type":"integer"}}}},"open_intents":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"The payment intent, at the top level, identically to GET /v1/billing/deposits/{reference}.","properties":{"reference":{"type":"string"},"status":{"type":"string","description":"open | matched | expired | cancelled."},"amount_usd":{"type":"string"},"expires_at":{},"matched_at":{},"payment_id":{"type":["null","string"]}},"headers":{"x-ratelimit-limit":{"type":"integer"},"x-ratelimit-remaining":{"type":"integer"},"x-ratelimit-reset":{"type":"integer"}}}},"chains":{"type":"array","items":{}}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/funding/intents":{"post":{"summary":"Open a one-time payment","description":"Returns the address to send stablecoin to and opens a single-use intent matched on the EXACT amount within its TTL. Use this for a first deposit — it needs no registered wallet. Requires owner/admin role.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["chain","amount_usd"],"properties":{"chain":{"type":"string","maxLength":64},"amount_usd":{"type":"string","pattern":"^\\d+(\\.\\d{1,6})?$"}}}}},"required":true},"security":[{"bearerAuth":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call opens another payment intent. Same caution as POST /v1/billing/deposits.","responses":{"201":{"description":"Where to send, and the single-use intent matched on the EXACT amount.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Where to send, and the single-use intent matched on the EXACT amount.","properties":{"chain":{"type":"string"},"deposit_address":{"type":"string"},"token":{},"deposit_intent":{"type":"object","additionalProperties":true,"properties":{"reference":{"type":"string"},"amount_usd":{"type":"string"},"expires_at":{}}}}}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/funding/intents/{reference}":{"get":{"summary":"Check a one-time payment","description":"Status of a single payment intent, so a waiting screen can poll instead of asking the customer to reload. The intent's fields are returned at the top level, identically to `GET /v1/billing/deposits/{reference}`. Scoped to the active organization. Requires a Firebase ID token.","parameters":[{"schema":{"type":"string","maxLength":64},"in":"path","name":"reference","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The payment intent, at the top level, identically to GET /v1/billing/deposits/{reference}.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The payment intent, at the top level, identically to GET /v1/billing/deposits/{reference}.","properties":{"reference":{"type":"string"},"status":{"type":"string","description":"open | matched | expired | cancelled."},"amount_usd":{"type":"string"},"expires_at":{},"matched_at":{},"payment_id":{"type":["null","string"]}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/funding/addresses":{"post":{"summary":"Register a wallet you pay from","description":"Every future deposit from this address credits this organization automatically. An address may belong to only ONE organization per chain — a duplicate is refused rather than merged, because ambiguous attribution would credit the wrong customer. Requires owner/admin role.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["chain","address"],"properties":{"chain":{"type":"string","maxLength":64},"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$"},"label":{"type":"string","maxLength":120}}}}},"required":true},"security":[{"bearerAuth":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"Same uniqueness rule as the API-key route above.","responses":{"201":{"description":"The registered wallet, at the top level, identically to POST /v1/billing/deposit-addresses.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The registered wallet, at the top level, identically to POST /v1/billing/deposit-addresses.","properties":{"id":{"type":"string"},"chain":{"type":"string"},"address":{"type":"string"},"label":{"type":["null","string"]},"is_active":{"type":"boolean"},"verified_at":{},"created_at":{}}}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"409":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/funding/addresses/{id}":{"delete":{"summary":"Stop crediting deposits from a wallet","description":"Deactivates the address. It is not deleted — past attributions must stay explicable. Requires owner/admin role.","parameters":[{"schema":{"type":"string","maxLength":64},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"x-retry-safety":"idempotent","x-retry-note":"Deregistration is a state.","responses":{"200":{"description":"Deposits from this wallet are no longer credited automatically.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Deposits from this wallet are no longer credited automatically.","properties":{"deactivated":{"type":"boolean"}}}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/device":{"post":{"summary":"Start a CLI device authorization","description":"Allocates a device code and a short user code so a terminal can obtain its own API key without a human copying a secret. PUBLIC — the caller has no credential yet, which is the point. This grants nothing on its own: the request belongs to no organization until a signed-in owner or admin approves it, and the key is minted only when the device collects it with the device code. The device code is returned ONCE and stored only as an HMAC.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"device_name":{"type":"string","maxLength":120,"description":"What to call this device on the approval screen and on the minted key."}}}}}},"security":[],"x-retry-safety":"unsafe","x-retry-note":"Unsafe by design — each call starts a new device authorization with its own code, which is what a fresh login attempt should do.","responses":{"201":{"description":"The device authorization is open. Show `user_code` to the human, send them to `verification_uri`, and poll `GET /v1/portal/device/{device_code}` no faster than `interval` seconds until `expires_in` elapses.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The device authorization is open. Show `user_code` to the human, send them to `verification_uri`, and poll `GET /v1/portal/device/{device_code}` no faster than `interval` seconds until `expires_in` elapses.","properties":{"device_code":{"type":"string","description":"Secret. Poll with this; never show it to the user."},"user_code":{"type":"string","description":"Short code the human reads and types. Show THIS."},"verification_uri":{"type":"string"},"verification_uri_complete":{"type":"string","description":"Prefills the code. Following it does NOT approve — approval is always an explicit click by a signed-in human, because a link that grants access by being followed is the whole device-phishing problem."},"expires_in":{"type":"integer","description":"Seconds until the request dies."},"interval":{"type":"integer","description":"Minimum seconds between polls."}}}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/device/{deviceCode}":{"get":{"summary":"Poll a device authorization, and collect the key once approved","description":"Returns `pending`, `denied`, `expired`, or `approved`. On the FIRST call after approval it mints the API key and returns it — once, in this response. Subsequent calls report `claimed` and carry no key. PUBLIC: possession of the device code is the authorization, which is why it is 256 bits and stored only as an HMAC.","parameters":[{"schema":{"type":"string"},"in":"path","name":"deviceCode","required":true}],"security":[],"responses":{"200":{"description":"Poll result. `status` is one of pending | approved | denied | expired | claimed. The key appears ONLY on the single `approved` response — the code becomes `claimed` immediately after, so a client that does not persist it there cannot recover it.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Poll result. `status` is one of pending | approved | denied | expired | claimed. The key appears ONLY on the single `approved` response — the code becomes `claimed` immediately after, so a client that does not persist it there cannot recover it.","properties":{"status":{"type":"string","enum":["pending","approved","denied","expired","claimed"]},"interval":{"type":"integer","description":"Present while pending: wait at least this long."},"api_key":{"type":"string","description":"Only on `approved`, and only once. Save it immediately."},"key_prefix":{"type":"string"},"key_id":{"type":"string"},"org_id":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"warning":{"type":"string"},"note":{"type":"string"}}}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/portal/device/approve":{"post":{"summary":"Approve a CLI device authorization","description":"Attaches the caller's organization to a pending device request, allowing that device to collect an API key. Requires a Firebase ID token AND owner/admin role — the same bar as minting a key by hand, because that is what this authorizes.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["user_code"],"properties":{"user_code":{"type":"string","maxLength":32}}}}},"required":true},"security":[{"bearerAuth":[]}],"x-retry-safety":"idempotent","x-retry-note":"Approved stays approved.","responses":{"200":{"description":"The human granted it. The waiting client collects the key on its next poll.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The human granted it. The waiting client collects the key on its next poll.","properties":{"approved":{"type":"boolean"},"device_name":{"type":["null","string"]},"expires_at":{}}}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/portal/device/deny":{"post":{"summary":"Deny a CLI device authorization","description":"Refuses a pending device request. Exists so that someone shown a code they did not start has an action to take other than closing the tab — an unrecognised code is the signal that matters most here.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["user_code"],"properties":{"user_code":{"type":"string","maxLength":32}}}}},"required":true},"security":[{"bearerAuth":[]}],"x-retry-safety":"idempotent","x-retry-note":"Denied stays denied.","responses":{"200":{"description":"The human refused it. The waiting client sees `status: denied` and stops.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The human refused it. The waiting client sees `status: denied` and stops.","properties":{"denied":{"type":"boolean"}}}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/orgs":{"get":{"summary":"List organizations (admin)","tags":["Admin"],"description":"Lists organizations with membership/identity/active-key counts, filtered by approval status (pending|approved|all, default pending). Auth: admin API key or an allowlisted Firebase admin session.","parameters":[{"schema":{"type":"string","enum":["pending","approved","all"]},"in":"query","name":"status","required":false}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/orgs/{id}/approve":{"post":{"summary":"Approve an organization (admin)","tags":["Admin"],"description":"Marks an organization approved, lifting the self-service identity/credit caps. Auth: admin API key or allowlisted Firebase admin session.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Approved stays approved.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/orgs/{id}/suspend":{"post":{"summary":"Suspend an organization (admin)","tags":["Admin"],"description":"Reverts an organization to unapproved, re-applying the self-service caps. Auth: admin API key or allowlisted Firebase admin session.","parameters":[{"schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Suspended stays suspended.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/obligations":{"get":{"summary":"What pending intents will cost when they execute","description":"Every non-terminal intent and the price it will charge on execution, so an account can be kept solvent deliberately rather than by luck. This matters for multi-signature intents, which may wait hours or weeks for quorum: CERTEN does the expensive work — and charges — only once quorum is reached, by which time the originator is long gone. `uncovered_usd` is the portion NOT already reserved by a hold, i.e. the amount still needing balance behind it; `remaining_usd` is what is left to commit after those. Measured against spendable (available + credit limit), so an invoiced account reads correctly. Requires the billing:read scope.","security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/balance":{"get":{"summary":"Get the organization balance","description":"Current prepaid balance, funds held against in-flight work, and any granted postpay credit limit. `spendable_usd` is what may actually be spent right now (available + credit limit). **Gate on `remaining_usd`, not on `spendable_usd`.** `remaining_usd` subtracts what pending intents will consume when they execute; a multi-signature intent can wait weeks for quorum, so an account can show a healthy spendable balance that is entirely committed and still be refused on its next call. Both numbers are returned here so that answering \"can I afford this\" costs one request. `GET /v1/billing/obligations` itemises WHICH intents claimed it. Requires the billing:read scope.","security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"currency":{"type":"string"},"available_usd":{"type":"string"},"held_usd":{"type":"string"},"credit_limit_usd":{"type":"string"},"spendable_usd":{"type":"string"},"remaining_usd":{"type":"string"},"pending_intents":{"type":"integer"},"uncovered_usd":{"type":"string"},"status":{"type":"string","enum":["active","suspended","closed"]},"suspended_reason":{"type":["null","string"]},"enforcing":{"type":"boolean"},"credit":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["none","trial","comp","terms"]},"label":{"type":["null","string"]},"granted_limit_usd":{"type":"string"},"expires_at":{"type":["null","string"]},"expired":{"type":"boolean"},"warns_at_usd":{"type":"string"},"suspends_at_usd":{"type":"string"}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Billing not enabled.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Billing not enabled."}}}}}}},"/v1/billing/ledger":{"get":{"summary":"List ledger entries","description":"Append-only, double-entry ledger for this organization, newest first. Every balance change has an entry here; corrections appear as new reversing groups rather than edits. Requires the billing:read scope.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"entries":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"account":{"type":"string"},"amount_usd":{"type":"string"},"kind":{"type":"string"},"ref_type":{"type":"string","nullable":true},"ref_id":{"type":"string","nullable":true},"memo":{"type":"string","nullable":true},"created_at":{"type":"string"}}}},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/quote":{"post":{"summary":"Price a proof-gated execution before submitting it","description":"Returns a single-use, expiring quote with a full breakdown and a `max_total_usd` cap that will not be exceeded even if gas moves during execution. Every input is published and the total is recomputable: the platform fee comes from a hash-identified price book, the FX rate from a signed observation, and the gas estimate from costs actually measured on that chain. Pass the returned `quote_id` to POST /v1/transaction to execute at this price. A quote must cover the WHOLE request: name every destination chain in `additional_chains` and the execution class in `proof_class`, or submission is refused as a mismatch. Requires billing:read.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["chain"],"properties":{"chain":{"type":"string","maxLength":64},"additional_chains":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":64},"description":"Further destination chains this request will touch. Each is gated and gas-estimated in its own right, so the quote is the sum of the real work rather than one chain priced N times."},"proof_class":{"type":"string","enum":["on_demand","on_cadence"],"description":"Execution class to price for. on_cadence shares one anchor across a batch; on_demand pays for its own. A quote issued for one class cannot be spent on the other."},"sku":{"type":"string","maxLength":64},"leg_count":{"type":"integer","minimum":1,"maximum":20}}}}},"required":true},"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call issues a new quote. Harmless — quotes are free, single-use and expire — but a retry does not return the first one.","x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"Chain not priceable, or no price book / FX rate.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Chain not priceable, or no price book / FX rate."}}}}}}},"/v1/pricing":{"get":{"summary":"List every priced operation and what it costs","description":"The full price book in effect for the calling organization: every sku, on every chain, with its platform fee and pricing mode. `flat` is an all-in price; `quoted` prices gas dynamically at execution and the fee shown is the platform component only. Carries the same price_book_version and price_book_hash as quotes and receipts, so a price can be traced from discovery through to the charge. Use it to discover sku names for POST /v1/quote. Requires billing:read.","security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"The price book in effect.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The price book in effect.","type":"object","additionalProperties":true,"properties":{"price_book_version":{"type":"string"},"price_book_hash":{"type":"string"},"currency":{"type":"string"},"items":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string"},"chain":{"type":"string","description":"\"*\" applies to any chain without its own entry."},"mode":{"type":"string","description":"flat | quoted"},"platform_fee_usd":{"type":"string"},"gas_buffer_bps":{"type":"number"},"min_charge_usd":{"type":"string"},"max_charge_usd":{"type":["null","string"]}}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"description":"No price book is in effect.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"No price book is in effect.","type":"object","additionalProperties":true}}}}}}},"/v1/quote/{id}":{"get":{"summary":"Get a quote","description":"Retrieve a previously issued quote and its current status. Requires billing:read.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/payments":{"get":{"summary":"List payments","description":"Payments received for this organization across every rail (stablecoin deposits, card, wire). A payment moves pending -> confirmed -> credited; only `credited` payments have moved the balance. Requires the billing:read scope.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"payments":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/deposits":{"post":{"summary":"Get stablecoin deposit instructions","description":"Returns the CERTEN treasury address to send stablecoin to on the requested chain. Deposits are attributed to your organization in one of two ways: (a) you send from an address registered via POST /v1/billing/deposit-addresses — recommended, works for any amount and every future deposit; or (b) you supply `amount_usd` here, which opens a single-use deposit intent matched on the EXACT amount within its TTL. A deposit that matches neither is held as unattributed until an operator resolves it, so register an address if you can. Requires the billing:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["chain"],"properties":{"chain":{"type":"string","maxLength":64},"amount_usd":{"type":"string","pattern":"^\\d+(\\.\\d{1,6})?$","description":"Exact amount you will send, for one-off attribution without a registered address."}}}}},"required":true},"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call opens a new payment intent, and two open intents for the same money is worse for the payer than one. The 402 resolution path reuses an existing intent; direct calls do not.","x-required-scopes":["billing:fund","billing:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unsupported chain.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Unsupported chain."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/deposits/{reference}":{"get":{"summary":"Check a one-time payment","description":"Status of a single payment intent you opened, so a client can wait for the credit rather than polling its balance and guessing. Scoped to your organization. Requires the billing:read scope.","parameters":[{"schema":{"type":"string","maxLength":64},"in":"path","name":"reference","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"The deposit intent. Poll until `status` leaves `open`.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"The deposit intent. Poll until `status` leaves `open`.","type":"object","additionalProperties":true,"properties":{"reference":{"type":"string"},"status":{"type":"string","description":"open | matched | expired | cancelled."},"amount_usd":{"type":"string"},"expires_at":{},"matched_at":{},"payment_id":{"type":["null","string"]}}}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/deposit-addresses":{"post":{"summary":"Register a sending address for deposit attribution","description":"Registers a wallet address you will send stablecoin from, so any future deposit from it is automatically credited to this organization. An address may belong to only ONE organization per chain — a duplicate registration is rejected rather than merged, because ambiguous attribution would mean crediting the wrong customer. Requires the billing:write scope.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["chain","address"],"properties":{"chain":{"type":"string","maxLength":64},"address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$"},"label":{"type":"string","maxLength":128}}}}},"required":true},"security":[{"apiKey":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"An address belongs to one organization per chain; a duplicate is 409, never merged.","x-required-scopes":["billing:write"],"x-scope-mode":"any","responses":{"201":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"409":{"description":"Address already registered.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Address already registered."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"get":{"summary":"List registered deposit addresses","description":"Requires the billing:read scope.","security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/receipts":{"get":{"summary":"List receipts","description":"Signed receipts for every payment, charge, refund, and adjustment on this organization, newest first. Requires the billing:read scope.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"receipts":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"receipt_number":{"type":"string","description":"Monotonic per org. A STRING: it can exceed 2^53."},"type":{"type":"string","description":"charge | payment | refund | adjustment."},"amount_usd":{"type":"string"},"currency":{"type":"string"},"ref_type":{"type":["null","string"]},"ref_id":{"type":["null","string"]},"digest":{"type":"string"},"signed":{"type":"boolean","description":"Whether an ed25519 signature exists."},"logged":{"type":"boolean","description":"Whether it is in the transparency log. An inclusion proof exists only for these — asking for one otherwise returns 404."},"issued_at":{}}}},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/receipts/{id}":{"get":{"summary":"Get a receipt with its signature, log proof, and computation","description":"The full receipt. Three independent layers of evidence: the ed25519 SIGNATURE (CERTEN issued this), the transparency-log INCLUSION proof against an Accumulate-anchored tree head (CERTEN did not hide or edit it), and the COMPUTATION inputs (the amount is a consequence of measured on-chain gas, a hashed price book, and a signed FX rate — not an assertion). `verification` is the gateway checking its own work; every check in it is reproducible by you from published data, and `independently_verifiable` says how. Requires the billing:read scope.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/receipts/{id}/proof":{"get":{"summary":"Get the transparency-log inclusion proof for a receipt","description":"The RFC 6962 audit path proving this receipt is a leaf of the log at the given tree size, plus the leaf salt (yours alone — leaves are salted so a published root discloses nothing, and the salt is what lets YOU verify while nobody else can enumerate). Defaults to the newest ANCHORED head: a proof against an unanchored head is only as good as our word. Keep this proof with the receipt — it stays valid forever against that head, which is itself pinned on Accumulate. Requires the billing:read scope.","parameters":[{"schema":{"type":"integer","minimum":1},"in":"query","name":"tree_size","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-required-scopes":["billing:read"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transparency":{"get":{"summary":"Describe the receipt transparency log","description":"How to audit CERTEN billing without trusting CERTEN. Returns the log parameters, the current and latest anchored tree heads, and the verification procedure for each artifact. No authentication.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transparency/heads":{"get":{"summary":"List signed tree heads","description":"Signed tree heads, newest first. Each is a commitment to the entire log at that size. Verify a head by checking its ed25519 signature over sha256(canonical_json(head body)), and check any two heads are consistent with GET /v1/transparency/consistency. No authentication.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"heads":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transparency/heads/{treeSize}":{"get":{"summary":"Get the signed tree head at a size","description":"The head committed at a specific tree size, including the exact canonical body the signature covers so a verifier does not have to reconstruct it. No authentication.","parameters":[{"schema":{"type":"string","pattern":"^[0-9]+$"},"in":"path","name":"treeSize","required":true}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transparency/consistency":{"get":{"summary":"Consistency proof between two tree sizes","description":"Proves the log at size `second` still contains everything it did at size `first`, unedited — the append-only property. This is the check a signature cannot express: it is what detects a deleted, altered, or back-dated receipt. Verify with the RFC 6962 algorithm against the two roots. No authentication.","parameters":[{"schema":{"type":"integer","minimum":1},"in":"query","name":"first","required":true},{"schema":{"type":"integer","minimum":1},"in":"query","name":"second","required":true}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transparency/price-books":{"get":{"summary":"List published price books","description":"Every published price book with its content hash. The hash referenced by a receipt identifies exactly which prices the charge was computed under, and published books are immutable — a price you were charged under cannot be edited after you dispute it. No authentication.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/transparency/fx/{id}":{"get":{"summary":"Get a signed FX observation","description":"The exact native-token-to-USD rate used by a charge, with its source, observation time, and signature. This is the one input to a charge that cannot be verified against a blockchain — so it is committed to in advance and logged, letting you check that we fixed the rate BEFORE the charge and compare it against public market data at that instant. No authentication.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/billing/receipts/verification-key":{"get":{"summary":"Get the receipt verification key set","description":"Every ed25519 key the gateway has ever signed receipts with, each with its validity window and status. Verify a receipt against the key whose window contains the receipt's issued_at — that is what makes rotation safe: an old receipt stays verifiable after the key that signed it is retired. Key registration and revocation are themselves entries in the transparency log, so the key history is anchored too. No authentication: a key you need permission to fetch cannot settle a dispute with the party granting permission.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/entitlement/current":{"get":{"summary":"Current signed entitlement epoch","description":"The signed set of accounts entitled to CERTEN execution, with per-account ceilings. Consumed by validators to admit or refuse intents. Self-authenticating: verify the ed25519 signature over the header against a pinned key, then check the set against header.set_hash. Treat this transport as untrusted.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/entitlement/health":{"get":{"summary":"Entitlement publisher health","description":"Whether epochs are being published, and how fresh the current one is.","security":[],"responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/chain-coverage":{"get":{"summary":"Which chains have enough measured cost data to be priced","tags":["Admin"],"description":"Per-chain measured-cost coverage and the resulting verdict: `flat` (sticker price is safe), `quoted` (p95 cost is too high for a flat price — Ethereum L1 lives here), or `unavailable` (not enough data; the chain must not go on the price list). This is the enforcement of the \"no chain ships without 30 days of measurement\" rule — the eight non-EVM chains start here as `unavailable` and stay there until the validator reports real per-leg costs. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/margin":{"get":{"summary":"Net revenue and drift by chain","tags":["Admin"],"description":"Per-chain NET revenue (amount charged minus measured on-chain cost), gas recovered, and average quote drift. Net is the correct KPI: gross margin on Ethereum L1 reads ~0.6% because gas is 99% of the amount charged, which says nothing about whether the business works. A chain whose net revenue is negative is being sold below cost. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":365,"default":7},"in":"query","name":"days","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/enforcement-readiness":{"get":{"summary":"Is it safe to turn BILLING_ENFORCE on","tags":["Admin"],"description":"While BILLING_ENFORCE=false every charge is computed and recorded as a shadow charge, and nobody is blocked. `would_have_failed` counts the shadow charges that become live 402s the instant the flag flips, and `affected_orgs` names how many customers that hits. Drive the decision from those numbers, not from elapsed calendar time. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":365,"default":14},"in":"query","name":"days","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/price-books":{"post":{"summary":"Publish a price book","tags":["Admin"],"description":"Publishes an immutable, content-hashed price book and appends it to the transparency log. This is how fees are set — deliberately NOT environment variables, because every receipt cites the price-book hash it was computed under, and a customer must be able to fetch the exact prices that produced their charge. Republishing the same version with different content is rejected; publish a new version instead. The previous open-ended book of the same kind is closed automatically so exactly one is in force at any instant.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["version","items"],"properties":{"version":{"type":"string","maxLength":64,"description":"e.g. \"2026-07-25.1\""},"kind":{"type":"string","enum":["public","partner","enterprise"]},"notes":{"type":"string","maxLength":1000},"effective_from":{"type":"string"},"items":{"type":"array","minItems":1,"items":{"type":"object","required":["sku","platform_fee_usd"],"properties":{"sku":{"type":"string","maxLength":64},"chain":{"type":"string","maxLength":64,"description":"Defaults to \"*\" (any chain)"},"mode":{"type":"string","enum":["flat","quoted"]},"platform_fee_usd":{"type":"string","pattern":"^\\d+(\\.\\d{1,6})?$"},"gas_buffer_bps":{"type":"integer","minimum":0,"maximum":10000},"min_charge_usd":{"type":"string","pattern":"^\\d+(\\.\\d{1,6})?$"},"max_charge_usd":{"type":"string","pattern":"^\\d+(\\.\\d{1,6})?$","nullable":true}}}}}}}},"required":true},"security":[{"apiKey":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"Price books are immutable per version. Re-publishing identical content returns the existing row; different content under the same version is a 409.","responses":{"201":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"409":{"description":"Version exists with different content — published books are immutable.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Version exists with different content — published books are immutable."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/signing-keys":{"post":{"summary":"Register a receipt signing key","tags":["Admin"],"description":"Registers the ed25519 key that signs receipts, FX observations, and transparency-log heads, and appends the registration to the transparency log so the key history is itself anchored. The new key becomes active and the incumbent is retired with its validity window left open in the past, so receipts it already signed stay verifiable — that is what makes rotation safe rather than merely relocating trust.\n\nFor KMS/Vault-backed keys the private half never reaches this process: supply the public key and the provider config, and signing is delegated. `env-seed` is development-grade — it holds key material in process memory from BILLING_RECEIPT_SIGNING_SEED — and registering one in production is an explicit, logged choice rather than something that happens silently.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["provider_type","public_key"],"properties":{"provider_type":{"type":"string","enum":["local","aws-kms","azure-keyvault","gcp-kms","hashicorp-vault","callback","env-seed"]},"public_key":{"type":"string","pattern":"^[0-9a-fA-F]{64}$"},"provider_config":{"type":"object","additionalProperties":true},"not_before":{"type":"string"},"not_after":{"type":"string"}}}}},"required":true},"security":[{"apiKey":[]}],"x-retry-safety":"unsafe","x-retry-note":"Each call registers another signing key.","responses":{"201":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/signing-keys/{keyId}/revoke":{"post":{"summary":"Revoke a receipt signing key","tags":["Admin"],"description":"Marks a key revoked (or COMPROMISED) and logs the event. The distinction matters: signatures from a retired key remain trustworthy inside its validity window, while a compromised key means every signature under it is suspect from the compromise onward — bounded by whichever tree heads were anchored before it.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","maxLength":500},"compromised":{"type":"boolean"}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"keyId","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Revoked stays revoked.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/price-books/{id}/assign":{"post":{"summary":"Assign a price book to an organization","tags":["Admin"],"description":"Points one organization at a specific price book — used for partner wholesale rates and negotiated enterprise pricing. Orgs with no assignment use the current public book.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["org_id"],"properties":{"org_id":{"type":"string"}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Assignment is a state, not an event.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/payments/unattributed":{"get":{"summary":"Deposits that arrived but could not be attributed","tags":["Admin"],"description":"Final on-chain deposits with no resolvable organization. The watcher attributes a deposit only from a registered sending address or an exact-amount deposit intent; anything else lands here rather than being guessed at, because crediting the wrong customer is worse than crediting late. Each row carries the sending address and tx hash so the payer can be identified out of band. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/payments/{id}/attribute":{"post":{"summary":"Attribute an unattributed deposit and credit it","tags":["Admin"],"description":"Assigns a deposit to an organization and credits it through the normal ledger path — double entry, signed receipt, transparency log. Verify the sending address belongs to that customer BEFORE calling this: it moves real money and the resulting receipt is permanent and anchored. Set `register_address` to also record the sender so that customer's future deposits attribute automatically and never reach this queue again.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["org_id"],"properties":{"org_id":{"type":"string"},"register_address":{"type":"boolean"}}}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"x-retry-safety":"guarded-by-state","x-retry-note":"Refuses with 409 once the payment is credited, so it cannot double-credit.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"409":{"description":"Already credited.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Already credited."}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/balance-drift":{"get":{"summary":"On-chain balances vs the ledger","tags":["Admin"],"description":"The anti-smoke check. Everything else proves internal consistency — the ledger balances, the log is append-only, receipts are anchored — and none of it detects books that simply do not correspond to the money. This compares the treasury stablecoin delta against deposits credited, and each relayer EOA native delta against gas recorded in cost_events. A `drift` verdict means the books and the chain disagree and one of them is wrong. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/run-balance-check":{"post":{"summary":"Snapshot on-chain balances and reconcile now","tags":["Admin"],"description":"Takes a fresh balance snapshot of every treasury and relayer address and reconciles it against the previous one. The first run per address only records a baseline — reconciliation needs two snapshots to compute a delta.","security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Takes a fresh snapshot each time.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/reconcile":{"post":{"summary":"Run a reconciliation pass now","tags":["Admin"],"description":"Runs the full reconciliation pass on demand rather than waiting for the hourly job: charge drift vs. measured cost, ledger drift, unlogged receipts, and transparency-log head integrity.","security":[{"apiKey":[]}],"x-retry-safety":"idempotent","x-retry-note":"Recomputes from current data; running twice reaches the same conclusion.","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/summary":{"get":{"summary":"Everything the fee console landing page needs, in one call","tags":["Admin"],"description":"Window totals, per-chain P&L, the alert set, and the revenue/cost trend. Read this response with one rule in mind: `totals.net_revenue_usd` counts ONLY charges whose on-chain cost was actually measured. `totals.unmeasured_charged_usd` is money billed with NO cost data behind it — its margin is unknown, not positive, and it is never added into net. A null `net_revenue_usd` means nothing in the window was measured at all; rendering that as zero is how a platform selling at 20% of cost reported healthy revenue. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":365,"default":7},"in":"query","name":"days","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/chains":{"get":{"summary":"Per-chain P&L merged with the priceability verdict","tags":["Admin"],"description":"Charges, measured/unmeasured counts, amount charged, measured cost, net revenue, margin in bps, average quote drift, and whether the chain may be sold at all. Sorted worst-first with UNMEASURED chains ahead of loss-making ones: a chain with no cost data is the larger problem because its true net could be anything. `state` is one of profitable | break_even | below_cost | unmeasured | no_charges, and `net_revenue_usd` is null — never 0 — whenever nothing behind the charges was measured. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":365,"default":7},"in":"query","name":"days","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/intents":{"get":{"summary":"Per-intent itemisation: what was charged and what each leg cost","tags":["Admin"],"description":"The itemised view — it answers \"why did this intent cost $2.49\". Each row carries the amount charged split into platform fee and gas, the measured cost broken down per leg (anchor, verify, vault_execute) with gas used, effective gas price and the tx hash for each, the net, the completion basis, and the receipt id.\n\nCost is joined to intents through the normalized Accumulate tx hash, NEVER through `cost_events.intent_id` (the validator's own id, which never equals a gateway intent id). That exact mistake silently zeroed the margin report.\n\n`measured_cost_usd` and `net_usd` are null on an intent with no cost events. Filter with `only=unmeasured` to work that queue down, or `only=below_cost` for intents whose cost IS known and exceeds what was charged. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":365,"default":7},"in":"query","name":"days","required":false},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false},{"schema":{"type":"string","maxLength":64},"in":"query","name":"chain","required":false},{"schema":{"type":"string","format":"uuid"},"in":"query","name":"org_id","required":false},{"schema":{"type":"string","enum":["below_cost","unmeasured","all"],"default":"all"},"in":"query","name":"only","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"intents":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true,"description":"Where this page sits. Loop until `has_more` is false — do NOT infer the end from a short page, which ends early whenever a page lands exactly on the page size.","properties":{"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Where this page started."},"has_more":{"type":"boolean","description":"True when at least one further row exists. The only field a pager needs."},"returned":{"type":"integer","description":"Rows in this page."}}}}}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/accounts":{"get":{"summary":"Who actually pays, and who is being carried","tags":["Admin"],"description":"Per organization: available, held, credit limit, spendable, arrears, lifetime charged, lifetime PAID (credited payments — real money), lifetime GRANTED (promotional balance, deliberately kept out of \"paid\"), shadow charged, and last payment.\n\nSegments: `paying` (real money received), `on_credit` (negative balance inside a granted limit), `in_arrears` (negative with no headroom left — refused the moment enforcement is on), `comped` (consuming, or holding granted balance, having never paid), `dormant` (no activity at all). A comped account is NOT a paying customer however positive its balance looks, which is the distinction this screen exists to make.\n\n`shadow_charged_usd` matters while BILLING_ENFORCE=false: nothing is ever captured, so lifetime_charged is zero for everyone and every org would otherwise read as dormant. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/timeseries":{"get":{"summary":"Revenue, cost and net over time","tags":["Admin"],"description":"Bucketed by hour, day or week. `charged_usd` is filtered to MEASURED charges so it is comparable with `measured_cost_usd`; `charged_all_usd` is the unfiltered total. Plotting the unfiltered total against measured cost draws a rising margin that is really rising ignorance — watch `unmeasured_charges` on the same axis. `net_revenue_usd` is null in any bucket where nothing was measured. Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":365,"default":30},"in":"query","name":"days","required":false},{"schema":{"type":"string","enum":["hour","day","week"],"default":"day"},"in":"query","name":"bucket","required":false},{"schema":{"type":"boolean","default":false},"in":"query","name":"by_chain","required":false}],"security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/v1/admin/billing/health":{"get":{"summary":"Whether the numbers on every other screen can be trusted","tags":["Admin"],"description":"Treasury and relayer balances vs the ledger, materialized-balance drift, anchor lag and the unanchored head backlog, receipts missing from the transparency log, FX feed freshness per symbol, and chain priceability coverage.\n\nThree states are distinguished on purpose, because two would let missing data read as healthy: balance drift is `match` | `drift` | `never_run` (nothing has ever compared the books to the chain); anchoring is `anchored` | `never_anchored` (a null lag, not a zero one); each FX symbol is `fresh` | `stale` | `never_observed` (a chain the feed has never managed to price at all, which cannot be quoted). Requires any of `billing:admin_read` or `admin:read` or `admin:write`.","security":[{"bearerAuth":[]}],"x-required-scopes":["billing:admin_read","admin:read","admin:write"],"x-scope-mode":"any","responses":{"200":{"description":"Default Response","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","headers":{"x-ratelimit-limit":{"schema":{"type":"integer"}},"x-ratelimit-remaining":{"schema":{"type":"integer"}},"x-ratelimit-reset":{"schema":{"type":"integer"}},"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"description":"Rate limit exceeded. Every response — not only this one — carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, so this is avoidable by watching `x-ratelimit-remaining` reach 0. **`x-ratelimit-reset` is SECONDS REMAINING, not a Unix timestamp** — reading it as an epoch yields an instant in 1970 and silently disables any throttle built on it. On this response, wait `retry-after` seconds and prefer that over any local backoff curve.","type":"object","additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}}},"servers":[{"url":"https://gateway.kompendium.co","description":"Production"}],"tags":[{"name":"Identity","description":"Identity management"},{"name":"Transaction","description":"Transaction lifecycle"},{"name":"Governance","description":"Governance operations"},{"name":"Pending","description":"Pending actions inbox"},{"name":"Sign","description":"Universal signing"},{"name":"Proof","description":"Proof retrieval"},{"name":"Portfolio","description":"Multi-chain balances"},{"name":"Admin","description":"Organization and API key management"},{"name":"OAuth","description":"OAuth2 client credentials flow"},{"name":"Health","description":"Service health"}]}