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

# Buyer API Reference

> Apostra Buyer API reference documentation for brand advertisers

# Buyer API Reference

## Overview

Apostra Buyer API provides enhanced capabilities for brand advertisers to manage advertising campaigns through both REST and MCP (Model Context Protocol) interfaces.

The API follows the **SESOFI (Single Endpoint, Single Object, Full Intent)** design principle:

* **Single Endpoint** - One endpoint per high-level intent (update an advertiser, run a media buy, refine a discovery query)
* **Single Object** - The endpoint accepts one object describing the full intent
* **Full Intent** - That object carries every related decision so an agent can express what it wants in one call instead of orchestrating sub-resource updates

## Connection Methods

<Tabs>
  <Tab title="REST API">
    ### REST API (Recommended for traditional integrations)

    Standard HTTP methods with JSON request/response bodies.

    **Production:**

    ```
    https://api.apostra.com/api/v2/buyer
    ```

    **Example:**

    ```bash theme={null}
    curl https://api.apostra.com/api/v2/buyer/advertisers \
      -H "Authorization: Bearer scope3_your_api_key_here"
    ```
  </Tab>

  <Tab title="MCP (AI Agents)">
    ### MCP (Model Context Protocol) for AI Agents

    JSON-RPC 2.0 over HTTP POST. MCP 2026-07-28 clients use stateless,
    per-request routing; existing clients continue using the initialize/session
    lifecycle. Compatible MCP SDKs negotiate this automatically—there is no
    Apostra opt-in.

    **Production:**

    ```
    https://api.apostra.com/mcp/buyer
    ```

    **Example:**

    ```bash theme={null}
    curl -X POST https://api.apostra.com/mcp/buyer \
      -H "Authorization: Bearer scope3_your_api_key_here" \
      -H "Content-Type: application/json" \
      -H "MCP-Protocol-Version: 2026-07-28" \
      -H "Mcp-Method: tools/list" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"example","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
    ```
  </Tab>
</Tabs>

### Hosted buyer-agent MCP edge

<Warning>
  This endpoint is published ahead of production activation. It remains
  fail-closed with `503` until the hosted runtime router, controller trust, and
  deployment credentials are enabled for the environment. Do not configure a
  production client until hosted buyer-agent availability is announced.
</Warning>

Apostra-hosted buyer-agent runtimes connect through an installation-scoped
Streamable HTTP endpoint:

```text theme={null}
https://api.apostra.com/agents/{installationId}/mcp
```

This endpoint is for a hosted installation provisioned by the deployment
orchestrator. It is not a replacement for the general `/mcp/buyer` endpoint.
See [Buyer agent credentials](/v2/setup/buyer-agent-credentials) for the
ordinary credential lifecycle. Send the hosted runtime token as a bearer
credential. Apostra authenticates the token, reads back the active
installation's exact owner and lifecycle generation, resolves the ready runtime
through the deployment controller, and forwards the MCP stream with a fresh
deployment-signed caller assertion. The client credential itself is never
forwarded.

The edge supports `GET`, `POST`, and `DELETE`. Preserve the `Mcp-Session-Id`
header returned by the runtime on subsequent stateful MCP requests. A `401`
means the bearer credential is missing or invalid, a `404` deliberately covers
both missing and unauthorized installation IDs, and a `503` means the hosted
runtime or its deployment control plane is unavailable. Other HTTP methods
return `405`.

Hosted-runtime provisioning may temporarily send either `installationId` or
`lifecycleGeneration` alone to the internal binding endpoint until 7 September
2027\. Apostra accepts those deprecated legacy requests but discards the
partial provenance, returns `Deprecation` and `Sunset` headers, and reports the
`partial_ignored` disposition. A complete noncanonical legacy pair is preserved
as a separate non-routable tuple and reports `legacy_complete_bound`; it never
becomes a hosted runtime target. Only a versioned UUID `installationId` paired
with a canonical non-negative lifecycle generation reports `bound` and is used
for routing.

## OpenAPI Specification

Download the OpenAPI specification for SDK generation or API exploration:

