HTTP API

Hosted JSON lives under /api. MCP is POST /mcp (see MCP tools). OAuth is /oauth/* on botpasses.com, which is its own authorization server. Local npx vault serve is a smaller loopback API, listed at the end.

Operator JSON never includes secret values after submit. Model tokens cannot resolve a value. Trusted tokens cannot call MCP. JSON bodies must be sent as Content-Type: application/json (anything else is 415); bodies over 128 KiB are 413.

Who may call what

WhoCredentialMay call
OperatorHttpOnly session cookie after email code and authenticator. Send X-CSRF-Token on POST and DELETE/api/* operator routes, Inbox, Access, approve, revoke
Model (agent)OAuth access JWT (aud is https://botpasses.com/mcp; its org_id is the organisation the operator consented in, and the operator must still be a member) or a console-issued avm_... bearerPOST /mcp, GET /mcp, GET /mcp/tools, POST /api/grants/request
Trusted runtimeavt_... bearerPOST /runtime/resolve only

A session that has not completed the authenticator step gets 403 { "error": "mfa_required", "enroll_url": "/enroll-totp" } when no authenticator is enrolled, or { "error": "mfa_required", "verify_url": "/verify-totp" } when one is and this session has not passed it yet. Ids from another organisation are 404. A foreign browser Origin on /api, /console, /collect, and auth is 403 with no CORS allow header. /mcp, the well-known documents, and /oauth reflect the caller’s Origin so browser-based MCP hosts can connect. Public site GET/HEAD/OPTIONS (pages, /og.png, /logo.png, /favicon.ico) also reflect a foreign Origin so a composer or unfurler can load the card and icon. Invalid bearer on public HTML is ignored (the page loads); the same bearer on /api or POST /mcp is 401.

Health and discovery

MethodPathNotes
GET/health{ "ok": true, "product": "botpasses" }. No key fingerprint
GET/ready200 or 503 from the database ping
GET/.well-known/oauth-protected-resourceresource is https://botpasses.com/mcp. Same document at the /mcp path-aware URLs
GET/.well-known/oauth-authorization-serverRFC 8414. response_types_supported is ["code"], PKCE S256 only. Also at /.well-known/openid-configuration
GET/robots.txtProduction disallows console, auth, collect, api, mcp, oauth. Lists Sitemap: https://botpasses.com/sitemap.xml. Comments point at /llms.txt and /llms-full.txt
GET/sitemap.xmlPublic urlset of every canonical page
GET/llms.txt, /llms-full.txtMachine-readable site index and full docs for AI crawlers
GET/.well-known/security.txt, /security.txtVulnerability contact. Same bytes at both paths. See Security disclosure
GET/favicon.ico, /apple-touch-icon.png, /favicon.svg, /mark.svg, /mark-on-dark.svg, /og.png, /og-square.png, /logo.png, /android-chrome-192x192.png, /android-chrome-512x512.png, /site.webmanifestBrowser, iOS, Android, and social-card URLs. /mark.svg is the navy ticket. /mark-on-dark.svg is the white ticket. /favicon.svg is the simplified 16 px tile. /logo.png is the square favicon raster; /og.png is the 1200x630 card

Sign-in and account

MethodPathBody
GET/sign-in, /sign-upHTML. A ready session redirects to /console; a verified email without an authenticator redirects to /enroll-totp; an enrolled account that has not passed the authenticator step this session redirects to /verify-totp
GET/enroll-totpHTML. Session required. Shows backup codes once after confirm. A ready session that started a re-enroll from the console lands here and sees the pending QR
GET/verify-totpHTML. The sign-in authenticator step: one field for an authenticator code or a backup code
POST/api/auth/otp/send{ "email" }. Returns { "ok": true } for known and unknown emails. Sending again replaces the previous code; five sends per email in 15 minutes
POST/api/auth/otp/verify{ "email", "otp" }. Sets a pending session cookie. Returns { "ok", "enroll", "verify" }: enroll means go to /enroll-totp, verify means go to /verify-totp. A 401 carries attempts_remaining
POST/api/auth/totp/startBegin authenticator enrollment. Returns otpauth_url and a locally rendered qr_svg. Re-enrolling needs a ready session and { "current_code" }
GET/api/auth/totp/pendingThe enrollment in flight (otpauth_url, qr_svg) while it is live, 10 minutes from start; 404 no_pending_enrollment otherwise. The enroll page reads this before calling start, so a reload or a re-enroll shows the secret that confirm will check
POST/api/auth/totp/confirm{ "code" }. Marks the session ready and returns backup codes once
POST/api/auth/totp/verify{ "code" }. The sign-in authenticator step: an authenticator code or an unused backup code. Marks the session ready. Ten failures lock the account for 15 minutes (429 with retry_after)
GET/api/auth/me{ "email", "totp_enabled", "backup_codes_remaining", "created_at" }
POST/api/auth/backup-codes/regenerate{ "code" }. Replaces every unused backup code and returns the new set once
POST/api/auth/logoutClears the session cookies and ends the OAuth sign-in session. Needs X-CSRF-Token
POST/api/orgs{ "name" } (1 to 80 characters). Creates an organisation for the signed-in user after the authenticator step; up to ten owned organisations per account (402 plan_limit)
DELETE/api/orgs{ "confirm_name" }. Deletes the organisation and everything in it

Email codes are 8 digits, valid 10 minutes, single use; five wrong attempts end the challenge. Authenticator codes are RFC 6238 (SHA-1, 6 digits) with replay protection. Authenticator secrets are encrypted under a key that vault kek-rotate re-wraps.

Items and collect

MethodPathNotes
GET/api/itemsNames, last-4, hosts, inject. No values. Omit environment for every environment this plane serves. On staging, environment=production is 404
POST/api/itemsname, value, environment, kind (secret, client_secret, login), allowed_hosts, inject (bearer, client_credentials, basic, header:Name), optional username, folder_name. A client_secret needs the client ID in username
POST/api/items/:idEdit name, kind, environment, hosts, client ID, inject. value optional; blank keeps the current secret
POST/api/items/:id/metaSame as POST /api/items/:id
POST/api/items/:id/rotate{ "value" }. Replaces the value only
DELETE/api/items/:idRemoves the credential and its approvals
POST/api/folders{ "environment", "name" }
GET/api/need-items/:idOperator only. Metadata of a pending collect request, including recipe when the host matches a known provider. Unauthenticated, or a request from another organization, is 404
POST/api/need-items/:id/fulfill{ "value", "name?", "allowed_hosts?", "inject?", "kind?", "username?", "always_allow?" }. Stores the value from the collect page and activates the requesting agent’s approval. always_allow: true writes a standing approval for that agent in the same transaction. A connect request (an agent needs a provider account) is 409: it is answered from the Inbox card, not typed in
POST/api/need-items/:id/denyDenies a pending request from the Inbox
GET/collect/:idHTML shell for the collect page. Details load only for a signed-in operator
POST/api/integrations/:provider/start:provider is spotify, github, google, slack, or stripe (unknown is 404). { "item_name", "environment?", "client_id?", "redirect_uri?", "agent_client_id?", "need_id?" }. Returns authorize_url, redirect_uri, and provider for a user connect. agent_client_id names one agent that gets a standing approval on the source credential and the refresh item after connect; need_id closes the Inbox request the connect answers
GET/connect/callbackOperator cookie, authenticator step passed. Exchanges the code, stores the refresh token as <ITEM>_REFRESH, redirects to /console#vault?connected=<provider> or ?connect_error=<provider>&reason=<code> (state_expired, provider_denied, exchange_failed, or no_refresh_token). The provider id is in the sealed state. /integrations/:provider/callback is the expand-only alias for apps that already registered that URI

Names match [A-Z][A-Z0-9_]{0,127}. A duplicate name is 409. An empty value is 400.

Clients, grants, inbox

MethodPathNotes
POST/api/clients/modelIssues an avm_... once, plus mcp_url. The token is not listed later
POST/api/clients/trustedIssues an avt_... once for /runtime/resolve
POST/api/clients/:id/rotateNew token once. The old hash stops working
POST/api/clients/:id/revokeLater bearer calls are 401; OAuth tokens are denylisted; approvals revoked. Every OAuth grant this organisation’s members gave for the client is destroyed; the same person’s grants for it in other organisations survive
POST/api/grants/requestModel or operator. { "item_name", "task_description?" }. Optional host, method, path. Returns the covering grant, or a pending grant when standing scope does not admit the call. 30 per organisation per hour
POST/api/grants/:id/approve{ "policy", "confirm_name?" }. folder_standing is owner-only and needs confirm_name
POST/api/grants/:id/revokeStatus becomes revoked. The row stays listed
POST/api/grants/approve-by-code{ "code" }, 8 digits. Reuse is 409, expired is 410
GET, POST/approve?token=...Email approval link. Operator session required; the token is single use
GET/api/inboxPending approvals and requests. Each request has a kind: secret (typed on the collect page) or connect (an agent needs a provider account for a stored client secret; the card’s Connect button opens the connect dialog with that agent pre-selected)
GET/api/auditActions, names, and host on auto_approved. Optional client_id and item_name filters. No values

Policies: prompt (one successful call, then consumed; a failed call keeps it usable), session (8 hours), item_standing, folder_standing.

Access

MethodPathNotes
GET/api/accessLive snapshot: operators, clients, grants, sessions, with created, first and last access, fetched names, and bearer last-4. Grants include item_id to distinguish same-name credentials in different environments. No ledger array, no tokens
GET/api/access/eventsIssuance ledger, newest first, limit 200. jti is hashed
POST/api/sessions/:id/revokeOnly sessions acting in this organisation are listed and revocable. 400 cannot_revoke_current for the session making the call
POST/api/sessions/revoke-othersEnds every other operator session

Trusted resolve

POST /runtime/resolve with an avt_... bearer and { "item_name": "STRIPE_KEY" } returns the plaintext to the trusted process. This is the hosted equivalent of vault run, and it is the inject. A model token is 403.

MCP

POST /mcp JSON-RPC with http_request, find_items, list_items, request_grant, list_grants. There is no get_secret. Unauthenticated POST /mcp (including initialize) and GET /mcp (SSE keepalive) are 401 with WWW-Authenticate resource_metadata and scope="mcp", so Streamable HTTP hosts including Grok Bot can start OAuth. GET /mcp/tools returns the tool list. See MCP tools.

OAuth (botpasses.com is the authorization server)

PKCE S256 is required. Clients are public (token_endpoint_auth_method none; a registration that asks for a client secret is refused) and use the code response type. Access tokens are RS256 JWTs with audience https://botpasses.com/mcp, a 600 second lifetime, and an org_id claim naming the organisation the operator consented in. Refresh tokens rotate.

PathRole
/oauth/authorizeAuthorization code. The operator signs in and consents in the browser; the consent is bound to the organisation their session is acting in
/oauth/tokenCode or refresh token to JWT. A refresh for an agent that was revoked in the console is invalid_grant; only a new consent brings the agent back
/oauth/jwksRS256 public keys. Two are listed while a key rotation is in progress; new tokens are signed with the first
/oauth/registerDynamic client registration. redirect_uris may be https, RFC 8252 loopback http (127.0.0.1, [::1], localhost), or a desktop scheme such as cursor:// or grokbot://. javascript:, data:, and file: are rejected. 20 per IP per hour
/oauth/device/authRFC 8628 device code. The operator finishes at /device (10 code attempts per 15 minutes per IP)
/oauth/revokeRFC 7009. Revoking a refresh token also revokes every access token issued with it; a JWT access token is revoked by its jti. Marked in the Access ledger
/consent, /deviceHTML pages for the two flows above. The consent page’s script posts { "uid", "decision" } with X-CSRF-Token and Accept: application/json and gets 200 { "location" } to navigate to; a caller with the CSRF header and no such Accept gets a 303 to the same resume URL. A form submit without the header (scripts off) is 403

Local vault serve (loopback)

npx vault serve listens on 127.0.0.1:8788 and prints two loopback bearers. Send the operator bearer as Authorization on /api/* (the console stores it) and the model bearer on POST /mcp (vault mcp --remote sends it). Each is refused on the other surface, so an MCP client cannot approve its own requests. The server answers only to a loopback Host.

Each MCP client gets its own session: initialize answers with an Mcp-Session-Id header, and every later frame must send it back (400 without it, 404 once the server no longer holds it, after 8 idle hours, so the client initializes again). Two clients on one server keep separate agent ids and separate approvals. vault mcp --remote handles the header for stdio clients.

PathRole
GET /Local operator console
GET /health{ ok, product }
GET /api/secretsNames, last-4, hosts, inject mode, username as { secrets }
GET /api/itemsThe same list as { items }, the hosted key
POST /api/secretsStore { name, value, allowed_hosts?, inject?, username? }, answered as { secret }. username is the HTTP Basic user, OAuth client id, or AWS access key id
POST /api/itemsThe same store, answered as { item }
GET /api/grantsGrant metadata
POST /api/grants/requestRequest a pending grant
POST /api/grantsApprove (scope is once or session; tool_id is http_request for agent calls)
POST /api/grants/:id/revokeRevoke
GET /api/auditEvents, no values
POST /mcpLocal MCP JSON-RPC, the same six tools as hosted

There is no OAuth, provider connect, item edit or rotate route, or Access panel on the local plane; /api/items only lists and stores.

Status codes

StatusWhen
401Missing or invalid session or bearer
403CSRF, mfa_required, wrong channel (a model token on an operator route), foreign Origin on /api, /console, /collect, and auth
404Unknown id, or an id from another organisation
409Duplicate credential name, reused approval code
410Expired approval
413Body over 128 KiB
415A JSON route was sent a body that is not application/json
429Rate limit. See Rate limits