# Console and admin API

> Every endpoint of the customer console API (/v1/account, /v1/console) and the private operator admin API (/v1/admin), with their OpenAPI contracts.

Everything the console at `/app` does goes through a JSON API you can call
yourself. The contracts are OpenAPI 3.1 documents, checked by the test
suite against the registered routes and real responses:

- [/docs/openapi-console.yaml](/docs/openapi-console.yaml): the account and
  console API, for customers;
- [/docs/openapi-admin.yaml](/docs/openapi-admin.yaml): the operator admin
  API.

Errors are `{"error": "<code>", "message": "…"}`; the codes are on
[Error codes](/docs/error-codes#console-and-admin-api).

## Authentication

- **Console.** `POST /v1/account/login` sets the `ag_account` session cookie
  (HttpOnly, SameSite=Lax, Secure over TLS; 12 hours, or 2 hours without
  use). `GET /v1/account/session` returns the signed-in user, the customer
  and a CSRF token; send it as `X-CSRF-Token` on every request that changes
  something. Requests from other sites are refused (`cross_origin`).
- **Two-factor authentication.** With 2FA on, `login` answers
  `{"mfaRequired": true}` and a 5-minute pre-session; `POST
  /v1/account/login/mfa` with the code from the authenticator app (or a
  recovery code) completes the sign-in. An account can require 2FA of all
  its members; members without it get `403 mfa_setup_required` on the
  console until they turn it on.
- **API tokens.** For scripts and CI, create a token under API tokens and
  send `Authorization: Bearer agt_…` to `/v1/console/*` (no CSRF token).
  A token is limited to its scopes (`sites:read`, `sites:write`,
  `analytics:read`, `members:read`) and never works on `/v1/account` or
  `/v1/admin`.
- **Admin.** The admin API is private: the public host answers `404` for
  `/v1/admin/`. Operators reach it on a private network or through an SSH
  tunnel, sign in with the operator key (`POST /v1/admin/login`, cookie
  `ag_admin`) and send `X-CSRF-Token` on changes.
- Operator and customer sessions never unlock each other's APIs. A customer
  sees only its own sites; another customer's site key answers `404`.
- **Accounts and roles.** A user can belong to several accounts; the session
  works in one at a time (the active account, changed with
  `POST /v1/account/switch`). In each account the user has a role: `owner`,
  `admin`, `member` or `readonly`. Every console route needs a permission of
  that role: `sites:read`, `sites:write`, `analytics:read`, `members:read`,
  `members:write`, `tokens:write`, `account:write`, `account:delete` or
  `audit:read`. A missing one answers `403 forbidden` and names it. A
  suspended account answers `403 account_suspended` (its sites keep
  protecting), a suspended user `403 user_suspended`.

| Role | Permissions |
| --- | --- |
| `owner` | all, including `account:delete` (delete the account, transfer ownership) |
| `admin` | everything else: sites, analytics, members, tokens, account settings, audit log |
| `member` | `sites:read`, `sites:write`, `analytics:read`, `members:read` |
| `readonly` | `sites:read`, `analytics:read`, `members:read` |

## Account

| Method | Path | Summary |
| --- | --- | --- |
| `POST` | `/v1/account/request-access` | Ask for an account; the same answer whether or not the address is known |
| `POST` | `/v1/account/login` | Sign in; sets ag_account |
| `POST` | `/v1/account/logout` | End the session |
| `GET` | `/v1/account/session` | The signed-in user, their accounts, the active account with role, limits and permissions, and the CSRF token |
| `POST` | `/v1/account/switch` | Make another of the user's accounts the active one |
| `POST` | `/v1/account/accounts` | Create another account, owned by the caller (at most 3 owned) |
| `GET` | `/v1/account/invite/{token}` | Who an invite link is for, without using it |
| `POST` | `/v1/account/invite/accept` | Set the first password with an invite link (single use, 7 days) and sign in |
| `POST` | `/v1/account/password/forgot` | Email a reset link (1 hour) if an account uses the address; always 202 |
| `POST` | `/v1/account/password/reset` | Set a new password with a reset link; ends the user's other sessions and signs in |
| `POST` | `/v1/account/login/mfa` | Complete a sign-in with a 2FA code or a recovery code |
| `GET` | `/v1/account/mfa` | Your 2FA state and whether the account requires it |
| `POST` | `/v1/account/mfa/setup` | Start 2FA setup: a TOTP secret and an otpauth:// URL for a QR code |
| `POST` | `/v1/account/mfa/enable` | Turn 2FA on with a code; returns 10 single-use recovery codes once |
| `POST` | `/v1/account/mfa/disable` | Turn 2FA off (password and a code or recovery code) |
| `POST` | `/v1/account/password` | Change your password; ends your other sessions |
| `GET` | `/v1/account/sessions` | Your signed-in sessions (browser, pseudonymous address) |
| `DELETE` | `/v1/account/sessions/{id}` | End one session |
| `POST` | `/v1/account/sessions/revoke-others` | End every session but this one |

## Console

| Method | Path | Summary |
| --- | --- | --- |
| `GET` | `/v1/console/account` | The active account: name, status, limits and usage |
| `PATCH` | `/v1/console/account` | Rename the account (`account:write`) |
| `DELETE` | `/v1/console/account` | Delete the account (owner; type its name to confirm; no site may enforce); data is purged after 30 days |
| `POST` | `/v1/console/account/transfer-ownership` | Make another member the owner; the caller becomes an admin |
| `GET` | `/v1/console/members` | The account's members with role, status and last sign-in |
| `POST` | `/v1/console/members` | Invite someone by email with a role (`members:write`) |
| `PATCH` | `/v1/console/members/{userId}` | Change a member's role (only owners change owners; never the last owner) |
| `DELETE` | `/v1/console/members/{userId}` | Remove a member (never the last owner) |
| `POST` | `/v1/console/members/{userId}/resend-invite` | Email a fresh invite link to a member who has not set a password |
| `GET` | `/v1/console/audit` | The account's audit log: members, roles, sites, credentials, limits, status, sign-ins |
| `GET` | `/v1/console/sites` | The customer's sites |
| `POST` | `/v1/console/sites` | Add a site (monitor mode, widget mode invisible unless given); siteKey, customerId and customerName are refused; default action submit; at most 10 allowed origins (400 origin_limit) |
| `GET` | `/v1/console/sites/{siteKey}` | A site with credentials, changes and rules summary |
| `PATCH` | `/v1/console/sites/{siteKey}` | Change name, origins (at most 10), actions, routes, mode or widget mode |
| `GET` | `/v1/console/sites/{siteKey}/changes` | The site's audit trail |
| `POST` | `/v1/console/sites/{siteKey}/credentials` | A backend credential, shown once (at most 3 live per site) |
| `POST` | `/v1/console/sites/{siteKey}/credentials/{credId}/rotate` | Replace a credential; the old one works for overlapSeconds |
| `POST` | `/v1/console/sites/{siteKey}/credentials/{credId}/revoke` | Revoke a credential now |
| `GET` | `/v1/console/sites/{siteKey}/rules` | The site's rule set and overrides |
| `PUT` | `/v1/console/sites/{siteKey}/rules` | Replace the site's rules or overrides |
| `POST` | `/v1/console/sites/{siteKey}/rules/validate` | Check rules without saving |
| `GET` | `/v1/console/sites/{siteKey}/install` | waiting until AgentGate sees a decision for the site, then live |
| `GET` | `/v1/console/sites/{siteKey}/health` | Integration health over the last 24 hours: is the server verifying tokens, is traffic arriving, are pages on unlisted origins, are siteverify calls failing |
| `GET` | `/v1/console/sites/{siteKey}/analytics` | Challenge outcomes, solve rate, solve types, token validation and top lists (default: last 24 hours) |
| `GET` | `/v1/console/sites/{siteKey}/agent-prompt` | A setup prompt for a coding agent (text/markdown): site key, endpoint contract, AGENTGATE_SECRET from the environment (never the secret), checklist |
| `GET` | `/v1/console/managed-groups` | The managed rule-group catalog (read-only, the same for every account): what each group reference in a rule set expands to |
| `GET` | `/v1/console/metrics` | Daily counts, challenges, latency (errors.eventWriteFailures is always 0 here: it is process-wide) |
| `GET` | `/v1/console/decisions` | Decisions, newest first (same filters as the admin API) |
| `GET` | `/v1/console/decisions/{decisionId}` | One decision with its labels and rule (no trace link) |
| `GET` | `/v1/console/visitors` | Pseudonymous request sources |
| `GET` | `/v1/console/visitors/{visitorId}` | One source and its recent decisions |
| `GET` | `/v1/console/tokens` | The account's API tokens (never the secrets) |
| `POST` | `/v1/console/tokens` | Create an API token with scopes and an optional expiry; the secret is shown once |
| `DELETE` | `/v1/console/tokens/{id}` | Revoke an API token now |

Limits are per account: by default 5 sites, 3 live backend secrets per site,
10 members and 25 live API tokens (`GET /v1/console/account` shows them and
the usage). New sites
start in monitor mode with one browser-checked action, `submit`.

## Admin (private)

| Method | Path | Summary |
| --- | --- | --- |
| `POST` | `/v1/admin/login` | Exchange the operator credential (ADMIN_KEY) for a session cookie |
| `POST` | `/v1/admin/logout` | End the session |
| `GET` | `/v1/admin/session` | The current operator and the CSRF token for mutations |
| `GET` | `/v1/admin/sites` | Every site with today's traffic and integration health |
| `POST` | `/v1/admin/sites` | Create a site (monitor mode unless mode is given) |
| `GET` | `/v1/admin/sites/{siteKey}` | Site detail with credentials, recent changes and rules summary |
| `PATCH` | `/v1/admin/sites/{siteKey}` | Change name, origins, actions, routes or mode |
| `GET` | `/v1/admin/sites/{siteKey}/changes` | The site's configuration audit trail, newest first |
| `POST` | `/v1/admin/sites/{siteKey}/credentials` | Create a backend credential; the secret is returned once |
| `POST` | `/v1/admin/sites/{siteKey}/credentials/{credId}/rotate` | Create a replacement; the old credential works for overlapSeconds (default 86400, at most 7 days) |
| `POST` | `/v1/admin/sites/{siteKey}/credentials/{credId}/revoke` | Revoke immediately |
| `GET` | `/v1/admin/sites/{siteKey}/rules` | The site's base rule set, per-rule overrides and effective rule set |
| `PUT` | `/v1/admin/sites/{siteKey}/rules` | Replace the site's rule configuration |
| `POST` | `/v1/admin/sites/{siteKey}/rules/validate` | Validate without storing; errors carry line and column for ruleSetText |
| `GET` | `/v1/admin/managed-groups` | Every registered managed rule group version and its member rules |
| `GET` | `/v1/admin/metrics` | Overview counts and a bucketed series for a time range |
| `GET` | `/v1/admin/visitors` | Pseudonymous request sources, most recently seen first |
| `GET` | `/v1/admin/visitors/{visitorId}` | One source's summary and its 50 newest decisions (default range: 31 days) |
| `GET` | `/v1/admin/decisions` | Decisions, newest first, with filters |
| `GET` | `/v1/admin/decisions/{decisionId}` | Why: labels grouped by source, matched rule, scores, versions, would-decision, trace |
| `PUT` | `/v1/admin/decisions/{decisionId}/label` | Label a decision human, bot, agent or unsure for the model loop |
| `GET` | `/v1/admin/labels` | Export operator labels in update order (oldest first) for training |
| `POST` | `/v1/admin/labels` | Label a decision (by decision or request ID) with a source, for probes and scripts |
| `GET` | `/v1/admin/sites/{site}/feature-capture` | Whether the site stores model feature vectors (never content) and for how long |
| `PUT` | `/v1/admin/sites/{site}/feature-capture` | Opt in or out of feature capture; opting out deletes the site's vectors |
| `GET` | `/v1/admin/models` | Active, candidate and previous model per kind (agent, bot, content) |
| `GET` | `/v1/admin/models/{kind}/{role}` | A model file (weights and metadata) |
| `PUT` | `/v1/admin/models/{kind}/{role}` | Load a candidate model; it scores traffic in shadow without affecting decisions |
| `DELETE` | `/v1/admin/models/{kind}/{role}` | Stop shadow scoring |
| `GET` | `/v1/admin/models/{kind}/compare` | Candidate vs active on recent traffic: agreement, would-change counts, per-label rates |
| `POST` | `/v1/admin/models/{kind}/promote` | Make the candidate active (recorded in config_changes; survives restarts) |
| `POST` | `/v1/admin/models/{kind}/rollback` | Restore the previous active model |
| `GET` | `/v1/admin/sites/{siteKey}/install` | Whether AgentGate has seen traffic for the site (the add-site wizard's install check) |
| `GET` | `/v1/admin/sites/{siteKey}/health` | Integration health: typed checks over the last 24 hours |
| `GET` | `/v1/admin/sites/{siteKey}/analytics` | Challenge outcomes, solve types, token validation and top lists for a range |
| `GET` | `/v1/admin/sites/{siteKey}/agent-prompt` | A self-contained setup prompt for a coding agent (Markdown) |
| `GET` | `/v1/admin/access-requests` | Access requests from /signup, newest first |
| `POST` | `/v1/admin/access-requests/{id}/approve` | Create the customer (named by company, else name), the user and a 7-day invite link; email the link |
| `POST` | `/v1/admin/access-requests/{id}/decline` | Decline a pending request (no email is sent) |
| `GET` | `/v1/admin/customers` | Accounts with owner, member and site counts, status, limits and last activity (`status`, `q` filters) |
| `GET` | `/v1/admin/customers/{id}` | One account: members, sites with health, API tokens, limits, usage, recent audit events |
| `PATCH` | `/v1/admin/customers/{id}` | Rename an account or change its limits |
| `DELETE` | `/v1/admin/customers/{id}` | Delete an account (same rules as the owner's) |
| `POST` | `/v1/admin/customers/{id}/suspend` | Suspend: console and API closed, sites keep protecting |
| `POST` | `/v1/admin/customers/{id}/reactivate` | Lift a suspension, or restore a deleted account within 30 days |
| `POST` | `/v1/admin/customers/{id}/sites` | Create a site in the account |
| `POST` | `/v1/admin/customers/{id}/members` | Add someone to the account with a role; returns an invite link for a new user |
| `PATCH` | `/v1/admin/customers/{id}/members/{userId}` | Change a member's role |
| `DELETE` | `/v1/admin/customers/{id}/members/{userId}` | Remove a member |
| `POST` | `/v1/admin/customers/{id}/members/{userId}/resend-invite` | A fresh invite link for a member without a password |
| `GET` | `/v1/admin/customers/{id}/audit` | The account's audit log |
| `DELETE` | `/v1/admin/customers/{id}/tokens/{tokenId}` | Revoke one of the account's API tokens |
| `POST` | `/v1/admin/sites/{siteKey}/move` | Move a site to another account |
| `POST` | `/v1/admin/users/{userId}/suspend` | Suspend a user everywhere; their sessions end |
| `POST` | `/v1/admin/users/{userId}/reactivate` | Lift a user's suspension |
| `POST` | `/v1/admin/users/{userId}/reset-link` | A one-hour password reset link, also emailed |
| `POST` | `/v1/admin/users/{userId}/mfa/reset` | Turn a user's 2FA off (lost authenticator) |
| `GET` | `/v1/admin/accounts` | Customer users with their accounts and roles |
| `POST` | `/v1/admin/accounts` | Invite a user directly (a new customer, or customerId to add a teammate) |
| `GET` | `/v1/admin/outbox` | Messages the service sent or will send, newest first; bodies hold one-time links |
| `GET` | `/v1/admin/docs-feedback` | "Was this helpful?" answers on the public docs, counted per page (most answered first) |

## Public endpoints

These need no session; they are documented on their own pages.

| Method | Path | Page |
| --- | --- | --- |
| `GET` | `/sdk/v1/agentgate.js`, `/sdk/v1/worker.js` | [Widget configuration](/docs/widget-reference) |
| `POST` | `/v1/browser/challenge`, `/v1/browser/verify`; `GET` `/v1/browser/instrument` | [How the check works](/docs/how-it-works) |
| `POST` | `/v1/siteverify` | [Validate tokens](/docs/siteverify) |
| `GET` | `/v1/gateway/check` | [Gateway overview](/docs/gateway) |
| `POST` | `/agent/submit` | [Sign requests](/docs/web-bot-auth) |
| `GET` | `/.well-known/agentgate-tools.json`, `/v1/agent-tools/{siteKey}` | [Declared tools](/docs/agent-tools) |
| `GET` | `/v1/handoff/{id}` | [Human handoff](/docs/human-handoff) |
| `GET` | `/.well-known/agentgate-keys.json`; `POST` `/v1/receipts/verify` | [Signed receipts](/docs/agent-receipts) |
| `POST` | `/v1/docs/feedback` | the "Was this helpful?" buttons on these pages |
| `GET` | `/healthz`, `/readyz` | liveness and readiness: `/readyz` answers `200` while AgentGate can decide (`ready`, or `degraded` when only optional data such as threat intelligence is missing) and `503` with `Retry-After: 5` when it cannot |