* **Production:** [/api/v2/buyer/openapi.yaml](https://api.apostra.com/api/v2/buyer/openapi.yaml)

## Authentication

All API requests require authentication using your Apostra API key:

```bash theme={null}
Authorization: Bearer scope3_your_api_key_here
```

<Card title="Get API Key" href="https://app.apostra.com/user-api-keys" icon="key">
  Request API credentials to get started
</Card>

***

## REST API Endpoints

### Send a fax

See [Agent fax lines](/v2/reference/agent-fax) for provisioning, inbound
delivery, media hosting, retry behavior, and the complete error model.

<Warning>
  Fax delivery is in limited rollout. The endpoint returns `404` until your
  customer account has an active fax line and has been enabled for access.
</Warning>

Send a PDF through a fax line provisioned for your account:

```bash theme={null}
curl -X POST "https://api.apostra.com/api/v2/communications/faxes" \
  -H "Authorization: Bearer $INTERCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "df3b1f38-77ad-4e2e-ad67-594433cf1be7",
    "endpointId": "123",
    "to": "+12025550101",
    "mediaUrl": "https://relay.example.com/fax.pdf",
    "mediaSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  }'
```

The credential needs `interchange:write`. `to` must use E.164 format.
`mediaUrl` must be a temporary HTTPS URL for the PDF, and `mediaSha256` must
be the lowercase SHA-256 digest of the same file. Apostra does not retain
the URL or destination number in the fax command ledger.

Use a new UUID for each intended fax. Retrying the same `requestId` with the
same endpoint, destination, and PDF is safe. Changing any of those fields
while reusing the UUID returns `409`.

A successful submission returns `202`:

```json theme={null}
{
  "data": {
    "fax": {
      "providerFaxId": "b4c7f7e8-33c4-4af6-82f4-55a41ba93427",
      "providerStatus": "queued",
      "duplicate": false
    }
  },
  "error": null
}
```

`duplicate` is `true` when the response comes from a completed submission
with the same `requestId`. A `503` with a submission state of
`submission_unknown` means the provider may have accepted the fax. Do not
retry it with a new UUID until the submission has been reconciled.

### Agent activation

| Method | Endpoint                                    | Description                                                                                                  |
| ------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| GET    | `/.well-known/buyer-activation.json`        | Public WorkOS endpoints and the zero-spend sandbox contract                                                  |
| GET    | `/api/v2/buyer/activation`                  | Stable activation/workload IDs, expiry, grants, and prohibitions for a WorkOS agent registration             |
| POST   | `/api/v2/buyer/activation/provider-binding` | Atomically retain the first customer-local provider account as `BOUND_FREE` without requiring identity claim |

The public contract is the machine-readable entry point for an agent that starts with
no Apostra credential. The protected probe accepts the short-lived WorkOS bearer
token; ordinary user, API-key, and M2M credentials receive
`AGENT_REGISTRATION_REQUIRED`. See [Authentication](/v2/authentication#agent-registration)
for the registration exchange and full response shape.

The contract's `allowance.grants` are effective sandbox capabilities derived by
Apostra from the verified Agent Registration credential and activation state.
They are not separate WorkOS permissions that an operator must create.

#### Discover the activation contract

The discovery endpoint is public and requires no bearer token:

```bash theme={null}
curl "https://api.apostra.com/.well-known/buyer-activation.json"
```

```json theme={null}
{
  "schemaVersion": "1.3",
  "signupMotion": "byoba_zero_touch",
  "authInstructions": "https://api.apostra.com/auth.md",
  "registrationEndpoint": "https://identity.scope3.com/agent/identity",
  "tokenEndpoint": "https://identity.scope3.com/oauth2/token",
  "activationEndpoint": "https://api.apostra.com/api/v2/buyer/activation",
  "providerBindingEndpoint": "https://api.apostra.com/api/v2/buyer/activation/provider-binding",
  "claimInstructions": "https://identity.scope3.com/agent/auth.md",
  "allowance": {
    "ttlDays": 30,
    "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"
    ],
    "cleanup": "automatic_at_expiry"
  }
}
```

Use `authInstructions` for the current WorkOS registration and assertion-exchange
steps instead of hard-coding that protocol. The endpoint URLs in the contract are
environment-specific; the example above shows production.

#### Verify the registered workload

After exchanging the WorkOS assertion for an access token, make the authenticated
activation call:

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/activation" \
  -H "Authorization: Bearer $WORKOS_ACCESS_TOKEN"
```

```json theme={null}
{
  "data": {
    "schemaVersion": "1.3",
    "activationId": "0198abcd-0000-7000-8000-000000000001",
    "workloadId": "agent-registration:41",
    "state": "unbound_free",
    "expiresAt": "2026-09-17T00:00:00.000Z",
    "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"
      ]
    },
    "account": null,
    "claim": { "available": true, "preservesActivationId": true }
  }
}
```

The same registration returns the same `activationId` and `workloadId` on retries.
Apostra durably records the first successful authenticated activation before it
returns `2xx`. Its first customer-local provider mapping changes the response to
`state: "bound_free"`, clears anonymous cleanup, and preserves the zero-dollar spend
cap. Replaying that exact mapping is safe; a different second account is rejected.
Claiming the registration later is optional for recovery or named administration and
preserves `activationId`; the response then reports `state: "customer_bound"` and the
customer-authorized permissions. `customer_bound` describes identity attachment only.
Human and agent principals then read the same Buyer Setup capability verdicts; there is
no separate agent admission or Account lifecycle mode. Claiming does not create or
admit an Account, accept Terms, establish payment authority, or grant permission for
real operations.
For schema 1.2 compatibility, a `customer_bound` response may include the
deprecated `account` posture projection when the caller has claimed customer
authority. Free and unclaimed responses return `account: null`. Never use this
field for authorization; use the operation-specific Buyer Setup capabilities.
An `unbound_free` registration that never maps an account is retired automatically after
expiry.

***

### Advertisers

Core advertiser management endpoints. Following SESOFI, `GET` returns full advertiser data including all nested configuration.

| Method | Endpoint                        | Description                            |
| ------ | ------------------------------- | -------------------------------------- |
| GET    | `/api/v2/buyer/advertisers`     | List all advertisers                   |
| POST   | `/api/v2/buyer/advertisers`     | Create a new advertiser                |
| GET    | `/api/v2/buyer/advertisers/:id` | Get advertiser with full configuration |
| PUT    | `/api/v2/buyer/advertisers/:id` | Update advertiser (partial or full)    |

**Query Parameters for `GET /advertisers`:**

* `includeAccounts` - When `true`, includes linked partner accounts in each advertiser response (avoids N+1 calls to `/accounts`)
* `includeBrand` - When `true`, includes resolved brand information (logos, colors, industry)
* `take` / `skip` - Pagination

**Example - List Advertisers with Accounts:**

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/advertisers?includeAccounts=true" \
  -H "Authorization: Bearer scope3_your_api_key_here"
```

