Skip to main content

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 different Origin 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 returned redirectUrl when POST /auth/password returns this response:
The token is opaque, expires after ten minutes, and is bound to the exact verified WorkOS subject and email. Do not log or persist it. The signup page reads the non-secret form fields from 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 the DEVELOPER 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.
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
See the Built for Agents guide for setup instructions.

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 same resource 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:
  1. Open the account menu in the top-right corner of Apostra UI.
  2. Select Connected apps.
  3. Find the app you want to remove and click Disconnect.
  4. Confirm the disconnection in the dialog.
Disconnecting an app immediately invalidates its access and refresh credentials. The next time that app tries to use Apostra it will need to go through authorization again. Disconnection does not affect your browser session or any other connected apps.

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

  1. Visit app.apostra.com/user-api-keys
  2. Sign up or log into your Apostra account
  3. Create a key while signed in as the user the integration should act as
  4. Copy the key when Apostra displays it
Keep your API key secure! Don’t commit it to version control or share it publicly. Use environment variables or secure key management systems.

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 a scope3_ 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.
Applications embedding the user management surface can request its widget token:
The response requires an interactive WorkOS user session and includes a short-lived 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 in budget_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.
Send {"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 the Authorization header:

Agent Registration

An agent that has no credential starts with https://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.
Anonymous registrations receive a short-lived WorkOS access token and can reach only the zero-spend Apostra sandbox. This bounded anonymous trial does not require an organization to accept Terms of Service because the agent is not associated with a customer yet. Before claim, the allowed API reads are 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.0 client_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.

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 a brand.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.
  1. Ask for signatures. Add a request_signing block to your get_adcp_capabilities response. Apostra signs every operation listed in required_for or supported_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.
  2. Find our key. Our agent URL is https://api.apostra.com. Its /.well-known/brand.json lists that URL in agents[], and the entry’s jwks_uri is https://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 answers get_adcp_capabilities over MCP, over A2A (its agent card is at https://api.apostra.com/.well-known/agent-card.json), and to a plain GET with Accept: application/json, all without credentials. Its identity block gives brand_json_url and key_origins.
  3. Verify. The key ID in each signature’s keyid parameter selects the key from that JWKS. The keyid is a bare key ID, such as scope3-req-sign-production. It never contains a URL: AdCP doesn’t let a signer say where its own keys are.
Resolve our key from our agent URL, not from the 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 URL https://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.json describes 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.json answers this through its authorized_operators, which names the agencies allowed to buy for it.
The agency named as 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

Run npx @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 with 403, 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.
Those states are configured and approved independently. Account availability is also not a universal credential-revocation control: revoke service tokens, API keys, and other grants separately when access must be terminated.

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_ADMIN UserPermission 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 — only ADMIN / SUPER_ADMIN levels 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 canAcceptTos is false, the current-user responses (GET /auth/me, POST /api/v2/accounts/switch, and the user_get_current tool) also carry organizationHasAdministrator. It is false when the organization governing your account has no admin, and it is omitted when you can accept.
  • Whether you can ask. When organizationHasAdministrator is false, the same responses carry canRequestOrganizationAdmin, which says whether you can file the request from the account you are in right now. When it is false, organizationAdminRequestBlockedReason says 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), or unavailable (the check could not be made; try again). The same responses carry organizationAdminRequestCustomerId, 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 is true and 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 field customerId names the account to file from; send organizationAdminRequestCustomerId so 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 returns organizationId, requestId, status (pending or approved), and created.
  • One request per person. You get one request for each organization. Asking again returns the same request with created: false and 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 with 409 if the organization already has an admin, and with 403 if you are not a member of the account or an API key lacks interchange:admin. Staff impersonating or simulating a user cannot make the request. Staff in a platform support session who are not members of the account get staff_session as 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" with created: 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 returns 409; if an admin invitation appears, the request stays pending.
  • What happens next. For a pending request, 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.
Pending organization invitations remain available while ToS is outstanding. Open the account selector, choose the pending invitation, and accept or decline it. Accepting an admin invitation creates the direct organization membership needed to review and accept the organization’s ToS; the ToS prompt does not block the invitation page. Account administrators can also invite account members and configure Communications while organization ToS is outstanding. Support-channel setup and notification preferences remain available for onboarding, but a person observed in a connected channel does not gain account access automatically; an administrator must grant that membership explicitly.

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.
Accepting a minor update keeps the existing commercial contract and any negotiated pricing attached to it. A major update does not silently replace custom commercial terms; Apostra coordinates that change separately. The current Terms of Service are the Apostra Platform Terms of Service v4.1, a major update from v3.0, effective September 23, 2026. Organizations that accepted v3.0 can choose Review later until November 1, 2026; after that date, account activity pauses until an eligible administrator accepts v4.1. Until then, an organization’s existing v3.0 standard contract, including its media fee rates, stays in place. Accepting v4.1 moves the organization to a standard contract that takes no percentage of media. Read the summary of changes for what changed between the two versions. The documents v4.1 incorporates by reference are published alongside it: the Facilitated Agreement Terms 1.2, which govern existing Relationship Agreements once an organization accepts v4.1, the Marketplace Policies 1.1, and the Payment Operations Schedule 1.1. Earlier versions remain available at their versioned URLs. REST and MCP integrations can read the transition in the 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 a Content-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.