Admin API endpoint catalogue (complete route reference)
This is the authoritative list of every route the engine serves under /admin. It is written for a developer integrating against the engine directly, or auditing exactly what the in-account console can reach. Every entry is taken from the route handlers in the engine’s admin router, not from a summary comment, so a row matches what the code actually dispatches. It is complete for a production engine: a gate here holds the rows to the generated route set and fails on a served route with no row, and the single family that is excluded is named and explained under what this catalogue leaves out.
The /admin surface is the only API the console calls, and the engine runs inside your own Cloudflare account. There is no inbound path from the vendor: this surface is reached only by your own console within your account. Each row names the method, the path, the capability a caller must hold, and the behaviour worth knowing before you call it.
For a generated route-level view, the method, path, summary and tag of every route, with a downloadable machine-readable spec, see the API explorer. This page is the human-grouped catalogue; the explorer is the route-level view generated from the same code at build time. The generated spec does not assert request or response shapes, and being a build-time snapshot it does not detect drift from a deployed engine. For how a request authenticates and how a role resolves to a capability, see authentication and authorisation.
How to read this catalogue
Authorisation is decided by capability, not by a role rank. A route gates on a single capability, and a caller holds a capability through their resolved role. The capability floor column names the capability the route requires; the roles and capabilities page maps each built-in role to its capability set. Where a column says “owner”, the route checks the role directly rather than a capability, which the notes call out.
A few behaviours recur across the table, so they are stated once here rather than repeated on every row.
The reads are open to any authenticated role that holds the relevant read capability, and they carry no body to mis-parse, so they have no per-caller rate-limit pre-check. Every mutating route is a POST, and each one is rate-limited per caller after its capability gate, failing open so an unavailable limiter never blocks a verified operator’s recovery action. The four break-glass routes gated directly on the bare ADMIN_TOKEN are the exception: POST /admin/demo/reset, POST /admin/control-plane/restore, POST /admin/control-plane/restore-sealed and POST /admin/control-plane/apply-staged check the bearer token directly rather than going through a capability gate, and none of the four is rate-limited. A 403 from a capability gate is JSON ({ "error": "forbidden", "required": <capability>, "have": <role> }), distinct from the plaintext 401 a sign-in failure returns, so the console can tell a missing capability from a missing session on the status code alone.
A defined set of high-consequence writes additionally requires a fresh step-up re-authentication, so a stale ambient session cannot perform one on its own. Which routes are in that set is decided in the engine rather than listed here, because a route list on this page is a value the clock moves. A caller on the bare break-glass token or behind Cloudflare Access is exempt, since each manages its own re-authentication; a cookie-session caller that has not re-authenticated recently gets a 401 carrying stepUpRequired: true, which the console catches to run the ceremony and retry with a single-use step-up token. The check fails closed, so an unavailable check refuses the action rather than waving it through.
Capability floor, not a ladder
Two of the built-in roles, restore-operator and access-admin, are deliberately not on a cumulative ladder: each holds a precise subset of powers. So “the capability floor” is the exact capability the route checks, and any role whose set contains it may call the route. Reading the floor as “this rank and above” is wrong for those two roles.
Routing that runs before the switch
Some requests are matched before the main route switch. Most are matched early because a dynamic path segment cannot be a literal switch case; the cost estimate is grouped here as an early fixed-path read. They are listed so the catalogue is genuinely complete.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/health | Liveness probe. The only route served before the authentication gate. Returns { ok, service } with no account facts. |
none (unauthenticated) |
| (any) | /admin/auth/* , /admin/oidc/* , /admin/saml/* | The engine’s own sign-in flows (passkey WebAuthn, native OIDC/OAuth2, native SAML SP), handled before the authorisation gate because obtaining a session cannot require a prior session. | none (the flow is the authentication) |
| GET | /admin/reports/<kind> | A signed report by kind, optionally ?format=pdf. Matched early because the kind is a dynamic path segment. |
reports.read |
| GET | /admin/cost/estate-size | The analytics-first onboarding size estimate. It sizes each configured downpipe source from Cloudflare storage analytics so the cost screen shows a real figure before the first backup runs. Read-only and best-effort, returning an honest unavailable estate rather than guessing. | downpipe.read |
| POST | /admin/config/changes/<id>/approve , /admin/config/changes/<id>/reject | Approve or reject a pending config change. The router gate is coarse (downpipe.read); the Durable Object re-resolves the specific write capability the change needs and the maker-is-not-checker rule. | downpipe.read (router), then the change’s own write capability (in the Durable Object) |
| POST | /admin/owner-actions/<id>/approve , /admin/owner-actions/<id>/reject | Approve or reject a pending owner action. The router gates on the owner-exclusive capability; the Durable Object enforces owner, maker-is-not-checker and single-use. | keys.ceremony (router), then owner re-resolved (in the Durable Object) |
The SAML assertion-consumer POST is an exception to the origin-checked write guard. It is an identity-provider-driven post that carries no same-site cookie, so it is not subject to the cookie-session origin check; its replay defence is a single-use RelayState bound to the in-response-to value, enforced in the Durable Object.
Downpipes and runs
The configured backups, running one now, and the run history. The restore screen’s calendar consumes point-in-time resolution directly, and the recovery-time estimate is read by the Recovery time (RTO) panel on the SLA report card. The console keeps coverage and recovery figures honest from the engine’s own answers.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/downpipes | List configured downpipes with their state. | any authenticated role |
| POST | /admin/downpipes | Add or update a downpipe. Audited as a create. Changing the restore-test cadence carries a second, narrower capability re-checked in the Durable Object. | downpipe.write |
| POST | /admin/downpipes/bulk | Create or update many downpipes in one call ({ downpipes: [...] }). Each item runs the same gated single-upsert path (validation, change control, audit), continue-on-error with index-aligned per-item outcomes (applied, pending with a change id, or error with the reason). Capped per request (100 items; 10 while config approval is on, since each gated item dry-runs against a full keyspace checkpoint); an oversized batch is refused whole with the cap echoed as maxBatch so a client re-batches deterministically. |
downpipe.write |
| POST | /admin/downpipes/delete | Stop scheduling a downpipe and drop its history ring. Does not delete archives, so it is reversible by recreating. | downpipe.delete |
| POST | /admin/trigger | Run a downpipe now. Allocates a run in the Durable Object, then seals it in the invocation’s background so a run-now genuinely writes bytes. | run.trigger |
| GET | /admin/history | The recent-run ring for one downpipe (?id=), or every ring when the id is absent. Each row also carries prevRunId (its predecessor run, or null for the downpipe’s first run) and prevRunIdStatus (none, retained, pruned or unknown), so a caller can reconstruct the predecessor chain from this one read. |
any authenticated role |
| GET | /admin/runs/at | Point-in-time resolution: ?downpipe=&at=<rfc3339> returns the latest successful run completed at or before T, or an honest miss with the retained-window bounds. The restore screen’s calendar reads this route directly for its own floor. |
downpipe.read |
| GET | /admin/rto | The recovery-time estimate per downpipe and across the fleet, derived from observed restore-test throughput, with a “based on N drills” caveat and an honest unknown when there is no drill history. | reports.read |
| GET | /admin/replication | Per-destination replication state (?id= or all): the honest source for “N of M copies” and which destination is down. |
any authenticated role |
| GET | /admin/downpipes/roster-hygiene | The roster’s structural integrity: ghost rows whose storage key no longer matches their config id, which the delete route cannot reach, plus entries that have never run. Redaction-safe, carrying your own downpipe ids and storage keys only. The console’s map drawer uses it to explain an undeletable “Unknown” edge. | any authenticated role |
| POST | /admin/downpipes/reconcile-roster | Heal those structural ghosts by restoring the “storage key equals config id” invariant every consumer already assumes. A repair rather than a configuration change, so it is owner-grade rather than dual-control gated, and it never touches a never-ran downpipe, which is a valid config the normal gated delete removes. | keys.ceremony |
GET /admin/runs/at resolves a recovery-timeline pick into a run id; that id then flows through the normal restore path. The restore screen’s calendar is built directly on this route, both for grouping a downpipe’s ring by day and for reading its own honest floor. The portal restore still always picks a run by id, never an arbitrary timestamp, and shows recovery freshness as the newest good run. The reasoning behind that choice lives on recovery objectives.
Canary and the integrity probe
The integrity canary flies a tiny seal, read and restore on a schedule to prove the whole path still works end to end. Reading its state is open to any authenticated role; flying it now is a run action; changing whether, where and how often it flies is a governance decision the owner makes directly.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/canary | The canary view: liveness, the last flight’s aspects and the history ring. A read of state, never a write and never a secret. | any authenticated role |
| POST | /admin/canary/config | Set whether the canary flies, where it flies and how often. The Durable Object records the actor and validates a repoint against the real destination set. The router checks the owner role directly rather than a capability. | owner |
| POST | /admin/canary/run | Fly the canary now rather than waiting for the next cron tick. The Durable Object arms the bird due and reclaims a stale lease; a disabled bird is refused. | run.trigger |
Restore and recovery
The most consequential family. A dry-run plans and writes nothing; an apply writes archived data back and needs both the apply capability and a separate dual-control approval. The verify and attest routes prove an archive is recoverable without writing or surfacing any plaintext. The restore family has its own deep-dive at the restore and recovery API guide; the rows here are the catalogue summary.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| POST | /admin/restore | Dry-run plan when confirm is omitted or false. With confirm: true it is an apply that writes data back, and additionally requires a dual-control approval bound to the server-recomputed plan hash, maker not equal to checker. One path carries both, so the dangerous direction is gated on the parsed confirm rather than on the route. |
restore.dryrun (dry-run); restore.apply plus a dual-control approval (apply) |
| POST | /admin/restore/request | Raise a restore request bound to the plan hash, with a mandatory reason. The blast-radius cues are recomputed server-side, never trusted from the body. | restore.request |
| POST | /admin/restore/approve | A different authorised identity approves a pending request for a plan hash. A self-approval is refused. | restore.approve |
| POST | /admin/restore/reject | Reject a pending request for a plan hash. | restore.approve |
| GET | /admin/restore/approvals | The pending-approval inbox. An approver sees every request; a requester sees their own. | any authenticated role (the Durable Object filters by the caller and the approver flag) |
| POST | /admin/restore/verify | The blind restore test: decrypt every in-scope record to a discard sink, verify each plaintext hash, return counts and a restore digest, never any plaintext. | restore.verify |
| POST | /admin/restore/attest | The keyless Tier-0 attestation: signature, completeness and anti-rollback, with no key and no data, so it runs even in a break-glass-only posture. | restore.verify |
| POST | /admin/restore/capsule | Serve one caller-chosen run’s non-secret master-capsule wraps and key commitment, so an operator’s browser recovers that run’s own master locally and supplies only that 32-byte master back on the restore. The break-glass private never leaves the browser. The run’s root manifest is signature-verified before the capsule is served, and a runId that is not a ULID is refused at the boundary. | restore.verify |
| POST | /admin/drill | A read-only recovery drill over a run. The recovery role holds this even though it cannot edit downpipes. | drill.run |
| POST | /admin/drill-evidence | Record a drill or offline-rehearsal evidence entry. This is a separate evidence log, not the hash-chained audit trail. | drill.run |
| GET | /admin/drill-evidence | List drill-evidence entries newest-first. | any authenticated role |
| POST | /admin/drill-all | Start an on-demand fleet-wide restore-test campaign (an optional downpipeIds list narrows it; absent or empty drills the whole fleet). Same authority as a single drill; the cron driver then drains the campaign under the shared subrequest budget so a fleet drill never competes with backups. |
drill.run |
| GET | /admin/drill-all | The fleet-drill campaign progress: the active campaign, or the last finished one. | any authenticated role |
The apply is never a single call. The engine gates it in order: the apply capability first (an unauthorised apply is a 403 and an audited denial before the rate limiter, so a blocked production write is never masked by a 429), then the step-up re-authentication described above, then a usable approval bound to the exact plan hash with a distinct approver (no approval yields 403 restore not approved with the plan hash), then a two-phase integrity verify. The mechanics live on dual control, which is the canonical home rather than this catalogue.
Attended verification sessions
Proving archives recoverable on an engine that holds no operational key, without the break-glass private ever leaving the operator’s browser. A session pins the runs it will cover, proves live possession of the recovery identity against the break-glass public key the engine holds in either posture, then verifies each pinned run from a browser-supplied per-run master, sample-decrypting to a discard sink. Every route below gates on drill.run, is rate-limited and is audited, and starting a session additionally needs a step-up re-authentication, because starting one is what enables per-run masters at all. The session record holds a sampling seed and a challenge proof hash, never a key, and the Durable Object refuses a caller whose subject differs from the session’s creator, so only the identity that started a session can drive it.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| POST | /admin/attest/session/create | Start a session: pin the latest completed run per downpipe in scope, clamp the sample rate into 1 to 100, and issue the live-possession challenge bound to the break-glass public key. One session is active at a time and it expires after eight hours. A caller with no attributable subject, which is what the bare break-glass token is, is refused with that reason rather than owning a session. | drill.run |
| POST | /admin/attest/session/prove | Verify the operator’s answer to the challenge. Idempotent: an already-proven session answers ok rather than re-challenging. | drill.run |
| POST | /admin/attest/session/capsules | Serve the pinned runs’ non-secret master capsules so the browser can recover each run’s master locally. Refused until the session is proven. | drill.run |
| POST | /admin/attest/session/verify | Verify a batch of at most 25 runs from browser-supplied masters and return per-run results with the session’s progress. A runId this session did not pin is never verified, so a recovered master can only ever be spent on a run the session committed to. | drill.run |
| GET | /admin/attest/session/status | Read one session by ?id=. The challenge proof hash is stripped from the response. |
drill.run |
| POST | /admin/attest/session/abort | End a session and free the one active slot. | drill.run |
The operator-facing account of what a session proves, and what it does not, is on attended verification.
Retention pruning
Applying a downpipe’s retention policy on an engine that holds no operational key. The scheduled retention pass honestly defers on a break-glass-only estate, because deciding which segments a retained run still references means reading the archive back. These routes move that key work to the browser: the engine serves each candidate run’s non-secret master capsule, the browser recovers each master locally, and it hands back only the masters. Each recovered master builds one enumerator for one call and is zeroed before the response is built. The planner and the apply are the same ones the cron pass calls, unmodified.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| POST | /admin/retention-prune/candidate | The current retained and superseded split for one downpipe ({ downpipeId }), with each candidate run’s non-secret master capsule so the browser can recover the masters before anything is requested. Capped at 200 runs in one call. |
restore.verify |
| POST | /admin/retention-prune/request | Raise a prune request bound to a plan hash over that downpipe’s current split, with a reason. The hash is keyless, so a request can be raised and approved before anyone recovers a master. | restore.request |
| POST | /admin/retention-prune/approve | A different authorised identity approves a pending request for a plan hash. | restore.approve |
| POST | /admin/retention-prune/reject | Reject a pending request for a plan hash. | restore.approve |
| GET | /admin/retention-prune/approvals | The pending prune-approval inbox. | any authenticated role (the Durable Object filters by the caller and the approver flag) |
| POST | /admin/retention-prune/apply | Plan, and commit only when this downpipe’s own stored enforce is true, previewOnly is not set and a usable approval for this exact plan exists. Takes a batch of browser-recovered masters, at most 200 runs per request. A batch that cannot open every candidate run, superseded ones included, is refused whole and names each run that failed, so the planner never enumerates a run this route has not already proven readable. A preview can delete nothing and needs no approval. |
restore.apply |
Approving a prune and applying one each additionally need a step-up re-authentication. A prune apply deletes the archive itself, which a restore apply does not, so it also carries its own maker-is-not-checker approval rather than riding the config change-control queue. What retention keeps and when it prunes is on retention and pruning.
Keys and the key ceremony
Installing and rotating the engine’s own secrets without a customer command line. Every write here is owner-exclusive: the key ceremony is the highest-consequence cryptographic action, and the capability behind it is held by the owner alone. The vintage inventory is a read, gated like the run-history reads it is derived from.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| POST | /admin/keys/install | Install the in-browser key-ceremony output as the engine’s own Worker secrets, using a one-shot scoped token for the writes only. The token and private values are never stored or logged; the install is audited by secret name only. | keys.ceremony |
| POST | /admin/keys/rotate | Write a new break-glass public key via a one-shot scoped token. A rotation, not a re-key: archives sealed before it still need the old recovery identity. | keys.ceremony |
| POST | /admin/keys/add-operational | Install a fresh operational pair on a break-glass-only engine, and nothing else. SIGNER_PRIVATE and BREAK_GLASS_PUBLIC are never read or written here, so it is a targeted add rather than a re-key: every run stays signed by the same signer. Refuses when an operational key is already present, because replacing one is the full ceremony’s job. Only runs sealed after the add gain the operational recipient. |
keys.ceremony |
| POST | /admin/keys/break-glass-only | Remove both operational Worker secrets, so the engine holds no key that can read an archive. Safe for recovery because every archive is also wrapped to the break-glass recipient. Reversible with the targeted add above, which needs a fresh operational key; archives sealed before that add stay break-glass-only. | keys.ceremony |
| POST | /admin/keys/posture-acknowledgement | Record that an operator confirmed the versioned acceptance statement for the posture they chose. The words are a frozen repo constant; the audit chain carries the posture, the statement version and a SHA-384 the engine computes over its own canonical text, never a client-supplied hash, so a posted text that does not match is refused. The statements are quoted in full on choosing your key posture. | keys.ceremony |
| POST | /admin/custody/send-share | Email one Shamir share of a split recovery key to one custodian. The share value and the custodian address never reach storage or the log; only the threshold and the total are audited. The share must decode to exactly 33 bytes, the custodian address must sit at a dotted custom domain, and the share rides in the body rather than an attachment. No ciphertext passes through this route, so a captured share opens nothing on its own. Step-up gated: a stale session could still send enough shares across enough calls to matter, and a share that has left the engine cannot be un-sent. | keys.ceremony |
| GET | /admin/keys/vintages | List which archive vintages each key opens, which runs are stranded to a key the engine no longer holds, and whether each run still verifies under the current signer. Read keylessly from each run’s signature-verified root manifest, so no stored index can fabricate a safe answer. Public fingerprints, role names and counts only. | downpipe.read |
In the two-recipient posture the engine holds a decryption-capable operational private key, so it can run its own restore tests. The break-glass-only route removes that key on purpose. The vendor holds nothing in either posture. The trust model is set out on the no-custody trust model.
Destinations and the archive target
Where backups land. Verifying a destination is a live write probe before anything is stored; repointing where every backup lands is break-glass-grade, so these are owner-exclusive.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/destination | The redaction-safe view of the single configured destination (host, bucket, region, who and when, never a credential). | any authenticated role |
| POST | /admin/destination | Set the default destination. Verifies the submitted credentials live (reachability, read, a real write probe, a best-effort delete) before the Durable Object stores anything. config: null clears. |
keys.ceremony |
| POST | /admin/destination/verify | Re-probe the effective destination (?id= for a specific one) and return the honest live result. Writes nothing. |
keys.ceremony |
| GET | /admin/destinations | List every console-set destination plus which id is the default. Redaction-safe. | any authenticated role |
| POST | /admin/destinations | Add a new destination (no id) or edit one (id present). Verifies the submitted credentials live before storing. | keys.ceremony |
| POST | /admin/destinations/remove | Delete a destination by id. force overrides the orphan guard that protects the only proven copy of some runs. |
keys.ceremony |
| POST | /admin/destinations/default | Make a destination the default. | keys.ceremony |
Sources and discovery
What the engine can back up, plus attaching new source bindings to the engine itself. The account-wide discovery token and the attach token are owner-exclusive; both are verified live and never persisted.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/sources/discover | Enumerate the engine’s own bindings and classify the backup-able ones, plus an account-wide listing when a discovery token is present. Lists binding names only, never values. | downpipe.read |
| POST | /admin/sources/discovery-token | Verify a pasted read-only Cloudflare token live, then hand it to the Durable Object to store and audit (never the value). token: null clears. |
keys.ceremony |
| POST | /admin/sources/discovery-accounts | Set which discovered accounts to scan. | keys.ceremony |
| GET | /admin/sources/discovery-status | The discovery token presence and the selected accounts. | downpipe.read |
| POST | /admin/sources/enable | Set which token-authenticated source types (cf-config, Workers, Stream, Images) the create-downpipe wizard offers. Additive and reversible: it changes only what the wizard offers, never a binding or a running downpipe, so it is not dual-control gated. | keys.ceremony |
| POST | /admin/sources/attach | Add or detach source bindings on the engine itself via a one-shot deploy token, used once and never stored. Opt-in dual control: with the gate armed, the first call queues a second-owner approval before the token is even required. | keys.ceremony (plus a second-owner approval when the gate is armed) |
| POST | /admin/sources/reattach-missing | Heal source bindings a deploy dropped: recomputes the missing set against the already-approved downpipe roster and re-adds them via a one-shot deploy token, with their original names and recorded native ids, so each downpipe’s next run continues the same lineage. Restores only the already-approved roster (zero scope expansion), so unlike attach it is not dual-control gated. | keys.ceremony |
| POST | /admin/downpipes/cf-config/rediscover | Re-run Cloudflare config discovery for one cf-config downpipe ({ id }), re-probing which surfaces are present, empty or unavailable and caching the partition. Uses the stored read-only discovery token; refuses if none is set. |
downpipe.write |
| POST | /admin/downpipes/cf-config/mode | Set a cf-config downpipe’s capture mode ({ id, mode }): auto captures the discovered present set, manual captures the operator’s explicit surface selection. |
downpipe.write |
A Worker sees only what is bound to it, so resources in the account that are not attached to the engine do not appear in discovery. Attaching them is the binding step the connect-a-source flow documents. The Cloudflare configuration source advertises its surface registry here: there are 313 Cloudflare config surfaces, of which 60 auto-restore in-band and 253 are backup-and-preview only. The full list is on the Cloudflare config surface reference.
Identity providers and people
Connecting external identity providers, mapping their groups to roles, and composing custom roles. The provider-connection routes are owner-exclusive because a connection is an authentication trust root; group and custom-role writes sit behind the access.policy capability.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/idp/presets | The built-in provider presets the console offers. | keys.ceremony |
| GET | /admin/idp/connections | List configured identity-provider connections (redacted; a stored client secret is never read back). | keys.ceremony |
| POST | /admin/idp/connections | Create or update a connection. A refused attempt is audited. | keys.ceremony |
| POST | /admin/idp/connections/delete | Remove a connection (which also kills its live sessions via a per-connection epoch). | keys.ceremony |
| POST | /admin/idp/connections/enabled | Enable or disable a connection. | keys.ceremony |
| POST | /admin/idp/connections/cert | Zero-downtime SAML signing-certificate rollover: append or replace the pinned certificate set on a connection in place, so a cert rotation no longer needs a delete-then-recreate that would kill every live session. Routed through the dual-control owner action (one approval). | keys.ceremony |
| POST | /admin/idp/test | A read-only pre-save probe over a connection config, so a misconfiguration is caught before first sign-in. It never touches storage. | keys.ceremony |
| POST | /admin/idp/test-saved | The same read-only probe as /admin/idp/test, run over an already-stored connection so it can be re-verified after an identity-provider-side change (a metadata move, a cert rollover) without retyping it. |
keys.ceremony |
| GET | /admin/roles | Read the per-email role table. | any authenticated role |
| POST | /admin/roles | Grant or change a member’s role (with an optional expiry). The Durable Object keeps the anti-escalation guard so only an owner may grant the owner role. | roles.write |
| POST | /admin/roles/delete | Remove a member’s role grant. | roles.write |
| GET | /admin/group-roles | Read the optional identity-provider group-to-role mapping. | any authenticated role |
| POST | /admin/group-roles | Map a group to any built-in role except owner (viewer, operator, restore-operator, approver or access-admin), or to a custom role. A group can never map to owner. | access.policy |
| POST | /admin/group-roles/delete | Remove a group mapping. | access.policy |
| GET | /admin/custom-roles | Read the custom-role catalogue. | any authenticated role |
| POST | /admin/custom-roles | Compose a custom role. The Durable Object enforces no privilege escalation, bars owner-reserved capabilities, and requires a write capability behind any editable screen. | access.policy |
| POST | /admin/custom-roles/delete | Delete a custom role. Its holders drop to the least-privilege floor. | access.policy |
downpipes ships 8 named providers plus generic OIDC and OAuth2. SAML is sign-only and service-provider-initiated only. A per-connection allowIdpInitiated boolean is accepted and stored by the connection API, and the XML response verifier does read it, but setting it true does not enable anything: the assertion consumer refuses any assertion that carries no server-minted RelayState record, and that refusal returns before the connection is loaded. Only an SP-initiated request mints such a record. Treat the field as inert and do not build against it. The full provider matrix, and which login modes actually complete at first sign-in, are on the supported providers reference.
Sessions and passkeys
Managing enrolled passkeys and terminating sessions. Self-service actions need only an authenticated caller; acting on another member is gated inside the Durable Object.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/passkey/credentials | List a named member’s enrolled passkeys with ?email=, or your own when it is omitted. A caller with no email of its own, which is what a bearer token is, cannot resolve a target and is refused with a 400 naming the account-wide route rather than answering an empty list. |
any authenticated role (self); roles.write (another member) |
| GET | /admin/passkey/credentials/all | Enumerate every enrolled passkey in the account, needing no email. Each row carries hasRoleEntry, which records whether that email holds a row in the role table, and is named for the measurement rather than for a conclusion: a member whose role comes from an identity-provider group claim legitimately reads false. Reports only, deletes nothing. |
roles.write |
| POST | /admin/passkey/credentials/delete | Revoke a WebAuthn credential. The Durable Object bumps the member’s session epoch so sessions predating the revoke die. | any authenticated role (self); roles.write (another member) |
| GET | /admin/signin-factors | Read every way one email can still authenticate, across all three stores at once: passkey credentials, recovery codes and unredeemed invites. ?email= scopes it to one member; omitting it reads the whole account, with no fallback to the caller’s own email. Redaction-safe, and a named email always comes back as a row. |
any authenticated role (self); roles.write (another member, or the account) |
| POST | /admin/signin-factors/revoke | Remove all three sign-in stores for one email as one audited operation and terminate their sessions. CSRF-gated, has no self-service arm, and carries the sole-Owner floor and dual control. This is the offboarding write; a role deletion does not do it. | roles.write |
| POST | /admin/stepup/begin | Begin a WebAuthn step-up re-auth: issue a fresh assertion challenge over the caller’s own passkeys. A caller with no passkey gets ok: false and re-authenticates instead. The rpId and origin are server-resolved, never trusted from the body. |
any authenticated role |
| POST | /admin/stepup/finish | Finish the step-up: verify the assertion and mint a single-use step-up token the console presents on the retry of a sensitive action. | any authenticated role |
| POST | /admin/sessions/terminate-others | Terminate your other sessions and keep this one alive with a fresh cookie. | any authenticated role with a first-party session |
| POST | /admin/sessions/terminate-user | Terminate a named member’s sessions. | roles.write |
| POST | /admin/sessions/terminate-all | Terminate every session by rotating the session signing key. | owner |
Notifications and alerting
Alert channels, rules and the delivery history. The writes sit behind the notify capability; with the change-control gate armed, a channel change is queued for a second approver.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/notify/channels | List configured alert channels. | any authenticated role |
| POST | /admin/notify/channels | Create or update an alert channel. | notify.config |
| POST | /admin/notify/channels/delete | Remove a channel. | notify.config |
| GET | /admin/notify/rules | List the alert rules. | any authenticated role |
| POST | /admin/notify/rules | Create or update a rule. | notify.config |
| POST | /admin/notify/rules/delete | Remove a rule. | notify.config |
| GET | /admin/notify/history | The delivery history. | any authenticated role |
| POST | /admin/notify/test | Send a redaction-safe test to one channel. The router delivers it itself; the test carries no secret and is not recorded as a real event. | notify.config |
Posture, coverage and expiry
The honest posture and coverage views, the credential and key expiry tracker, and accepting a posture risk. Coverage stays honest-unknown from the portal when no inventory is stored; the inventory submission is engine-only and is reference data that never grants data access.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/posture | The posture report. | posture.read |
| POST | /admin/posture/accept | Record an override for a known check: kind is risk-accepted (the default when absent), attested-pass, compensating-control or not-applicable, with a mandatory reason. Owner-reserved. |
posture.riskaccept |
| POST | /admin/posture/unaccept | Withdraw an override so the check is graded on its observed state again. Owner-reserved. | posture.riskaccept |
| POST | /admin/coverage/inventory | Store the reference resource inventory. Reference data only; it never grants data access. | access.policy |
| GET | /admin/coverage | The gap view (protected, unprotected, untested, plus a rollup). With no inventory stored it returns the honest unknown shape and never implies full coverage. | posture.read |
| GET | /admin/expiry | The computed expiry status of tracked credentials and keys (days remaining and a state). | any authenticated role |
| POST | /admin/expiry | Add or update an expiry item. Dispatches through the change-control gate. | expiry.config |
| POST | /admin/expiry/delete | Remove an expiry item. Dispatches through the change-control gate. | expiry.config |
| POST | /admin/expiry/cleanup-attest | Attest that a spent credential was deleted in Cloudflare, so the registry can mark it cleaned. An attestation, never a verified deletion: the engine holds no Cloudflare token to check. | expiry.config |
The coverage inventory is submitted from the Security centre’s populate-inventory modal, which parses what you paste into the per-type resource lists and posts them. From the portal, coverage is never green for an unknown resource. GET /admin/runs/at now feeds the restore screen’s calendar directly. The reasoning is on coverage.
Config change-control and version history
downpipes versions its own governance configuration and offers an opt-in dual-control gate over config mutations. The reads sit behind the config-read capability; the gate toggle is owner-only, with an asymmetric off switch so it can never deadlock its own disarm.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| POST | /admin/config/snapshot | Capture the current config posture as a new signed history version. De-dupes against the head. | access.policy |
| GET | /admin/config/history | The version history newest-first, with the chain head and a verify verdict. | downpipe.read |
| GET | /admin/config/version | One full version by ?id=N. |
downpipe.read |
| GET | /admin/config/diff | The plain-language change list between two versions, ?from=&to=. |
downpipe.read |
| GET | /admin/config/approval-policy | The opt-in dual-control gate flag. | downpipe.read |
| POST | /admin/config/approval-policy | Toggle the gate. Arming is immediate; an attributable owner disarming is queued behind a second owner, while the bare break-glass token can always disarm immediately to avoid a deadlock. | keys.ceremony, then owner re-resolved |
| POST | /admin/config/change-number-policy | The owner opt-in “Require Change Number” toggle (ITIL/CAB change management). Applies immediately and is audited as a config-policy-change; not dual-control gated, since it is a process control rather than a security control. | keys.ceremony |
| POST | /admin/config/signin-context-policy | The owner opt-in notification for a sign-in from an unusual location. Applies immediately and is audited as a config-policy change; the current value rides on the GET /admin/config/approval-policy view alongside the other flags. |
keys.ceremony |
| GET | /admin/config/attended-cadence | The estate-wide attended-verification interval in days. One integer that names no run and carries no proof history, so a config reader may read it. | downpipe.read |
| POST | /admin/config/attended-cadence | Set that interval, or 0 to clear it. Applies immediately and is audited. It gates nothing, so there is no dual-control arm here. |
keys.ceremony |
| GET | /admin/config/changes | The pending-change inbox, each with its plain-language diff. | downpipe.read |
| GET | /admin/owner-actions | The owner-action approval inbox (pending and armed high-blast-radius owner actions awaiting a second owner). The Durable Object strips any live secret from the listed parameters. | downpipe.read |
The approve and reject for both inboxes are the dynamic-path routes listed under routing that runs before the switch. The change-control mechanics are covered on change control, and the version history on config version history.
Audit trail
The tamper-evident, hash-chained trail. The reads are open to any authenticated role; the one write the console performs records an intent marker only, never a result or a value, and is owner-only.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/audit | The trail, newest-first, paged and filtered, with the chain head. | any authenticated role |
| GET | /admin/audit/verify | Recompute the chain and report intact or broken-at. A break is a 200 result, the on-screen proof of tamper-evidence. |
any authenticated role |
| GET | /admin/audit/export | The filtered or whole log as a download, JSON or ?format=csv, with the chain head hash for an external verifier. |
any authenticated role |
| POST | /admin/audit/intent | Record an intent marker (a key ceremony or an access-policy change happened out of band). The engine cannot witness the out-of-band step itself, so it records that it occurred, never a value. | keys.ceremony |
The trail records operator identity (the actor email, the source IP, the role, and approver emails on a restore apply), so it carries personal data and is tamper-evident rather than identity-free. The feed and its contents are described on the SIEM audit feed.
Audit-log push (SIEM)
The engine can also push the same hash-chained trail outward to a SIEM over HTTPS, rather than waiting on a collector to poll it. One configured destination covers three payload shapes, raw JSON, Splunk HEC, or Datadog, chosen by a format field alongside the endpoint URL and the auth header name. The auth secret is sealed at rest and never returned by any of the reads below.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/push | The redacted view of the configured push destination: endpoint, format, header name, enabled flag, who set it and when, the cursor lag, and the delivery trail. Never the secret. | any authenticated role |
| POST | /admin/push | Set or replace the push destination. The auth secret is sealed at rest before it reaches storage and is never read back; replacing it kills the old secret immediately. | keys.ceremony |
| POST | /admin/push/delete | Clear the configured push destination. | keys.ceremony |
| POST | /admin/push/test | Send one synthetic, audit-shaped event to the configured destination and return its honest HTTP outcome. Does not advance the delivery cursor. | keys.ceremony |
The outbound POST runs through the same egress-secure fetch the alert webhook uses (https only, private and metadata address ranges blocked by default, a timeout, no redirect followed), and delivery is at-least-once: a sequence cursor advances only on a 2xx, so an interrupted delivery re-sends rather than silently drops. Because the engine is the one dialling out, a Cloudflare Access perimeter in front of the console hostname never blocks it, unlike the pull feed above. The full account, the eight body formats, the three sinks that carry them, the security posture, and the at-least-once semantics, is on forwarding the audit log to your SIEM (push).
Metrics push (OTLP)
The engine can also push its own backup-health metrics outward to an OpenTelemetry collector over OTLP/HTTP, so a monitoring stack sees run health without polling. One destination, one transport and one bearer or API-key secret, sealed at rest before storage and never read back by the view below.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/otlp-push | The redacted view of the configured metrics-push destination. Never the secret. | any authenticated role |
| POST | /admin/otlp-push | Set or replace the destination. The endpoint is shape-checked and egress-checked, and the secret is sealed at the router before the Durable Object ever sees it, so an invalid submission never reaches the approval queue and storage holds only ciphertext. With a second owner present, or the opt-in config gate armed, the call queues a pending approval rather than repointing the egress inline. An omitted secret keeps the stored one, so the endpoint or the enabled flag can be edited without resupplying a write-only value. | keys.ceremony |
| POST | /admin/otlp-push/delete | Clear the configured metrics-push destination. | keys.ceremony |
Both writes are among the step-up-gated set described at the top of this page: a stolen ambient session redirecting operational telemetry is the same class of egress theft as redirecting the audit trail. The collector-side setup is on OTLP metrics push.
Licence and the control plane
The assurance licence is fail-open and gates only assurance features, never the data or recovery path. The activation route is owner-exclusive and verifies the token live against the pinned vendor key before storing.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/licence | The effective assurance tier. Fail-open: a licence problem yields the community tier rather than an error. | any authenticated role |
| POST | /admin/licence | Activate a licence token (verified live against the pinned vendor key before storing) or clear it (token: null). |
keys.ceremony |
Control-plane disaster recovery
Rebuilding the control plane (the downpipe and destination configuration, the role table and the owner-governed policy) after it has been wiped, from a signed export pulled out-of-band from the destination bucket. The export carries a non-secret inventory of the identity-provider connections and the notification routing, so a recovery has the checklist of what to reconnect, but those connections, along with sessions and passkeys, are re-established after the rebuild rather than restored, and any account-held secret rides only as a wrapped envelope or a marker that it must be re-entered. The restore, restore-sealed and apply-staged routes are gated directly on the bare break-glass ADMIN_TOKEN rather than the capability model, because after a wipe the role table is empty and a passkey or Access caller resolves only to a recovery-required viewer that cannot authorise the rebuild. See recovering downpipes itself for the end-to-end flow.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/control-plane/status | Whether the control plane needs recovery, why, and whether it is empty. Readable before any owner exists, so the console’s recovery banner can surface it. | any authenticated role |
| GET | /admin/control-plane/export-download | Build the current control-plane export on demand and return it signed, so an operator can keep a fresh copy alongside their recovery kit rather than depending on the next scheduled write. No-custody is re-asserted before it is returned. | access.policy |
| POST | /admin/control-plane/restore | Rebuild the wiped control plane from a signed export and its detached signature, verified against the engine’s pinned signer before the Durable Object is touched. No-custody is re-asserted: an export carrying any plaintext secret is refused. | the bare break-glass ADMIN_TOKEN |
| POST | /admin/control-plane/restore-sealed | The same-account counterpart of restore, for the estate whose default export is sealed and whose engine holds no config-recipient key to open it. Takes the still-encrypted sealed artefact plus the plaintext an operator’s browser already recovered with their own break-glass identity, verifies the sealed wrapper against the engine’s own signer, cross-checks the plaintext against the sealed artefact’s signed body hash, then rebuilds through the same path as restore. Not yet reachable from the console UI. | the bare break-glass ADMIN_TOKEN |
| POST | /admin/control-plane/apply-staged | Confirm and apply a staged export’s authority slice (RBAC, the first-owner bootstrap re-arm) after the cron auto-heal has already verified, staged it and resumed backups. The signature is re-verified here before any authority is restored. | the bare break-glass ADMIN_TOKEN |
| POST | /admin/control-plane/import | Rebuild an estate’s definition (downpipes, destinations, discovery) from a signed export on a fresh engine, verified against the operator-supplied signer.pub from their offline recovery kit rather than the engine’s own signer. Tamper evidence only: it grants no authority, and downpipes from a different account arrive disabled pending re-pointing. |
access.policy |
| POST | /admin/control-plane/import-sealed | The browser-unseal counterpart of import, for a sealed export: takes the sealed artefact, its detached signature, the recovery kit’s signer.pub, and the plaintext the operator’s browser already recovered, cross-checks the plaintext against the signed body hash, then imports through the same no-authority path as import. |
access.policy |
Controlled self-update
A controlled, reversible self-update with a canary gate and an always-available rollback. A signed release can carry the engine alone or the engine and the console as components, applied engine-first. Every write here is owner-exclusive, requires a one-shot deploy token that is never stored, and never touches the data or recovery path.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/updates | The available-update check against the signed release channel. Surfaces the per-component release view additively; updateAvailable is computed for the engine row only, and the console compares its own baked version in the browser. |
any authenticated role |
| GET | /admin/update/status | The update lifecycle state the console reads, including the persisted settle outcome, which is recorded before any rollback deploy and is the authoritative result of an apply. | downpipe.read |
| POST | /admin/update/apply | Resolve and verify each signed artefact, record a rollback target, and (unless dryRun, the default) upload and promote the new version. An optional components list opts into the component-aware flow (engine first, the console only after the engine settles); without it the apply is engine-only. A downgrade needs an explicit owner opt-in. |
keys.ceremony |
| POST | /admin/update/settle | Fly the canary on the now-live version and either keep it or auto-roll-back, persisting the outcome before any rollback deploy. Called by the console immediately after a promote; a queued console component applies on a keep. | keys.ceremony |
| POST | /admin/update/rollback | A standalone one-click revert to the recorded known-good version, per component (component: "console" reverts the console alone). The safe direction, so it needs no second-owner approval. |
keys.ceremony |
| POST | /admin/update/ramp | An opt-in gradual percentage rollout that serves real traffic, canary-gated, holding at the percentage or auto-rolling-back. Engine-only: a static-assets swap is atomic at promote, so a ramp naming the console is refused. | keys.ceremony |
| POST | /admin/update/ramp/settle | Settle a gradual ramp, and a genuinely separate request from the start: Cloudflare routes this dispatch afresh, so it carries the ramp’s own percentage chance of landing on the ramped slice, which is what lets its verdict test the new code at all. Mutually exclusive with POST /admin/update/settle: that route refuses a ramp-shaped pending record and this one refuses a non-ramp record, so a pending record is settled by exactly one of them. Needs the one-shot deploy token, because a rollback is a real deploy, and never stores it. |
keys.ceremony |
The update flow, its trust model and the rollback path are on applying an update and update trust and rollback.
Status, setup and support
The presence-only onboarding reads, the live preflight, and the support-bundle and ingest-credential surface. The status reads carry presence and counts only, never a secret value.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| GET | /admin/whoami | The verified session identity, role, role source and session expiry. | any authenticated role |
| GET | /admin/status | Onboarding readiness as presence-only booleans (keys, destination, sources, expiry warnings, and more). | any authenticated role |
| GET | /admin/setup-state | The consolidated facts the guided first run derives its steps from: presence and counts only. | any authenticated role |
| GET | /admin/preflight | Live read-only probes that prove each Cloudflare prerequisite rather than assuming it, each with a remediation. | any authenticated role |
| POST | /admin/setup/acknowledge | Mark the guided first run done when keys are already present. Server-enforced: it only fires when the engine observes the keys present. | keys.ceremony |
| POST | /admin/email/test | Send a test message to your own verified email (the body is ignored), returning the honest platform outcome including the error code. | any authenticated role |
| GET | /admin/support | The supportability view: whether a vendor sealing key is configured, and the redacted state of each ingest credential. | posture.read |
| GET | /admin/support/bundle | Download the support bundle, sealed to the vendor key when configured and signed otherwise. | posture.read |
| POST | /admin/support/bundle | The console’s generate path: the same bundle as the download, plus an optional bounded ring of browser-side errors the console collected locally, re-validated and folded into this one response and then discarded. Nothing about the ring is stored, so it is evidence riding in the pack rather than a background beacon. An oversized body is refused before it is read. | posture.read |
| POST | /admin/support/credentials | Mint a read-only, scoped, expiring ingest credential. Owner-only; the secret is shown once and stored only as a hash. Opt-in dual control applies when the gate is armed. | owner (plus a second-owner approval when the gate is armed) |
| POST | /admin/support/credentials/delete | Revoke an ingest credential by scope. | owner |
| POST | /admin/policy/require-access | Report the Access-only hardening posture and whether Access is configured. A surface and echo, not a setter: it writes nothing. | access.policy |
| POST | /admin/policy/retire-break-glass-token | Retire the bare admin-token bearer in-app, so the engine stops honouring it immediately with no redeploy. Refused unless a way back in exists. | keys.ceremony |
GET /admin/status and GET /admin/setup-state are presence-only by design: they report what is configured or not, counted and proven, but never a secret value, so they are account reconnaissance behind the authentication gate rather than a data surface.
Demo-only
One route exists only on a throwaway demo engine.
| Method | Path | What it does | Capability floor |
|---|---|---|---|
| POST | /admin/demo/reset | Wipe the scheduler Durable Object back to first-run. It returns 404 before any authentication when the demo flag is off, so a production engine has no reset surface to find. When the flag is on, it is authorised specifically by presenting the bare admin token. |
the bare admin token (demo flag must be on) |
No reset surface in production
POST /admin/demo/reset is gated on a deploy-time demo flag and 404s before authentication when that flag is off. A self-hosted production engine never exposes it. It is destructive by design and exists only so a public demo can be reset.
What this catalogue leaves out
One family of routes is deliberately not a row above, and it is left out because a production engine does not serve it rather than because it is undocumented.
The harness fault-injection surface, POST /admin/test-fault/arm, POST /admin/test-fault/disarm, POST /admin/test-fault/canary-tick, POST /admin/test-fault/retention-tick and GET /admin/test-fault/status, exists only on an engine deployed with the HARNESS_TEST_FAULTS flag set, which is a test estate of ours rather than a customer deployment. Without that flag the router returns 404 for the whole /admin/test-fault/ prefix before authentication, exactly as the demo reset does, so a self-hosted engine has no fault surface to find or invoke. With the flag on, the caller must additionally hold the owner role. The routes arm and read one persisted fault and run a cron tick on demand, so a fault-provocation journey need not wait a real hour.
Nothing else is omitted. A gate in this repository compares the generated route set against the rows above on every run and fails naming any route that is served but has no row, with that one prefix as its only exclusion.
Where this fits
This catalogue is the map. For a generated route-level view, the method, path, summary and tag of every route, with a downloadable spec, open the API explorer. For how a call authenticates and how a role becomes a capability, read authentication and authorisation. For the restore family in depth, including the apply preconditions and the cf-config restore context, read the restore and recovery API guide. For the role-to-capability map, see roles and capabilities. When a call returns a status you did not expect, the error and status codes reference and troubleshooting explain the shapes.
Last updated .