**Example - Get Advertiser (returns EVERYTHING):**

```bash theme={null}
curl https://api.apostra.com/api/v2/buyer/advertisers/12345 \
  -H "Authorization: Bearer scope3_your_api_key_here"
```

**Response includes:**

* Advertiser details (name, brand, sandbox, etc.)
* Event sources
* Test cohorts
* Measurement configuration
* Resolved brand (when `includeBrand=true`)

**Example - Create Advertiser with Linked Accounts:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "brand": "acme.com",
    "linkedAccounts": [
      {
        "storefrontId": 1,
        "sourceId": "src_main",
        "accountId": "acc_acme_pinnacle",
        "billingType": "brand"
      }
    ]
  }'
```

**`linkedAccounts` field** (available on create and update):

* `storefrontId` - Storefront the source lives on
* `sourceId` - Inventory source within the storefront whose account is being linked
* `accountId` - Source-assigned account identifier (must come from `GET /accounts/available`)
* `billingType` - Billing arrangement type (e.g. `"brand"`, `"agency"`)

On update, existing linked accounts are not removed — only new ones are added.

***

### Event Sources

Register the channels (pixels, SDKs, CRM exports, MMPs) that send conversion events for an advertiser. Events sent against an unregistered `event_source_id` are rejected, so configure sources before turning on a pixel or feed.

| Method | Endpoint                                                     | Description                           |
| ------ | ------------------------------------------------------------ | ------------------------------------- |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/event-sources`      | List event sources                    |
| POST   | `/api/v2/buyer/advertisers/:advertiserId/event-sources/sync` | Upsert event sources (ADCP-spec sync) |

