> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apostra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How to authenticate with Apostra using Agent Registration, OAuth, API keys, or M2M credentials.

# 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.

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

### 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 solutions 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.

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; admission may place the seller and buyer workspaces beneath one
organization billing boundary.

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 or a magic code 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` or `POST /auth/verify-code` returns this response:

```json theme={null}
{
  "requiresSignup": true,
  "redirectUrl": "/signup?verified_pending=true&token=..."
}
```

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.

`POST /auth/magic-code` always returns the generic message
`If an account exists, a code has been sent`, whether the email exists only in WorkOS,
only in Apostra, in both systems, or in neither. Clients must not use this response
to infer account existence.

### Dark signup routing intent

Enrolled dark integrations may provide an optional `signup_experience` parameter to
`GET /auth/url` or `GET /auth/sso/url`. Its allowed values are
`ADVERTISER`, `ADVERTISER_PORTFOLIO`, `MEDIA_COMPANY`, and `DEVELOPER`. The value is
routing metadata only: it does not grant access, admission, organization membership,
entitlements, billing, or navigation authority. It is not inferred from account type,
email, domain, provider, or other caller data.

This remains a dark, non-public rollout. The live signup UI shows the chooser only to
explicitly enrolled users; public cohorts do not receive it. A selection originates
the same non-authorizing routing metadata described above. The server preserves a
submitted value 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 wider
marketplace 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 log in with their Apostra credentials and the agent receives a secure token.

* 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](/v2/setup/built-for-agents#connecting-ai-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 Murph, 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](https://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

<Warning>
  **Keep your API key secure!** Don't commit it to version control or share it
  publicly. Use environment variables or secure key management systems.
</Warning>

### 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:

| Permission           | What it grants                         |
| -------------------- | -------------------------------------- |
| `interchange:access` | Access to Apostra API as the key owner |

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.

```bash theme={null}
# List existing account-owned keys
curl -X GET "https://api.apostra.com/api/v2/org-api-keys" \
  -H "Authorization: Bearer your_user_access_token_here"

# Revoke an existing account-owned key
curl -X DELETE "https://api.apostra.com/api/v2/org-api-keys/key_abc123" \
  -H "Authorization: Bearer your_user_access_token_here"
```

Applications embedding the user management surface can request its widget token:

```bash theme={null}
curl -X GET "https://api.apostra.com/api/v2/user-api-keys/widget-token" \
  -H "Authorization: Bearer your_user_access_token_here"
```

The response requires an interactive WorkOS user session and includes a short-lived
`token` bound to that user and the active organization.

### Using Your API Key

Pass the key as a Bearer token in the `Authorization` header:

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -X GET "https://api.apostra.com/api/v2/buyer/advertisers" \
      -H "Authorization: Bearer your_api_key_here" \
      -H "Content-Type: application/json"
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const response = await fetch(
      'https://api.apostra.com/api/v2/buyer/advertisers',
      {
        headers: {
          'Authorization': `Bearer ${process.env.SCOPE3_API_KEY}`,
          'Content-Type': 'application/json',
        },
      }
    );

    const { data } = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests
    import os

    response = requests.get(
        "https://api.apostra.com/api/v2/buyer/advertisers",
        headers={
            "Authorization": f"Bearer {os.environ['SCOPE3_API_KEY']}",
            "Content-Type": "application/json",
        },
    )

    data = response.json()["data"]
    ```
  </Tab>
</Tabs>

***

## Agent Registration

An agent that has no credential starts with
[`https://api.apostra.com/auth.md`](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.

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.

```json theme={null}
{
  "data": {
    "schemaVersion": "1.3",
    "activationId": "0198...",
    "workloadId": "agent-registration:41",
    "state": "unbound_free",
    "expiresAt": "2026-09-17T00:00:00.000Z",
    "account": null,
    "allowance": {
      "ttlDays": 30,
      "cleanup": "automatic_at_expiry",
      "spendCap": { "amountMinor": 0, "currency": "USD" },
      "grants": [
        "buyer.activation:read",
        "buyer.activation.provider-binding:write",
        "buyer.advertisers:list",
        "buyer.advertisers:read"
      ],
      "prohibitedOperations": [
        "cross_customer_access",
        "other_mutations",
        "spend_commitment"
      ]
    },
    "claim": { "available": true, "preservesActivationId": true }
  }
}
```

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 Apostra-cleared spend from Organization
proof, Terms, grants, standing, and the exact payment 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.

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](/v2/setup/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:

<Note>
  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](/v2/setup/buyer-agent-credentials). 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.
