Skip to main content
If you build your own agent and a person is there to approve it, you do not ask Apostra for access first. Point an MCP client at the endpoint, let it sign in, and the person approves it on a consent screen. No account admin approves your agent, and no allowlist needs your domain or origin on it. If nobody is there to click, skip to Agents with no person present.

The short path

Point an MCP SDK client at https://api.apostra.com/mcp/v3. A client that supports MCP authorization does the rest:
  1. It calls the endpoint without a token and gets 401. The WWW-Authenticate header carries a resource_metadata URL.
  2. It reads that metadata to find Apostra’s authorization server, then reads /.well-known/oauth-authorization-server for the endpoints, including registration_endpoint and client_id_metadata_document_supported: true.
  3. It identifies itself, with a Client ID Metadata Document or by registering.
  4. It opens a browser. The person signs in to Apostra, then approves or denies your agent on the consent screen.
  5. The browser returns to your agent’s redirect URI with an authorization code. The client exchanges it, with a PKCE verifier, for an access token and a refresh token.
Then your agent calls the endpoint with Authorization: Bearer <access token>. Call get_status first to confirm which account the person signed in as. An MCP SDK client does all of this when you give it a redirect URI and a place to store tokens. Read the OAuth section of the SDK you use. The rest of this page is for when you write the flow yourself, or when something fails.

What the person sees

Apostra asks every time an agent authorizes, including when the person is already signed in. The consent screen shows: That screen is the trust anchor. The person, not an admin, decides whether this agent may act as them. Approve issues the code. Deny sends the browser to your redirect URI with error=access_denied and issues nothing. The decision is tied to a one-time session that lasts 10 minutes.

Register your agent

Your agent identifies itself to Apostra in one of two ways. Both create a public client: no client secret, with PKCE doing the proof. Use one or the other.

Client ID Metadata Documents

A Client ID Metadata Document is a JSON file on your own domain. Your client_id is the URL of that file. Nothing is registered ahead of time, and Apostra fetches the file when a person starts authorizing. The MCP specification (2026-07-28) prefers this method, and Apostra advertises support with client_id_metadata_document_supported: true, so clients that prefer it use it without being told.
Send the file’s URL as client_id when you start OAuth. Requirements:
  • The URL uses https, has a path beyond /, and has no fragment, username, password, or . or .. path segments. It must already be in canonical form: lowercase host, no default port.
  • client_id in the file equals the URL exactly.
  • redirect_uris is required and each one follows the redirect URI rules. The redirect_uri you send to /authorize must match one of them exactly, character for character. A metadata document gets no loopback subpath allowance.
  • The file is public. Leave token_endpoint_auth_method out or set it to none. A client_secret makes the document invalid.
  • Serve it with status 200, a JSON content type, and a body of at most 5 KB. Apostra does not follow redirects, connects only to public internet addresses, and gives up after a few seconds.
  • Apostra caches a valid file for as long as your Cache-Control: max-age says, between 1 minute and 24 hours, and 5 minutes when you set none. no-store and no-cache count as the 1-minute minimum. It remembers a failed lookup for about a minute. A change to redirect_uris takes effect when the cached copy expires.

Dynamic client registration

Dynamic client registration is a POST to /auth/register. Use it when you cannot host a public file, for example a desktop tool or a local development agent.
The 201 response carries a generated client_id that starts with mcp_client_, plus the redirect URIs you sent. Do not treat it as a secret. Notes:
  • Always send redirect_uris. A request without them returns a shared, preconfigured client that is not yours.
  • Send up to 25 redirect URIs of at most 2048 characters each. client_name is at most 120 characters. client_uri and logo_uri, if you send them, must be https URLs.
  • A registration you never use to start an authorization is deleted after 24 hours. A registration unused for 90 days is deleted too. Register again when you see redirect_uri is not allowed for a client that used to work.

Redirect URI rules