**Example - Sync event sources:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/event-sources/sync \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "account": { "account_id": "12345" },
    "event_sources": [
      {
        "event_source_id": "website_pixel",
        "name": "Website Pixel",
        "event_types": ["purchase", "add_to_cart"],
        "allowed_domains": ["shop.example.com"]
      }
    ]
  }'
```

See the [Measurement & Incrementality guide](/v2/guides/measurement-incrementality) for the full event-source workflow, conversion ingestion, and incrementality test setup.

***

### Test Cohorts

Manage test cohorts for A/B testing and incrementality measurement.

| Method | Endpoint                                                   | Description                |
| ------ | ---------------------------------------------------------- | -------------------------- |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/test-cohorts`     | List all test cohorts      |
| POST   | `/api/v2/buyer/advertisers/:advertiserId/test-cohorts`     | Create a test cohort       |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/test-cohorts/:id` | Get a specific test cohort |
| PUT    | `/api/v2/buyer/advertisers/:advertiserId/test-cohorts/:id` | Update a test cohort       |
| DELETE | `/api/v2/buyer/advertisers/:advertiserId/test-cohorts/:id` | Delete a test cohort       |

**Example - Create Test Cohort:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/test-cohorts \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "West Coast Treatment Group",
    "cohortType": "geographic",
    "role": "TREATMENT",
    "definition": { "type": "zip_code", "zips": ["94103", "97201", "98101"] },
    "estimatedSize": 50000
  }'
```

**Required fields:** `name`, `cohortType`, `definition`. Optional: `role` (`TREATMENT` | `CONTROL`, default `TREATMENT`), `description`, `estimatedSize`. The path param is `:cohortId` on the GET/PUT/DELETE routes.

***

### Measurement Config

Configure measurement settings for an advertiser. This is a singleton resource per advertiser.

| Method | Endpoint                                                     | Description                      |
| ------ | ------------------------------------------------------------ | -------------------------------- |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/measurement-config` | Get measurement configuration    |
| PUT    | `/api/v2/buyer/advertisers/:advertiserId/measurement-config` | Update measurement configuration |

**Example - Update Measurement Config:**

```bash theme={null}
curl -X PUT https://api.apostra.com/api/v2/buyer/advertisers/12345/measurement-config \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "mmmEnabled": true,
    "mmmConfig": { "provider": "measured", "reportingFrequency": "weekly" },
    "brandLiftEnabled": false
  }'