</Note>

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Client } from '@modelcontextprotocol/sdk/client/index.js';
    import { HttpClientTransport } from '@anthropic-ai/mcp-client-http';

    const transport = new HttpClientTransport({
      url: 'https://api.apostra.com/mcp/buyer',
      headers: {
        'Authorization': `Bearer ${process.env.SCOPE3_API_KEY}`,
      },
    });

    const client = new Client({ name: 'my-agent', version: '1.0.0' });
    await client.connect(transport);

    const result = await client.callTool('health', {});
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from mcp.client.streamable_http_transport import StreamableHttpTransport
    from mcp.client import Client

    transport = StreamableHttpTransport(
        "https://api.apostra.com/mcp/buyer",
        auth="your_api_key_here",
    )

    async with Client(transport) as client:
        result = await client.call_tool("health")
    ```
  </Tab>
</Tabs>

***

## Base URLs

| Type                           | Production                                  |
| ------------------------------ | ------------------------------------------- |
| Buyer REST                     | `https://api.apostra.com/api/v2/buyer`      |
| Storefront REST                | `https://api.apostra.com/api/v2/storefront` |
| Buyer MCP                      | `https://api.apostra.com/mcp/buyer`         |
| Canonical Storefront Agent URL | `https://interchange.io`                    |

<Note>
  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`.
</Note>

***

## 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.

| Form                      | Example                          | Behavior                                                                                                     | When to use                                                                                            |
| ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| **Versioned** (canonical) | `/api/v2/buyer`, `/mcp/v2/buyer` | Pinned to v2. Will keep serving v2 even after a future v3 ships.                                             | Production integrations, SDKs, anywhere a breaking change would silently break you.                    |
| **Unversioned** (alias)   | `/api/buyer`, `/mcp/buyer`       | 308-redirects to whatever major version is currently stable (today: v2). Auto-rolls when a new major is cut. | Quick demos, links you don't want to update, or environments where you actively want to follow latest. |

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.

Enabling an account does **not**:

* register its organization as an Apostra Partner or grant commercial
  Partner approval;
* grant certification, contract readiness, or marketplace 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.

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.

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:

| Document                | Maintained current                                           | Exact published revision                                                |
| ----------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------- |
| Terms of Service        | `https://api.apostra.com/agreements/terms-of-service`        | `https://api.apostra.com/agreements/terms-of-service/v3-0`              |
| Privacy Policy          | `https://api.apostra.com/agreements/privacy-policy`          | `https://api.apostra.com/agreements/privacy-policy/2026-07-13`          |
| Demo Environment Notice | `https://api.apostra.com/agreements/demo-environment-notice` | `https://api.apostra.com/agreements/demo-environment-notice/2026-09-07` |

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.

`/auth/me` also returns `contractBlockReason` when the active account cannot
transact, and omits it when the account is in good standing:

| `contractBlockReason`           | Meaning                                                                                                                                                                             |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `no_contract`                   | The account has no active contract with Apostra. Accepting the Terms of Service creates one.                                                                                        |
| `tos_not_accepted`              | The account is on standard terms it has not accepted — either it accepted an older version, or Apostra set it up on standard terms and nobody at the account has accepted them yet. |
| `organization_contract_missing` | The account inherits its parent organization's contract, and the organization has no active one. An organization admin must accept on its behalf.                                   |

`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.

***

## Next Steps

<CardGroup cols={3}>
  <Card title="Quick Start" href="/v2/quickstart" icon="rocket">
    Get up and running in minutes.
  </Card>

  <Card title="Built for Agents" href="/v2/setup/built-for-agents" icon="robot">
    Connect Claude, ChatGPT, Cursor, and more.
  </Card>

  <Card title="SSO Setup" href="/v2/setup/sso-setup" icon="lock">
    Configure single sign-on for your organization.
  </Card>
</CardGroup>