Both registration methods use the same rules for each redirect URI:
  • Web agents: https on any host.
  • Local tools: http://localhost, http://127.0.0.1 or http://[::1], with or without a port. No other http address is accepted.
  • Desktop and mobile apps: a private-use scheme, such as cursor://anysphere.cursor-mcp/oauth/callback or com.example.agent:/oauth/callback. The scheme is a letter followed by letters, digits, +, - or ., at least two characters long, and something must follow it (a host or a path beyond /). These schemes are refused: http (other than loopback), javascript, data, file, blob, vbscript, about, filesystem, ws, wss and ftp.
  • Never: a fragment (#...) or embedded credentials (user:pass@). A URI is at most 2048 characters.
The redirect_uri you send to /authorize and to /auth/token must match a registered URI exactly. The one exception is a client registered with dynamic registration and a localhost or 127.0.0.1 redirect: the request may use the same host and port with a deeper path (registered http://localhost:8123/cb accepts http://localhost:8123/cb/done), as long as neither has a query string. The port must still match. http://[::1] and custom schemes match exactly.

Tokens and PKCE

PKCE is required, with the S256 method only. Send code_challenge (43 to 128 base64url characters) and code_challenge_method=S256 to /authorize, then the matching code_verifier to /auth/token. plain is refused.
Always send resource. It names the MCP endpoint the token is for, and it must be the endpoint’s exact URL, with no query or fragment. If you leave it out, the grant is for the bare /mcp endpoint. Apostra accepts a /mcp grant at /mcp/v3 too, but at no other endpoint, so send the URL you will call. The browser comes back to your redirect URI with code, state and iss (the issuer, https://api.apostra.com). Check state, and check iss if your OAuth library supports it: Apostra advertises authorization_response_iss_parameter_supported. The code works once and expires after 10 minutes. Exchange it. /auth/token accepts form-encoded or JSON bodies.
The response is a standard token response with token_type: Bearer, scope mcp:access, and: Both tokens are bound to the person, your client, and the exact resource. Refresh before the access token expires:
Each refresh returns a new access token and a new refresh token, with a fresh 30 days. Save the new refresh token and discard the old one. Apostra tolerates concurrent callers that present the same refresh token within 90 seconds of its rotation (they get the same new pair), so two parallel workers do not break the grant. Presenting an already-used refresh token after that is treated as theft, and Apostra ends the whole grant. Use a lock around refresh, or keep one refresher.

What ends a grant

A grant lasts until one of these happens:
  • Your agent calls the revocation endpoint. POST /auth/revoke with token set to the access or refresh token (form-encoded or JSON) ends the whole grant. Send client_id too. A client_id that does not match the token’s client is silently ignored, and the grant stays live. The endpoint returns 200 for tokens it does not recognize, and 400 only when token is missing.
  • The person disconnects your agent from Connected apps in their account menu. Both tokens stop working immediately.
  • The refresh token goes 30 days without use.
  • Apostra refuses a refresh because the account no longer has active access, or because of a platform-wide security sign-out. The refresh returns invalid_grant and your agent must send the person through sign-in again.
A person signing out of Apostra in their browser does not end the grant. The grant belongs to your agent, not to their browser session, so your agent keeps working until one of the events above.

Agents that run in a browser

A browser-based agent can run on any origin. These paths answer a request from any origin with CORS headers and no approval step: /authorize, /auth/register, /auth/token, /auth/revoke, /auth/mcp/authorize, /.well-known/*, /mcp, /mcp/* (including /mcp/v3), and the per-Seller /seller/{id}/mcp endpoints. Some rules follow from that:
  • Bearer token only. Apostra returns Access-Control-Allow-Origin but not Access-Control-Allow-Credentials. Send Authorization: Bearer <access token> and do not set credentials: "include". Do not rely on Apostra cookies. A page with no token gets nothing, which is why no origin needs approval.
  • Everything else stays closed. These paths do not open the rest of the REST API, such as /api/v3/*, to your origin. An MCP access token is rejected on the HTTP API anyway.
  • null origins are refused. A sandboxed srcdoc frame or a page opened from a file: URL sends Origin: null, and Apostra does not answer it with CORS headers. Serve the agent from a real https origin.
  • Do not put an API key in browser code. Use the OAuth flow above.
  • Redirect URIs still matter. An agent on another origin can call these paths, but it only receives an authorization code at a redirect URI its client declared, and only after the person approves it.

Agents with no person present

A background agent, a scheduled job, or a deployed backend has nobody to approve a consent screen. Do not use this flow. Choose one of these:

Troubleshooting

These are the messages your agent can receive. Errors from /authorize, /auth/token, /auth/revoke and /auth/register are OAuth JSON bodies with error and error_description. If sign-in still fails after you check this table, include the exact error and error_description, the client_id, and the time of the failure when you contact support.