```

All fields are optional. Available top-level fields: `mmmEnabled`, `mmmConfig`, `brandLiftEnabled`, `settings` (key-value blob for additional vendor-specific settings). See the [Measurement & incrementality guide](/v2/guides/measurement-incrementality) for the full configuration shape.

***

### Campaigns

| Method | Endpoint                                                                                                 | Description                                                                               |
| ------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| GET    | `/api/v2/buyer/campaigns`                                                                                | List all campaigns                                                                        |
| POST   | `/api/v2/buyer/campaigns`                                                                                | Create a platform-managed campaign                                                        |
| GET    | `/api/v2/buyer/campaigns/:id`                                                                            | Get campaign details                                                                      |
| PUT    | `/api/v2/buyer/campaigns/:id`                                                                            | Update a managed campaign, or refresh a tracked provider-account projection               |
| GET    | `/api/v2/buyer/campaigns/:id/delivery`                                                                   | Read live delivery through the campaign's connected provider account or single storefront |
| POST   | `/api/v2/buyer/storefront-connections/:connectionId/accounts/:accountId/directed-campaigns/subscribe`    | Subscribe and mirror a connected provider account                                         |
| GET    | `/api/v2/buyer/storefront-connections/:connectionId/accounts/:accountId/directed-campaigns/subscription` | Get metadata-mirror freshness and the history boundary                                    |
| DELETE | `/api/v2/buyer/storefront-connections/:connectionId/accounts/:accountId/directed-campaigns/subscription` | Stop mirroring and retire local mirrors                                                   |
| POST   | `/api/v2/buyer/campaigns/:id/execute`                                                                    | Execute/launch campaign                                                                   |
| POST   | `/api/v2/buyer/campaigns/:id/pause`                                                                      | Pause campaign                                                                            |
| POST   | `/api/v2/buyer/campaigns/:id/reactivate`                                                                 | Reactivate a paused campaign                                                              |

***

### Product discovery

| Method | Endpoint                                        | Description                                                                                                                                              |
| ------ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| POST   | `/api/v2/buyer/products/query`                  | Start, poll, or page canonical `get_products` across named or all connected storefronts, with optional managed proposal screening and refine negotiation |
| POST   | `/api/v2/buyer/media-buys/batch`                | Stage or execute storefront-qualified proposals/products from `get_products` through a DRAFT campaign cart                                               |
| POST   | `/api/v2/buyer/discovery/discover-products`     | Discover products from a brief                                                                                                                           |
| GET    | `/api/v2/buyer/discovery/:id/discover-products` | Browse or poll a discovery session                                                                                                                       |

See [Get products across storefronts](/v2/buyer/discovery/multi-storefront-get-products)
for progressive polling and qualified product identity.

***

### Creatives

Creatives are manifest-based and nested under campaigns rather than advertisers. The full lifecycle (formats, manifests, asset uploads) is documented in the [Creative object guide](/v2/object-guides/creative).

| Method | Endpoint                                                    | Description                                                                            |
| ------ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| GET    | `/api/v2/buyer/formats`                                     | List supported creative formats                                                        |
| POST   | `/api/v2/buyer/campaigns/:campaignId/creatives/create`      | Create a manifest-based creative                                                       |
| GET    | `/api/v2/buyer/campaigns/:campaignId/creatives`             | List manifests on a campaign, including optional provider readback in `platform_links` |
| GET    | `/api/v2/buyer/campaigns/:campaignId/creativeManifest`      | Legacy alias for listing manifests on a campaign                                       |
| GET    | `/api/v2/buyer/campaigns/:campaignId/creatives/:creativeId` | Get a creative                                                                         |
| PUT    | `/api/v2/buyer/campaigns/:campaignId/creatives/:creativeId` | Update a creative                                                                      |
| DELETE | `/api/v2/buyer/campaigns/:campaignId/creatives/:creativeId` | Delete a creative                                                                      |

For a creative sent through `sync_creatives`, each `platform_links` entry names
the connected agent and may include its provider creative ID, latest sync state,
approval state, and UTC sync timestamp. Use that linkage to reconcile the saved
campaign creative with the object created on the platform. The field is optional
for unsynced and historical creatives.

***

### Media Buys

View media buy execution status and delivery metrics. Media buy and package budgets are gross (fee-inclusive) — reads include `budget_denomination: "gross"` and a read-only `budget_breakdown` showing the media/fee split at the fee terms locked when the buy was created (see [Budgets and fees](/v2/concepts/budgets-and-fees)). Reduce budgets through the campaign update endpoint: when lowering `budget.total` below the live allocation, include the `mediaBuys[]` package reductions in the same atomic request.

| Method | Endpoint                                                 | Description              |
| ------ | -------------------------------------------------------- | ------------------------ |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/media-buys`     | List all media buys      |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/media-buys/:id` | Get a specific media buy |

**Media Buy Statuses:**

* `DRAFT` - Initial state, not yet submitted
* `PENDING_APPROVAL` - Submitted, awaiting approval
* `INPUT_REQUIRED` - Additional information needed
* `ACTIVE` - Currently running
* `PAUSED` - Temporarily paused
* `COMPLETED` - Successfully finished
* `FAILED` - Execution failed
* `REJECTED` - Not approved
* `ARCHIVED` - Archived/deleted

**Example - List Media Buys:**

```bash theme={null}
curl https://api.apostra.com/api/v2/buyer/advertisers/12345/media-buys \
  -H "Authorization: Bearer scope3_your_api_key_here"
