Authentication
Apostra uses WorkOS as its credential control plane. Interactive connectors use OAuth, AI agents can use Agent Registration, simple customer automation uses user API keys, and backend integrations that require OAuth client credentials can use M2M applications.Browser session host boundaries
Browser sign-in cookies are scoped to the API hostname that issues them or to its configured parent domain. Opening Apostra through a separately hosted UI/API gateway pair may require a new sign-in; an existing session on another hostname is not copied automatically. For browser requests that change data, the API accepts only an exact configured UI origin. Supplying a differentOrigin header does not authorize that origin or make
its cookies valid. Authentication cookies stay host-only when the API hostname does
not match a configured parent domain.
A browser sign-in session works in the Apostra web app and on the HTTP API
(/api/*). Every MCP protocol endpoint, including /mcp, /mcp/v3, the v2
persona endpoints and a Seller’s /seller/{platformId}/mcp, refuses it with
401, whether it arrives as a cookie, an Authorization header or an API-key
header. The one exception is the web app’s own tool calls to /mcp/<toolName>,
which accept the session only as the sign-in cookie the web app sets and refuse
it as a header. Connect an MCP client with OAuth or an API key instead.
The featureFlags.productBrandCutover and
featureFlags.productBrandWordmarkUrl values returned by /auth/me are presentation
metadata for the first-party UI. They do not grant access or change API entitlements,
and custom clients should not depend on them.
Buyer signup and organization invitations
You can sign up with a personal email address to build agents. You can also use one to advertise once advertiser sign-up is open to you (it is currently invite-only). Selling on Apostra needs your company email address. A personal email address never identifies, joins, or claims an existing organization.Password requirements
Email-and-password signup and password reset use the same WorkOS-managed policy. Passwords must contain at least 10 characters, be strong and difficult to guess, and must not appear in a known data breach. There is no requirement to include a particular mix of uppercase letters, lowercase letters, numbers, or symbols, so long passphrases and password-manager-generated passwords are supported. The form shows the requirements while the password is entered. Its strength indicator is guidance; WorkOS performs the final strength and breach checks when the form is submitted. OAuth and SSO sign-in are unaffected. New standalone buyer organizations are admitted in stages. When buyer admission is closed, selecting Advertiser during signup opens the buyer preview form. It asks whether the buyer wants to manage campaigns directly in Apostra, connect their own AI agent, or use Apostra through their preferred AI chat. The own-agent path asks for the agent name; the AI-chat path offers Claude, ChatGPT, Copilot, or another chat the buyer names. Buyers can also say that they are still undecided. The form then asks which countries and channels they plan to use and when they want to start. After the form is accepted, Apostra sends a confirmation to the submitted work email address. This receipt confirms that the details were received; it does not mean that an account has been created or access has been approved. Applicants do not need to submit the form again while the team reviews their request. The live pilot countries are Australia, Belgium, France, Luxembourg, the Netherlands, the United Kingdom, and the United States. A buyer who selects Social does not need to select a pilot country. Buyers who select either Social or a pilot country are sent for general review at any timing. The way they want to manage campaigns does not change that route. Other buyers stay on the launch list under the exact countries they selected. Those country-level signals guide where Apostra expands next; selecting a country does not claim that a pilot is already open there. When a work-email domain already belongs to a known organization, Apostra associates the preview request with that organization and pre-fills its canonical name. This does not grant membership in an existing account: an invitation or the account’s normal domain-approval policy is still required for access. An invitation belongs to the mailbox it was sent to, so accepting one requires a verified email address. For a password account, Apostra verifies the address with a one-time code it emails to that address. Until you enter the code, signing up creates your identity but grants no access to the inviting organization, and a password sign-in is refused and sends a fresh code instead of signing you in. The signup lookup also does not reveal which organizations have invited an address. After you verify and sign in, Apostra lists the invitations waiting for you and you accept them there. Signing up with Google relies on the address Google has verified, so a matching invitation is accepted immediately. Apostra never marks an address verified because a provider says so: if the identity service reports the address as unverified, the sign-in is refused with a 403 (requiresVerification: true) and the same one-time
code is emailed. Enter it, then sign in again. This applies to any sign-in that
completes through the authorization-code exchange, including a single sign-on code
that reaches it. Single sign-on sign-ins that complete at the SSO callback keep
trusting your organization’s connection, so a matching invitation is accepted
immediately.
For a reviewed beta admission, submitting the preview request happens before Terms
of Service acceptance. After Apostra approves the organization, its selected
administrator receives a Your Apostra account is ready setup email. The
administrator uses that invitation to create an identity or sign in and join the
organization. If the current Terms of Service are still outstanding, a direct
organization administrator reviews and accepts them in Apostra. An existing seller account or
storefront for the same organization remains a separate workspace and does not itself
grant buyer access. When that seller account is dead (no live storefront, administrator, or
commercial or billing activity), admission retires it and creates a standalone buyer account
for the administrator. When it is live, admission creates no buyer account on top of it: the
seller account’s own administrator adds the buyer, or the person requests access.
When someone signs in and their existing account is not enabled for Apostra, but an
invitation to the account they were approved for is waiting, Apostra does not start
a session. It tells them they have a pending invitation and to open the invitation
email, or ask their organization administrator to resend it. Request access appears
only to a person with neither an invitation nor an enabled account. If an approved
person submits the request-access form again, Apostra replies that the request was
already reviewed and points them to the invitation email, or to the account selector
after they sign in.
For a reviewed first Buyer administrator invitation, Apostra may prepare a
Commercial Offer from a published IU plan with a specific discount or custom IU
package price before sending the invitation email. The offer is pending when
the email arrives. Verify the exact invited email address and join the Buyer
Account, then review and accept the Commercial Offer separately. Joining the
account does not accept the offer’s price or subscription terms. An invitation
without a custom Offer follows the usual account setup path.
When admission is open, the same signup creates the buyer organization and then
offers three starting points: work in Apostra, build with the API, or connect an
assistant. Choosing one does not remove access to the other two.
The optional marketing-email choice is separate from account access and transactional
account messages. Signup records both opt-in and opt-out decisions, including the exact
notice shown and when the decision was submitted, so later communication workflows can
read one durable permission record.
Recovering an interrupted signup
Sometimes WorkOS finishes verifying an identity before Apostra finishes creating the corresponding account. Signing in with the existing password now returns the user to the normal signup form to finish that missing account setup. This reuses the exact verified WorkOS identity and does not replace its password. If the recovery expires or the WorkOS identity changes, the user must sign in again. Custom auth clients should follow the returnedredirectUrl when
POST /auth/password returns this response:
GET /auth/verified-signup-data/:token, passes the token as
verifiedSignupToken when checking email availability and creating the account, and
stops using it once account creation begins. Account creation atomically consumes the
token, so a failed attempt must authenticate again for a fresh continuation. A direct
password-signup request without this authenticated continuation cannot adopt an
existing verified WorkOS identity.
Email-code sign-in is retired. POST /auth/magic-code and POST /auth/verify-code
return 410 with error code GONE; sign in with POST /auth/password, or with
Google, Microsoft or single sign-on.
Signup does not reveal existing accounts
POST /auth/check-email-availability and POST /auth/sign-up are public, so they do
not say whether an email address already has an account or whether that account is
verified. For a caller that has not proven control of the address, an email with an
account gets the same routing answer as an email without one, and a password signup for
it gets the same “check your email to verify your account” response a new signup gets.
That response has the same status and an empty data object for every password signup,
so it does not carry the new account’s customerId, landing path or plan-selection
fields. A client that needs them reads them after the address is verified. The existing
account is not changed and no email is sent to it. A client that wants to
send a returning person to sign-in does so from its own sign-in link, not from the
signup responses. A caller that holds a Google, Microsoft, single sign-on or verified
signup token for the exact address has proven control and still receives
EMAIL_EXISTS when that address belongs to an account with access.
Developer signup
Anyone creating a new organization can choose theDEVELOPER signup experience to
build agents. A successful POST /auth/check-email-availability response for a new
organization includes developerSignupAllowed: true; that means the caller can offer
Developer signup for that organization. It does not grant access to an existing
organization, bypass invitation or membership checks, or establish any paid product.
Link to /signup?intent=developer to preselect Build agents (Developer) in the
web signup page after that email check confirms eligibility. The browser submits
signupExperience: "DEVELOPER" and retains accountType: "BUYER" only as a legacy
provisioning compatibility projection; it does not request Buyer access. After signup,
the customer has a new organization holding one Developer account, is that
organization’s Admin, and lands on the Developer account’s Agents page.
Custom clients may provide signup_experience=DEVELOPER to GET /auth/url or
GET /auth/sso/url. The server accepts it without a feature-flag enrollment and
preserves it through OAuth or SSO in server-authoritative state. After signup, the
customer has a new organization holding one Developer account, is that
organization’s Admin, and is sent to the Developer account’s Agents page.
If the organization cannot be set up, the signup returns a retryable
SERVICE_UNAVAILABLE error instead of reporting success. Signing up again
with the same details finishes the setup.
Other signup experiences
ADVERTISER, ADVERTISER_PORTFOLIO, and MEDIA_COMPANY are the other three
signup experiences. Each one sets what a new account starts as: Advertiser
creates a Buyer account with one sandbox advertiser, Advertiser Portfolio a
Buyer account with no advertiser, and Media Company a Seller account with its
Storefront already created. They work with email and password, OAuth, and SSO.
See Account Access & Signup.
They are available only to accounts enrolled in the chooser rollout. An
unenrolled request to GET /auth/url or GET /auth/sso/url, or to sign up with
one of these values, receives HTTP 403. The value sets only the new account’s
starting shape: it does not grant access, admission, organization membership,
entitlements, billing, or navigation authority over an existing organization.
It is never inferred from account type, email, domain, provider, or other
caller data.
The server preserves a submitted routing intent through the OAuth or SSO round trip
only in server-authoritative state; clients must treat OAuth state as opaque and
must not attempt to recreate or alter the routing state. After successful
authentication, the server may carry the verified value into the corresponding signup
request and response.
If this state cannot be stored while starting OAuth or SSO, or a callback declares the
routing intent but its state is unavailable, expired, malformed, or for the wrong flow,
the affected endpoint returns HTTP 503 with SIGNUP_EXPERIENCE_STATE_UNAVAILABLE and
retryable: true. Restart sign-in to obtain a new state; do not retry the callback with
a reconstructed token. An OAuth or SSO flow with no routing-intent envelope keeps its
existing legacy behavior.
Seller Account signup and IU plans
Public Seller Account creation is independent from the Organization IU Rate Card rollout. A Seller Account prospect can accept Apostra’s standard Terms of Service and create an account even when IU plan selection is unavailable. That acceptance creates the account’s standard agreement; it does not publish or accept an IU Rate Card, enroll the organization in a paid IU plan, grant setup credit, or enable IU charging. When the IU rollout is closed, signup does not show pricing previews or signup-code entry and rejects clients that try to submit IU plan or signup-code data. Custom signup clients can distinguish the states in the email-availability response:storefrontAccountSignupAllowed controls account creation and
storefrontIuSignupAllowed controls the IU plan-selection step. The legacy
storefrontSignupAllowed field retains its IU-step meaning during the transition.
An organization invitation is a separate, org-scoped path. Its recipient can create
an account and join the named organization even while public buyer signup is
waitlisted. The invited organization must itself be admitted to the current rollout.
The invitation does not create a second buyer organization, grant access to the rest of
Apostra by itself, or replace any Terms of Service, account, or spend requirements
on the invited organization.
OAuth (Recommended for AI Agents)
When connecting through Claude Connectors or ChatGPT MCP Connectors, authentication is handled automatically via OAuth. Users sign in with their Apostra credentials, approve or deny the connector on Apostra’s consent screen, and on approval the agent receives a secure token. See Approving an MCP client.- No API keys to create or manage
- Tokens are scoped to the authenticated user
- Works with Claude.ai (Team/Enterprise), Claude Desktop, and ChatGPT
MCP protocol compatibility
The buyer and storefront MCP endpoints support both stateless MCP 2026-07-28 requests and the existing initialize/session lifecycle. MCP clients negotiate the protocol automatically; users do not enable a setting or repeat OAuth authorization to opt into that protocol lifecycle. The one-time reauthorization required for older, unbound OAuth credentials is described below. Specialized Apostra, creative, admin, TARS, and dynamic platform-storefront MCP endpoints remain on the session lifecycle during the staged rollout.MCP OAuth resource binding
Each MCP endpoint is a distinct OAuth protected resource. The server advertises the exact endpoint URI in its protected-resource metadata, and compatible clients carry that sameresource value through authorization, token exchange, and
refresh. Follow the resource_metadata URL in the server’s
WWW-Authenticate challenge instead of constructing the discovery URL.
Apostra issues dedicated mcp_at_ access credentials and rotating mcp_rt_
refresh credentials bound to the approving user, client, and exact resource URI.
A credential issued for /mcp/v3 is rejected on another MCP endpoint and on
REST routes.
This connection is independent of the browser session used to originally
authorize it, matching standard OAuth connected-app behavior: signing out of
Apostra in a browser, or that browser session later expiring, does not
disconnect Claude, ChatGPT, or any other MCP client. The connection keeps
refreshing itself automatically. It stops only when the account loses active
access to Apostra, or a platform-wide security sign-out is issued — never
from browser session inactivity alone.
Connectors authorized before resource-bound credentials were introduced must be
authorized once more. Apostra rejects their former browser refresh
credentials at the MCP token endpoint rather than converting them into an
unscoped MCP credential.
Managing connected apps
The Connected apps page in your account settings lists every AI host (Claude, ChatGPT, or any MCP client) that has been granted an active connection to your Apostra account. For each connection you can see the app name, when it was first connected, and when it last refreshed its credentials. To view and manage your connected apps:- Open the account menu in the top-right corner of Apostra UI.
- Select Connected apps.
- Find the app you want to remove and click Disconnect.
- Confirm the disconnection in the dialog.
API Keys
For CLI tools, scripts, shared automation, and direct REST API integrations, use a user API key unless the integration specifically requires OAuth client credentials.Getting Your API Key
- Visit app.apostra.com/user-api-keys
- Sign up or log into your Apostra account
- Create a key while signed in as the user the integration should act as
- Copy the key when Apostra displays it
User API Keys
User API keys act as the person who created them. WorkOS creates, stores, masks, and revokes the keys; Apostra resolves the owner and their active organization membership on every request. The key has no separate read/write/admin role: its effective access is the user’s current account and resource access. A key cannot grant access its owner does not have. The key is also bound to the organization selected when it is created. Each buyer, seller, parent, and child account has its own WorkOS organization, so a key cannot silently cross into a linked account where the same person is also a member. Create a separate key in that account’s organization when that boundary is intentional. Removing the user from an account removes the key’s access to that account. Changing the user’s role or resource assignments changes what the key can reach without rotating it. For a headless integration that should not depend on an employee, create a dedicated service user, give that user exactly the required account and resource access, and create the key while signed in as that user.Authorization
Every new user API key carries one WorkOS permission:
There is no permission selector when creating a key.
interchange:access does not
grant a role by itself; it tells Apostra to use the owner’s live WorkOS-backed
account and advertiser assignments. It applies across buyer and seller functionality
because V3 is one Apostra API.
Keys created with the earlier interchange:read, interchange:write,
interchange:admin, buyer:read, buyer:write, or buyer:admin names continue to
work with their existing hierarchical access during rotation. account:admin remains
unavailable to user-owned keys.
Existing account-owned keys
Existing WorkOS organization-owned keys continue to authenticate during migration. Organization administrators can inspect and revoke them under Account API keys, but new integrations should use a user-owned key so membership and resource changes apply automatically. Replacing an existing organization key is an explicit rotation; Apostra does not silently change or revoke it.Embedded key management widget
Apostra API Keys settings page uses an Apostra creation form and embeds the WorkOS user API Keys widget for listing and revocation. The page displays a key in full only when it is created; later views show a masked value. Members manage their own keys. Organization administrators can view and revoke user-owned keys across the organization, and also see the existing Account API keys migration section. Customers that still have ascope3_ API key see a Legacy API keys
section below the WorkOS widget. Account administrators see both user keys and
older customer-scoped keys so working account-level legacy credentials remain visible during
migration; other users see only their own keys. Settings no longer offers creation,
editing, or secret reveal for those keys. Create a user API key, update and
verify the integration that uses the legacy key, and then revoke it.
Existing legacy keys continue to authenticate until they expire or are revoked.
The embedded user-key surface gets a short-lived, user-bound widget session token
from Apostra. Never expose the WorkOS server API key to a browser.
The organization-key lifecycle endpoints remain available for migration tooling and
require an interactive organization-admin session; an API key or M2M token cannot
create another credential. POST /api/v2/org-api-keys can create only a
buyer:read key. The organization-key widget endpoint is no longer available.
token bound to that user and the active organization.
Hide fee breakdowns from a key
If your organization charges clients an operator fee, an organization administrator can mark a user API key to hide fee breakdowns. A marked key sees only the total cost of every buy it reads: gross budgets and gross spend, without the fee split inbudget_breakdown or the seller’s package bid prices. Use it for a key you give to a
partner, such as a measurement provider, who should not see your fees.
The mark applies to every buy the key reads, including a buy that has no
operator fee. A marked key therefore also stops receiving budget_breakdown,
the fee split Apostra shows on buys with locked fee terms. Unmarked keys, which
is every key until you mark one, are not affected. The mark has no other
effect on the key and does not change what it may do.
Mark or unmark a key with the WorkOS key ID shown in the key list. The request
needs an interactive organization-administrator session. An API key, an M2M
token, or a delegated support session cannot set it.
{"hidesFeeBreakdown":false} to remove the mark. The response echoes the
key ID and its current hidesFeeBreakdown. The list returns the marked keys
as keys: [{ "keyId": "…", "hidesFeeBreakdown": true }].
- The mark belongs to your organization. It applies only while the key authenticates into your organization.
- Apostra does not check that the ID names a key of yours. A mistyped ID marks nothing, so read the list back to confirm.
- A change can take effect on the key’s next request.
- If Apostra cannot read the mark while serving a request, the key sees total cost for that request rather than the breakdown.
- Marking applies to user API keys only. Organization-owned keys and M2M credentials are not covered.
Using Your API Key
Pass the key as a Bearer token in theAuthorization header:
- curl
- TypeScript
- Python
Agent Registration
An agent that has no credential starts withhttps://api.apostra.com/auth.md. The guide
leads with Apostra’s zero-touch activation contract, then includes the
WorkOS-generated anonymous registration, optional user claim ceremony, and assertion
exchange instructions. Credential-free agents should use this path rather than the
interactive OAuth authorization-code flow.
Apostra can close Agent Registration. While it is closed, the guide and the
contract return 503, and every call with an Agent Registration token returns
503 AGENT_REGISTRATION_UNAVAILABLE. See
When registration is closed.
On MCP, the credential works only on /mcp/v3. Every other MCP endpoint,
including /mcp, refuses it with 401. It also works on the HTTP API routes
its activation state allows.
The API origin links to that guide so an agent does not need to guess the path. A
machine-readable contract is also available at
/.well-known/buyer-activation.json; it publishes the WorkOS registration and token
endpoints, the authenticated /api/v2/buyer/activation probe, the trial lifetime,
the exact /api/v2/buyer/activation/provider-binding retention endpoint, and its
canonical interchange:read credential scope, grants, and prohibitions.
After exchanging the WorkOS assertion, call the activation probe with the returned
bearer token. The response is stable across retries and contains no secret:
The WorkOS token carries the environment’s untrusted interchange:read permission. Older buyer:read tokens remain accepted as a compatibility alias while credentials rotate.
The allowance.grants below are effective sandbox capabilities Apostra derives
from that verified Agent Registration credential and its activation state; they are
not additional WorkOS permissions that an operator must provision.
GET|HEAD /api/v2/buyer/activation and
GET|HEAD /api/v2/buyer/advertisers[/:id]. One exact zero-spend mutation is also
available: POST /api/v2/buyer/activation/provider-binding maps the first provider
account already connected inside that sandbox and atomically retains the registration
as BOUND_FREE. Apostra authorizes this lifecycle transition from the verified
Agent Registration principal and UNBOUND_FREE state; it does not require a separate
WorkOS permission. The allowance otherwise lasts 30 days and has a zero-dollar spend cap.
Every other mutation, nested advertiser route, MCP call, and money-moving route is
denied. Cross-customer, write, and other disallowed-route attempts return distinct
model-actionable error codes, including a separate denial for spend-changing media-buy
operations: UNBOUND_FREE_FOREIGN_TENANCY_PROHIBITED,
UNBOUND_FREE_WRITE_PROHIBITED, and UNBOUND_FREE_SPEND_PROHIBITED.
BOUND_FREE cancels anonymous cleanup but keeps the zero-spend boundary and exactly
one retained provider account. WorkOS service_auth claim remains optional for
recovery or named administration; it does not activate the free account. Customer
data, paid capabilities, and spend still require customer authorization, Terms of
Service, account grants, commercial entitlements, governance, and spend controls.
After claim, state: "customer_bound" says only that the Agent Registration credential
is attached to the customer identity. A claimed
agent can attach only to an existing active Buyer Account where the claiming user is an
active member; claim does not create or admit an Account. Human invitation and agent
claim are therefore two identity ceremonies over the same Account, and both continue
through the same Buyer Setup projection. That projection derives demo access, real
non-spend work, seller-direct spend, and consolidated-billing spend from Organization
proof, Terms, grants, standing, billing details, and the exact billing route; it does not
read an Account lifecycle mode.
M2M Applications
Use an M2M application when a deployed customer backend requires the OAuth 2.0client_credentials flow and short-lived JWT access tokens. An organization admin can
manage applications through /api/v2/m2m-applications; creation returns the client
secret once, together with the WorkOS token endpoint and Apostra resource
indicator. The token endpoint is hosted on Apostra’s AuthKit domain for the
environment. Store the secret in a secrets manager.
An M2M access token is accepted on the HTTP API (/api/*) only. Every MCP
endpoint, including /mcp, /mcp/v3 and a Seller’s /seller/{platformId}/mcp,
refuses it with 401; connect an MCP client with OAuth or an API key instead.
M2M applications support overlapping client secrets for rotation. Revoke the old
secret only after the new secret has successfully obtained and used an access token.
Deleting the application immediately disables its local Apostra customer association;
already-issued access tokens are rejected locally even if they have not expired.
Identity-only buyer-agent M2M credentials are a separate, deliberately narrow
workflow. A directly authenticated WorkOS organization administrator issues
them from Buyer-agent credentials. They
authenticate only as an opaque buyer-agent identity and have no action
permissions. They are not listed or mutable through /m2m-applications; use
the buyer-agent issue, replacement, reconciliation, and revocation endpoints
instead.
Legacy scope3_ Keys
Existing scope3_ keys remain accepted during the credential migration and can still
be revoked. They are compatibility credentials, not the target for new integrations.
Do not replace a working key until its WorkOS replacement has made a successful test
call. Apostra will publish any retirement date only after owners, usage telemetry,
customer communication, and rollback readiness meet the retirement gate.
MCP Authentication
For AI agent integrations using the Model Context Protocol:An MCP agent cannot retrieve an existing API key or M2M client secret, and it
must not mint or carry a new long-lived secret in chat. For a user API key,
the agent directs the signed-in user to Settings → API Access. M2M does
not have a management widget yet; a human admin or their developer uses the
documented
/api/v2/m2m-applications lifecycle with an interactive human
session. For an identity-only buyer-agent credential, the human instead
follows the dedicated buyer-agent credential
workflow. The human copies the one-time
secret directly into the workload’s secret manager. The workload then uses the
API key, or exchanges an M2M client secret for short-lived access tokens.- TypeScript
- Python
Verifying our signed requests
When your sales agent asks for it, Apostra signs its AdCP calls to your agent with RFC 9421 HTTP message signatures, over both MCP and A2A. This section covers our requests to you. To receive signed requests, your agent declares which operations it wants signed (step 1) and verifies them with our public key; it doesn’t publish a JWKS or abrand.json of its own.
Signing the webhooks your agent sends back to us is the other direction,
covered in Signing the webhooks you send us.
-
Ask for signatures. Add a
request_signingblock to yourget_adcp_capabilitiesresponse. Apostra signs every operation listed inrequired_fororsupported_for, for example:Without the block, calls are not signed, and your agent needs another way to recognise Apostra, such as an API key you issue for the source’s Auth field. -
Find our key. Our agent URL is
https://api.apostra.com. Its/.well-known/brand.jsonlists that URL inagents[], and the entry’sjwks_uriishttps://api.apostra.com/.well-known/jwks.json, which publishes our Ed25519 request-signing key. You can configure that mapping directly, or discover it from the agent URL the way AdCP describes: the agent URL answersget_adcp_capabilitiesover MCP, over A2A (its agent card is athttps://api.apostra.com/.well-known/agent-card.json), and to a plainGETwithAccept: application/json, all without credentials. Itsidentityblock givesbrand_json_urlandkey_origins. -
Verify. The key ID in each signature’s
keyidparameter selects the key from that JWKS. Thekeyidis a bare key ID, such asscope3-req-sign-production. It never contains a URL: AdCP doesn’t let a signer say where its own keys are.
operator or brand in the
account on the request. The operator is the agency holding the seat, and its
domain doesn’t publish Apostra’s keys. If your verifier fails on an agency’s
domain, or on a host such as test-agency.apostra.com, it is reading the
account’s operator instead of our agent URL.
Integrations configured with our earlier agent URL, https://api.interchange.io,
keep working: brand.json lists it too, and it resolves to the same key.
How you know a request is from Apostra
Nothing in a signed request names the agent that signed it, and AdCP forbids trusting a field that claims to. You know it’s us because you set us up once as a buying agent with the agent URLhttps://api.apostra.com. When a signed
request arrives, its keyid must match a key in that agent’s JWKS. Reject a
keyid that doesn’t resolve to exactly one agent you’ve set up.
Two separate checks
A signed request answers two questions, and different documents answer each.- Who sent it? The signature. Our agent’s own record,
https://api.apostra.com/.well-known/brand.json, lists our agent and its keys.apostra.com/.well-known/brand.jsondescribes the Apostra brand and lists no agents, so don’t look for our keys there. - May this agency act for this brand? The brand’s own
brand.jsonanswers this through itsauthorized_operators, which names the agencies allowed to buy for it.
operator on the account never lists or vouches for our
agent, and you don’t need it to. One agency works through many agents, and the
agent’s own record is what proves who signed.
Check your setup
Runnpx @adcp/sdk@latest resolve https://api.apostra.com to see every step
of key discovery against our live agent: capabilities, brand.json, the agent
entry, jwks_uri and the key IDs. The AdCP SDKs run the same discovery in
code: resolveAgent in @adcp/sdk/signing/server (JavaScript) returns the
agent’s JWKS, and verify_from_agent_url in adcp (Python) runs discovery
and verifies a request in one call.
Base URLs
Programmatic API endpoints and buyer MCP are served at
https://api.apostra.com. The canonical storefront agent URL is
https://interchange.io. Discovery endpoints under /.well-known/* (JWKS,
brand.json, OAuth/OIDC metadata) return the same response on both
https://api.apostra.com and https://interchange.io.Versioning
REST and MCP endpoints come in two forms — pick based on whether you want to pin to a specific major version or auto-roll with the platform.
The same pattern applies to storefront endpoints (
/api/v2/storefront vs /api/storefront, /mcp/v2/storefront vs /mcp/storefront).
The Base URLs table above lists the versioned form because it’s what we recommend for any integration you don’t plan to revisit on every major release.
Account availability
An Apostra account’s availability is separate from its account role, organization hierarchy, commercial standing, and feature entitlements. An enabled account is available for member sign-in and account selection. If an enabled account has no owner, it may be eligible for the first verified user whose email domain matches the account’s registered domain to claim it and become its administrator. Automatic claiming also requires an unambiguous, non-public domain match and an eligible organization hierarchy. A user on a different email domain must be invited manually. Buyer interest is scoped to the requested product and market/channel; it is not an Account state. When a product cell is not ready, the applicant may join that cell’s waitlist without blocking other available cells. A supply gap is shown separately and may offer continuing setup, bringing supply, or browsing other cells. Ordinary exact-domain Organization claims are self-service; reviewers handle ambiguous, disputed, shared-domain, or risk cases. After sign-in, Buyer Setup derives what the Account can do from Organization proof, current Terms, plan/entitlements, standing, and the exact payment route. No reviewer flips a sandbox/live Account mode. A deactivated account is not available for sign-in. When every account a person belongs to is deactivated, browser sign-in with OAuth, SSO or a verified password shows the person any pending invitation and otherwise sends them to signup to set up a new account. Token refresh, and password sign-in with an unverified email, refuse it with403, error: "NO_ACTIVE_HOME_CUSTOMER" and
reason: "ACCOUNT_DEACTIVATED", and the sign-in page says the account has
been deactivated so the person does not assume their password is wrong. To get
the deactivated account back, ask its administrator to restore it, or email
support@apostra.com. A person who belongs to no
account at all is handled by signup or a pending invitation too.
Enabling an account does not:
- register its organization as an Apostra Partner or grant commercial Partner approval;
- grant certification, contract readiness, or Apostra listing readiness; or
- choose whether a Buyer operates in sandbox or production.
Terms of Service acceptance
Every organization must accept Apostra’s Terms of Service before its authenticated members can use most of the API surface. ToS acceptance applies to the organization, not the individual. Accounts under a parent organization inherit the parent’s governing agreement; they do not accept separate account-level terms. Once a direct parent-organization ADMIN accepts, the organization and its accounts are unblocked. Only a direct ADMIN or SUPER_ADMINUserPermission on the organization that
owns the governing agreement is permitted to accept its ToS via
POST /api/v2/accept-tos (or the accept_tos MCP tool).
- An account member who is not a direct admin of its parent organization cannot accept the parent’s ToS. They must join the parent organization as an admin first.
- A direct member at a non-admin level (
BASIC,PREMIUM) also cannot accept — onlyADMIN/SUPER_ADMINlevels qualify.
If your organization has no admin
An organization can be left with no admin. Nobody inside it can then accept the ToS or manage its members, and asking “your admin” reaches no one. Apostra tells you when this is the case, and you can ask for an admin instead of waiting.- How you find out. When
canAcceptTosisfalse, the current-user responses (GET /auth/me,POST /api/v2/accounts/switch, and theuser_get_currenttool) also carryorganizationHasAdministrator. It isfalsewhen the organization governing your account has no admin, and it is omitted when you can accept. - Whether you can ask. When
organizationHasAdministratorisfalse, the same responses carrycanRequestOrganizationAdmin, which says whether you can file the request from the account you are in right now. When it isfalse,organizationAdminRequestBlockedReasonsays why:staff_session(Apostra staff are impersonating a user or viewing the account through platform support without a membership, so no request is filed in a customer’s name),not_a_member(you have no membership in the account),organization_has_admin,organization_setup_incomplete(the account has no organization; contact support), orunavailable(the check could not be made; try again). The same responses carryorganizationAdminRequestCustomerId, the account the decision was made for: the organization when you belong to it, otherwise the account you act in. The web app shows the button only when it istrueand otherwise shows the reason. - How you ask. In the web app, the blocking ToS notice shows Request
organization admin in place of Notify your admins. Through the API, call
POST /api/v2/accounts/request-organization-admin. The optional body fieldcustomerIdnames the account to file from; sendorganizationAdminRequestCustomerIdso the request is judged for the same account the button was offered for, whatever account your request headers select. You must be a direct member of that account. Without a body it acts on the organization of the account you are operating in. It returnsorganizationId,requestId,status(pendingorapproved), andcreated. - One request per person. You get one request for each organization.
Asking again returns the same request with
created: falseand does not notify Apostra again. - Who can ask. You must be a member of the account you file from (the
account in
customerId, else the account you are operating in). The request is refused with409if the organization already has an admin, and with403if you are not a member of the account or an API key lacksinterchange:admin. Staff impersonating or simulating a user cannot make the request. Staff in a platform support session who are not members of the account getstaff_sessionas the reason; staff who are direct members of the account can request as any member can. - If you would have owned the organization at signup, you are approved at
once. Signing up with a verified email on an organization’s domain makes you
its admin when it has no admin. A request follows the same rule. When the
organization has no admin and no pending admin invitation, and you are signed
in with a verified email on the organization’s domain, the call approves your
request itself and returns
status: "approved"withcreated: true. It is the approval Apostra staff would give: an admin invitation is emailed to you, and in the web app the notice shows an Accept link right away. You become an admin by accepting that invitation, then accept the ToS. Apostra staff are not asked. Nothing makes you an admin before you accept. - Everyone else waits for review. Any other request returns
status: "pending"and goes to Apostra staff. That includes an email on a different domain, a domain not registered to the organization or shared by several accounts, a personal email address, a mailbox that is not verified, and an organization that already has a pending admin invitation. If an admin appears while the call is running, it returns409; if an admin invitation appears, the request stays pending. - What happens next. For a
pendingrequest, Apostra staff review it. If it is approved, you receive an invitation by email to become an admin of the organization. Accept it from the pending invitation (see below), then accept the ToS. Nobody becomes an admin without an approval, and an approval is either Apostra staff’s or the signup rule above. - If you would rather email. Write to support@apostra.com.
When updated terms are published
When updated Terms of Service appear in Apostra, the required action depends on the kind of change:- A patch that does not materially change the terms, or is wholly favorable to customers, is a notice-only update. Existing acceptance continues without an interruption.
- A material minor or major update requires an organization administrator to accept. Before the displayed deadline, eligible administrators see the review prompt and can choose Review later; other members can continue working during that review period.
- Once the deadline passes, account activity pauses until an eligible administrator accepts. Non-admin members then see a notice telling them to contact an administrator, while the account selector remains available.
tosUpdate object on
current-user and account-switch responses. Its action is notice,
acceptance_due, or acceptance_required, with the target version, deadline,
terms URL, and change-summary URL. If Apostra cannot verify agreement
status, protected actions return a retryable service error rather than
proceeding without confirmed terms.
Citing an exact published revision
The unversioned agreement links always show the maintained current publication. Use them when you want the terms or policy that Apostra presents now. Each response includes aContent-Location header with the exact revision URL that
supplied its body.
Use that versioned URL when a citation must continue to identify the same text.
For example:
To find every published revision of a document, request
https://api.apostra.com/agreements/{document}/versions, for example
/agreements/terms-of-service/versions. The response lists each version
newest first, with its label, display version, publication date, and permanent
path, and names the current version. Agreement pages in Apostra list the
earlier versions at the bottom of each document from the same source.
Versioned responses use a one-year immutable cache policy and include a strong
ETag plus X-Content-SHA256. The service verifies that hash before returning
the body. A correction is published as a new revision; it does not replace the
bytes at an earlier revision URL. Current aliases use short-lived caching so
they can advance to the next approved revision.
To review what changed, fetch the two exact revision URLs rather than comparing
an old copy with the current alias. Where Apostra publishes a prepared
summary, it also has a permanent URL—for example,
https://api.apostra.com/agreements/changes/terms-of-service/v2_v3-rev1.
The original v2_v3 URL remains available with its previously published bytes;
the -rev1 suffix identifies the corrected summary without rewriting that
earlier citation. The
agreement bodies remain Markdown even when they are long, so callers can use
ordinary line- or section-based diff tools without relying on rendered website
content.
The user-info responses (/auth/me, POST /api/v2/accounts/switch,
POST /api/v2/accounts/create-child, and the user_get_current MCP
tool) include a canAcceptTos boolean reflecting this rule. When
showTosBox: true but canAcceptTos: false, the UI shows a blocking notice.
The global account selector remains available so the user can switch to an
unblocked organization or accept a pending organization invitation.
The deprecated customer.accountProducts.core.buyerAccessPosture on a Buyer
account is derived from the Buyer capability verdicts on every read and is never
stored: LIVE_ELIGIBLE when the Buyer can spend live, otherwise SANDBOX_ONLY.
Read the capability verdicts instead.
/auth/me also returns contractBlockReason when the active account cannot
transact, and omits it when the account is in good standing:
showTosBox answers “must this session be blocked”; contractBlockReason
answers “why can this account not transact”. They differ for an Apostra SuperAdmin
working inside a customer account: that session is deliberately not blocked
(showTosBox: false), but the reason is still reported so the operator sees the
state a real user of that account would hit.
/auth/me also returns customerAccess, which says how the session reached the
account it describes. X-SCOPE3-CUSTOMER-ID selects an account only when the
caller can reach it. A malformed value receives 400; an account the caller
cannot reach receives 403, rather than silently serving the home account.
The /auth/me recovery response is the exception: it serves the home account
and returns customerScopeDenied: true so browser clients can clear their
stale account selection. A request that successfully sends
X-SCOPE3-CUSTOMER-ID can land on that account in more than one way, and the
account appears in the returned account list in every case, so use this field
to tell them apart:
customerScopeDenied is false unless /auth/me recovered from an
unreachable account selector.
Next Steps
Quick Start
Get up and running in minutes.
Built for Agents
Connect Claude, ChatGPT, Cursor, and more.
SSO Setup
Configure single sign-on for your organization.