```

***

### Reporting

Access hierarchical reporting metrics for campaigns, media buys, and packages. The endpoint supports both summary and timeseries views, plus a CSV download mode.

| Method | Endpoint                          | Description                                                  |
| ------ | --------------------------------- | ------------------------------------------------------------ |
| GET    | `/api/v2/buyer/reporting/metrics` | Get reporting metrics (summary, timeseries, or CSV download) |

**Query Parameters:**

* `advertiserId` - Filter by advertiser ID
* `campaignId` - Filter by campaign ID
* `channelGroupId` - Filter to media buys in one saved campaign channel group
* `mediaBuyId` - Filter by media buy ID
* `startDate` / `endDate` - ISO 8601 dates
* `view` - `summary` (default — hierarchical advertiser → campaign → media buy → package) or `timeseries` (rows by date)
* `download` - Pass `true` to return a signed CSV download URL instead of JSON

**Example - Get Reporting:**

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/reporting/metrics?advertiserId=12345&startDate=2026-01-15&endDate=2026-01-21" \
  -H "Authorization: Bearer scope3_your_api_key_here"
```

**Response (summary view):**

```json theme={null}
{
  "data": {
    "advertisers": [
      {
        "advertiserId": "12345",
        "name": "Acme Corp",
        "totals": { "impressions": 325000, "spend": 2701.25, "clicks": 970 },
        "campaigns": [
          {
            "campaignId": "cmp_987",
            "totals": { "impressions": 200000, "spend": 1500.00 },
            "mediaBuys": [
              { "mediaBuyId": "mb_abc", "totals": { "impressions": 100000 }, "packages": [] }
            ]
          }
        ]
      }
    ],
    "totals": { "impressions": 325000, "spend": 2701.25, "clicks": 970 },
    "periodStart": "2026-01-15",
    "periodEnd": "2026-01-21"
  }
}
```

For event counts (conversion summary), see `GET /api/v2/buyer/advertisers/:advertiserId/events/summary` in the [Reporting overview guide](/v2/guides/reporting-overview).

***

### Source Accounts

Discover and link advertiser accounts on inventory sources (e.g. a DSP seat). The flow is: discover available accounts via `GET .../accounts/available`, then assign one to the advertiser via `POST .../accounts`.

| Method | Endpoint                                                      | Description                                     |
| ------ | ------------------------------------------------------------- | ----------------------------------------------- |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/accounts/available`  | List accounts discovered on an inventory source |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/accounts`            | List accounts linked to this advertiser         |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/accounts/:accountId` | Get a single linked account                     |
| POST   | `/api/v2/buyer/advertisers/:advertiserId/accounts`            | Assign a discovered account to this advertiser  |

**Query Parameters for `GET .../accounts/available`:**

* `storefrontId` (required) - Storefront the source lives on. Get from `GET /api/v2/buyer/storefronts`.
* `sourceId` (required) - Inventory source within `storefrontId` whose accounts to list.
* `credentialId` - ID of a specific registered credential to use for account discovery. **Required when the account has multiple credentials registered for this source.** Use `GET /api/v2/buyer/storefronts/credentials` to list credentials and their IDs.
* `refresh` - Defaults to `true`, which refreshes live source state. Set to `false` only for a read-only cached projection; cached results can be stale or empty and the request will not mirror accounts, call source discovery, or trigger `sync_accounts`.

**Example - Discover available accounts:**

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/advertisers/12345/accounts/available?storefrontId=1&sourceId=src_main" \
  -H "Authorization: Bearer scope3_your_api_key_here"
```

**Example - Assign an account:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/accounts \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "storefrontId": 1,
    "sourceId": "src_main",
    "accountId": "acc_acme_pinnacle"
  }'
```

**Account fields (from source):**

* `accountId` - Source-assigned account identifier
* `name` - Human-readable account name (e.g. `"Acme c/o Pinnacle"`)
* `advertiser` - Advertiser name as recorded by the source
* `billingProxy` - Agency or proxy buying on behalf of the advertiser
* `house` - Domain or house identifier
* `billing` - Billing arrangement type (e.g. `"brand"`)
* `sources` - Array of `{ storefrontId, storefrontName, sourceId, sourceName }` showing every storefront source through which this account is reachable

***

### Storefronts

Browse storefronts (publisher inventory marketplaces) and register credentials
per inventory source. See the [Storefront object guide](/v2/object-guides/storefront)
for the full data model and end-to-end flow.

| Method | Endpoint                                                                | Description                                 |
| ------ | ----------------------------------------------------------------------- | ------------------------------------------- |
| GET    | `/api/v2/buyer/storefronts`                                             | List storefronts (paginated)                |
| GET    | `/api/v2/buyer/storefronts/:storefrontId`                               | Get a single storefront                     |
| GET    | `/api/v2/buyer/storefronts/:storefrontId/capabilities`                  | Inspect source-level capability diagnostics |
| GET    | `/api/v2/buyer/storefronts/discover`                                    | Lightweight list (active + upcoming)        |
| GET    | `/api/v2/buyer/storefronts/interest`                                    | Check your interest submission status       |
| POST   | `/api/v2/buyer/storefronts/interest`                                    | Submit interest in upcoming storefronts     |
| GET    | `/api/v2/buyer/storefronts/credentials`                                 | List your credentials across storefronts    |
| POST   | `/api/v2/buyer/storefronts/:storefrontId/sources/:sourceId/credentials` | Register credentials for a source           |
| POST   | `/api/v2/buyer/storefronts/:storefrontId/sources/:sourceId/accounts`    | Register an account on a source             |

**Query Parameters for `GET /storefronts`:**

* `name` - Case-insensitive partial match on storefront name
* `limit` - 1..50 (default 20)
* `offset` - Pagination offset (default 0)

**Example - List Storefronts:**

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/storefronts?limit=20" \
  -H "Authorization: Bearer scope3_your_api_key_here"
```

**Example - Register an Account on a Storefront Source:**

```bash theme={null}
curl -X POST "https://api.apostra.com/api/v2/buyer/storefronts/acme-media/sources/acme-sales-agent/accounts" \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "accountIdentifier": "acme-brand-account",
    "accountType": "brand"
  }'
```

The partner-agent response fields (`requiresOperatorAuth`, `billingOptions`, `accountResolution`) appear on the **Sales Agents** endpoints, not on storefronts — see the Sales Agents section below.

<Note>
  The `advertiserId` field has been removed from the register account body. Use the Partner Accounts endpoints to link accounts to specific advertisers after registration.
</Note>

***

### Catalogs

Manage product and offering catalogs that can be synced to partner platforms.

| Method | Endpoint                                                      | Description                                     |
| ------ | ------------------------------------------------------------- | ----------------------------------------------- |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/catalogs`            | List catalogs                                   |
| POST   | `/api/v2/buyer/advertisers/:advertiserId/catalogs`            | Create a catalog                                |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId` | Get a catalog                                   |
| PUT    | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId` | Update a catalog                                |
| DELETE | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId` | Archive a catalog (also archives all offerings) |
| POST   | `/api/v2/buyer/advertisers/:advertiserId/catalogs/sync`       | Sync catalogs to partner platforms              |

**Supported catalog types:** `product`, `offering`, `hotel`, `flight`, `destination`, `home_listing`, `vehicle`, `job`, `local`, `retail`, `travel`, `education`

**Example - Create a catalog:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/catalogs \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Products",
    "type": "product",
    "description": "Summer 2025 product catalog"
  }'
```

**Example - Sync to partners:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/catalogs/sync \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "partnerIds": ["premiumvideo_a1b2c3d4"]
  }'
```

***

### Offerings

Manage individual offerings (products, listings, etc.) within a catalog.

| Method | Endpoint                                                                            | Description                 |
| ------ | ----------------------------------------------------------------------------------- | --------------------------- |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId/offerings`             | List offerings in a catalog |
| POST   | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId/offerings`             | Create an offering          |
| GET    | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId/offerings/:offeringId` | Get an offering             |
| PUT    | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId/offerings/:offeringId` | Update an offering          |
| DELETE | `/api/v2/buyer/advertisers/:advertiserId/catalogs/:catalogId/offerings/:offeringId` | Archive an offering         |

**Example - Create an offering:**

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/catalogs/cat_abc/offerings \
  -H "Authorization: Bearer scope3_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "offeringId": "sku_001",
    "title": "Wireless Headphones",
    "description": "Premium noise-cancelling headphones",
    "price": 299.99,
    "currency": "USD",
    "landingUrl": "https://example.com/products/headphones"
  }'
```

***

## MCP Tools

The Buyer MCP server exposes dedicated tools for session-level actions and a generic `api_call` tool for REST operations. Use the dedicated MCP tool when one exists. Use `api_call` for buyer REST operations that do not have a more specific tool. Every `api_call` requires a named `operation`; the legacy raw `method` + `endpoint` form is no longer supported and is rejected.

| Tool                   | Description                                                                                                                                                                                                                        |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `health`               | Health check to verify the API is reachable.                                                                                                                                                                                       |
| `accept_tos`           | Accept the platform Terms of Service. **Required before any write call** if your organization hasn't accepted yet — writes return `403 TOS_ACCEPTANCE_REQUIRED` until this runs.                                                   |
| `ask_about_capability` | Query the API documentation to look up correct endpoint paths, field names, and request schemas. **Call this before `api_call`** when unsure of the exact interface.                                                               |
| `api_call`             | Dispatches a named buyer `operation` (typed request/response). The method and endpoint are derived from the operation; pass `pathParams`, `params`, and `body` as needed. Call `ask_about_capability` to find the right operation. |
| `customer_switch`      | Switch the active account context. Available to any user with membership in more than one account; SuperAdmin users can switch to any account.                                                                                     |
| `admin`                | Platform-management operations (SuperAdmin only).                                                                                                                                                                                  |

<Note>
  **Required workflow for AI agents:** Choose the most specific MCP tool first. For account context, call `customer_switch` directly instead of routing through `api_call`. For REST operations, call `ask_about_capability` first when unsure of the exact interface, then call `api_call` with an `operation` name. Field names are camelCase (e.g. `advertiserId`, not `advertiser_id`).
</Note>

### Switch Account

Account switching is session state. It is not a per-call REST parameter and is not available through `api_call`.

To operate on a different account in the same MCP session, call the dedicated `customer_switch` MCP tool:

```json theme={null}
{
  "tool": "customer_switch",
  "arguments": { "customerId": 123 }
}
```

After switching, retry the original `api_call` without `customerId` in `params` or `body`. If the target account is not available to the current MCP session, re-authenticate with that account's credentials.

### Using `api_call`

The `api_call` tool dispatches a named buyer `operation`. It handles authentication automatically and derives the HTTP method and endpoint from the operation, so you never write a URL. Pass `params` (query), `pathParams`, and `body` as the operation requires.

**MCP Example - List Advertisers:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "api_call",
    "arguments": {
      "operation": "list_advertisers",
      "params": { "includeAccounts": true }
    }
  }
}
```

**MCP Example - Create Advertiser:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "api_call",
    "arguments": {
      "operation": "create_advertiser",
      "body": {
        "name": "Acme Corp",
        "brand": "acme.com"
      }
    }
  }
}
```

**MCP Example - Get Reporting:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "api_call",
    "arguments": {
      "operation": "get_reporting_metrics",
      "params": { "advertiserId": "12345", "startDate": "2026-01-15", "endDate": "2026-01-21" }
    }
  }
}
```

### Supported Endpoint Families

The `api_call` tool allows access to:

| Endpoint Family                         | Example Operations                                                                              |
| --------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `/api/v2/buyer/advertisers/*`           | Advertiser CRUD, event sources, test cohorts, measurement config, accounts, catalogs, offerings |
| `/api/v2/buyer/campaigns/*`             | Campaign CRUD, execute, pause                                                                   |
| `/api/v2/buyer/discovery/*`             | Product discovery and selection                                                                 |
| `/api/v2/buyer/campaigns/*/creatives/*` | Manifest-based creative lifecycle (nested under campaigns)                                      |
| `/api/v2/buyer/media-buys/*`            | View media buy status and delivery                                                              |
| `/api/v2/buyer/reporting/*`             | Impressions, spend, clicks metrics                                                              |
| `/api/v2/buyer/sales-agents/*`          | Partner agent discovery and account registration                                                |
| `/api/v2/buyer/signals/*`               | Audience signal listing                                                                         |

***

## Response Format

All responses follow a consistent structure:

```json theme={null}
{
  "data": { ... },
  "error": null,
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}
```

**Error Response:**

```json theme={null}
{
  "data": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "Advertiser not found"
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}
```

***

## Support

For technical support, contact us at [support@apostra.com](mailto:support@apostra.com).
