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

# Sales Agent requirements and best practices

> The declarations and live behaviour Apostra requires from a Sales Agent

This page explains what Apostra requires from a Sales Agent before we send it
buyer work, and the practices that make that work dependable. It is for the
developer of the Agent, not for a sales or onboarding questionnaire.

## What is enforced today

Routing buys by declared features is being rolled out. Today, when a buy has a
property list, we send the list only to Agents that declare property-list
support. We may still return Products from an Agent that does not declare
support, and may send that Agent a buy without the property list. Declare every
feature your Agent honours so the routing can apply it correctly.

The seven-day behaviour baseline below is our published requirement, but we do
not yet calculate or enforce it automatically. We are rolling out automated
checks for it; today, our team reviews the calls we make to your Agent with
you. Do not treat a missing automated check as permission to skip a requirement.

## How we assess an Agent

We use evidence from our own calls to your endpoint. That includes the
`get_adcp_capabilities`, `sync_accounts`, and `get_products` calls we make, as
well as requests made while discovering inventory and handling buys. We measure
the response your Agent sends, its timing, task progress, and whether returned
products honour the request. We do not accept a self-reported claim as proof
that an Agent behaves this way.

## Required declarations

### AdCP 3

**What it is.** Your `get_adcp_capabilities` response must include major
version 3 in `adcp.major_versions`. Responses must validate against the schema
for the version we negotiate.

**Why buyers care.** A buyer needs the same contract that your Agent says it
supports, so a discovery result or buy does not fail when it reaches your
system.

**How we measure it.** We call your capabilities endpoint and validate the
responses we receive at the negotiated version.

### A supported billing model

**What it is.** Declare `agent`, `operator`, or both in `supported_billing`.
This is a seller-wide declaration, selected for each account in
`sync_accounts`. We review `advertiser` billing case by case.

**Why buyers care.** The billing model determines who can operate the buy and
settle it without a manual intervention after the buyer is ready to transact.

**How we measure it.** We read the billing declaration from the account and
capability responses we call, rather than relying on a separate statement from
your team.

### A creative kind we supply

**What it is.** At least one Product must accept one of the creative kinds we
supply in `format_options`: `display_tag`, `video_vast`, or `audio_daast`.

**Why buyers care.** A buyer can only place a buy if Apostra can deliver a
creative that the Product accepts and can measure.

**How we measure it.** We inspect the Products returned by our own
`get_products` calls.

## Per-buy features

### Property-list targeting

**What it is.** Property lists are a per-buy feature. In AdCP 3.1, declare
`media_buy.features.property_list_filtering` and, for each applicable Product,
`property_targeting_allowed`. If your Agent negotiates AdCP 3.2, also declare
`media_buy.execution.targeting.property_list` and
`property_list_exclude`, plus `overlay_support.property_list` on each
applicable Product.

**Why buyers care.** A property list lets a buyer direct a buy to the intended
publishers, stations, or networks. It applies to every channel, including
linear TV and radio.

**How we measure it.** We read these declarations from the capability and
Product responses we call. On buys that include a property list, we also check
that the returned Products honour the property list, channels, and countries
we sent.

Property-list support is a per-buy requirement that is being rolled out; it
does not block an Agent. Today, we send a property list only to an Agent that
declares support. We may still return Products from an Agent that does not
declare it and send that Agent a buy without the list. As feature routing rolls
out, a buy that needs a property list will go only to Agents that declare
support, while Agents that do not declare it can still receive buys that do not
need it. Declare every feature your Agent honours.

## Required behaviour

We assess these behaviours over the preceding seven days. A failure in this
baseline applies to the Agent as a whole, not to one Product or one use case.

| Behaviour | Requirement and threshold | Why buyers care | How we measure it |
| - | - | - | - |
| Responsiveness | `get_products` returns an answer or task handle within 10 seconds. We flag a p95 over 10 seconds without a handoff, and block when more than 5% of at least 20 calls time out. | Buyers need discovery to return promptly, even when longer work moves to a task. | The elapsed time and result of our calls to your Agent. |
| Async completion | A handed-off task completes, or sends status, before its deadline. One stale handoff is a flag; repeated stale handoffs are a blocker. | Buyers need a task to reach a useful outcome rather than disappear after acknowledgement. | The task handle, status, deadline, and completion returned through polling or webhook. |
| Call success rate | At most 2% of calls fail for reasons other than timeout. We flag a rate above 2%, and block above 20% or when every call fails. | Buyers need routine discovery and buy operations to work consistently. | The outcomes of our calls, excluding timeout failures from this row. |
| Honours what we send | Returned Products respect the property list, channels, and countries in our request. Any breach is a flag; repeated breaches are a blocker. | Buyers must not see inventory outside the audience and channel they asked for. | The request we sent compared with the Products your Agent returned. |
| Account resolution | An account reference resolves to exactly one account. `ACCOUNT_AMBIGUOUS` is a flag; a persistent condition is a blocker. | Buyers need a buy to reach the intended seller account. | The account-resolution result from our calls. |
| Declarations match behaviour | Your Agent does what it declares. A mismatch is a flag; a persistent mismatch is a blocker. | Buyers need declared capabilities to predict what an Agent will do. | The capability and Product responses compared with observed requests and results. |
| Machine-readable errors | Use AdCP error codes. Errors we cannot classify are flagged, but this is a best practice and never a blocker on its own. | Buyers and their systems can recover or give a useful next step when failure is clear. | Error responses returned to our calls. |

**Repeated** means three or more events within the seven-day window. For this
baseline, that applies to stale async handoffs and breaches of the request.
**Persistent** means that the flagged condition appears on three or more
separate days within that window.

### Async tasks

AdCP 3.1 has no capability field for async tasks. We therefore assess them by
behaviour, not declaration. If a request cannot finish in time, return
`submitted` or `working` with a task handle. Then complete the task, or send a
status update, before its deadline through polling or webhook. The
responsiveness and async-completion rows above are how we assess that handoff.

## What happens when we find a problem

A **flag** tells you what we observed and how to fix it. A **blocker** stops
new buys for every use case on the Agent. Running buys continue in both cases.
This protects buyers from starting another buy while preserving an active buy
that needs to complete, report, or be stopped safely.

Automated flags and blockers for the behaviour baseline are not live yet. Until
they are, our team reviews the evidence from our calls with you. The thresholds
on this page remain the standard we use for that review and for the automated
checks now being rolled out.

## Choose the sandbox account to use for certification

Before live-traffic certification can run its test buys for an Agent source,
choose one account that the Sales Agent currently returns from `list_accounts`
as an active sandbox account. The setting uses the same
seller-account authorization as other inventory-source changes. It allows a
principal acting in a Seller Account, including signed-in seller users, staff
switched into the seller account, connected chat assistants acting through MCP OAuth, user-owned API
keys, and agent, hosted-runtime, acting-user, or agent-registration contexts
when they carry an attributed user. The principal type does not change this
seller-account check.

Every change must also identify a user for the audit record. Seller-scoped
organisation API keys and other machine credentials without an attributed user
cannot choose or clear the certification sandbox account. Buyer and parent
account contexts are refused by the same seller-account authorization used for
other source changes. System credentials and SuperAdmins receive the normal
seller-account role bypass, but still need an attributed user to make this
change. Callers from another seller tenant cannot access that seller’s Source.
Use the REST API at
`/api/v2/storefront/inventory-sources/{sourceId}/certification-sandbox-account`,
or by asking a connected chat assistant, which uses `save_inventory_source`.
The certification sandbox account records the specific account used for
certification. Before every certification buy, we check that the Sales Agent
still reports it as active and `sandbox: true`. This
prevents test buys from being sent to an account that the seller has not
explicitly identified as safe for certification.

Use `POST /api/v2/storefront/inventory-sources/{sourceId}/certification-sandbox-account`
with `{ "accountId": "seller-sandbox" }`. `GET` on the same path returns the
certification sandbox account, including its verification time and the user who
last chose it, plus the sandbox accounts currently reported by the Source. Use
`DELETE` on the same path to clear the certification sandbox account.

In v3, call `save_inventory_source` with the existing Source `id` and
`certificationSandboxAccountId`, for example
`{ "id": "my-sales-source", "certificationSandboxAccountId": "seller-sandbox" }`.
Set `certificationSandboxAccountId` to `null` to clear the certification
sandbox account.
Read it back with `get({ kind: "inventory_source", id: "my-sales-source",
include: ["certificationSandboxAccount"] })`.

Managed sales-agent sources do not use this setting. Certification uses the
certification buyer's admitted sandbox storefront account instead. The account
must be active and marked `sandbox: true`, and it must match the buyer's
operator and the certification advertiser's brand. We send the buy and any
creative sync through the normal managed path, which resolves the managed
sales-agent account when it creates the buy. There is nothing for the seller to
choose or clear on a managed source.

If there is no matching active sandbox storefront account, certification skips
that family with `sandbox_buyer_account_missing`. This includes an account with
the matching operator and brand that is not marked as a sandbox account.

For managed certification, name a no-charge test advertiser in your ad server.
The certification buyer's sandbox storefront account is mapped to that advertiser
through the normal Buyer Account Mapping decision as an active, dedicated binding.
The buy runs on your real serving path; the platform never bills it, and you
provide the no-charge test placement. Certification does not use a shared default,
an unbound native account, or a `not_required` binding.

## Best practices

These practices are not separate eligibility requirements, but they make an
Agent easier to integrate and recover when something fails.

### Return AdCP error codes

**What it is.** Return the applicable AdCP error code whenever an operation
fails.

**Why buyers care.** A structured error lets buyer software decide whether to
retry, correct the request, or show the buyer the next action.

**How we measure it.** We classify the errors returned by our calls. An error
we cannot classify is visible in the behaviour review above.

### Return an account ID from `sync_accounts`

**What it is.** When `sync_accounts` creates or confirms an account, return
its `account_id` on that account's row. Apostra stores the ID and sends
`account: { account_id }` on later `get_products` and `create_media_buy`
calls for that account, instead of the brand and operator.
Keep the ID stable for the life of the account. A response row that names a
different `brand_id` from the one we sent, or reports a failed action, is not
taken as the account's ID.

**Why buyers care.** An ID names exactly one account. If the same brand and
operator match more than one account on your side, the brand and operator
alone return `ACCOUNT_AMBIGUOUS`, and the buyer's discovery or buy fails.

**How we measure it.** We read the `account_id` from the account rows in your
`sync_accounts` responses, and the account-resolution results of the calls
that follow.

### Hand off long work quickly

**What it is.** Return `submitted` quickly when work needs more time, with a
task handle that callers can poll or receive by webhook.

**Why buyers care.** The buyer gets a durable acknowledgement instead of
waiting for a request that will exceed the response window.

**How we measure it.** We record the initial response time, task handle, and
later task status from our calls.

### Declare AdCP channels on every Product

**What it is.** Every Product declares the AdCP media channels it sells in
`channels`, such as `display`, `olv`, `ctv`, or `streaming_audio`. A Product
that sells different channels through different formats can instead declare
`applies_to_channels` on each format option. A format type on its own, such as
a video or audio format, is not a channel, because several channels share a
format.

**Why buyers care.** Buyers filter discovery, route proposal requests, and
report delivery by channel. A Product with no channel is missing from
channel-filtered discovery, and its delivery reports with no channel.

**How we measure it.** We read `channels`, then `applies_to_channels`, on the
Products returned by our `get_products` calls. Storefront readiness counts the
Products that declare neither (see
[Diagnostics](/v2/storefront/inventory-sources/diagnostics)), your Agent page
shows the same count as an advisory note under **Catalog Product Quality**, and live-path
certification skips such a Product with the reason `missing_channels` rather
than certifying it on a guessed channel. When a Product relies on
`applies_to_channels` and only some of its format options declare it, the
other formats are skipped with the reason `missing_channels:<format_kind>`. Today this is a best practice: a
missing channel produces a warning and never blocks your Agent. Channels will
become required for external Agents' Products in a later change, announced
before it takes effect.

### Declare every targeting dimension you honour

**What it is.** Declare each targeting dimension your Agent and Products will
honour, and do not declare dimensions that your upstream system ignores.

**Why buyers care.** Buyers use declarations to choose inventory that can meet
their brief. An incomplete declaration excludes compatible buys; an inaccurate
one can route a buy that cannot meet its targeting.

**How we measure it.** We compare declared capabilities with the request and
Products returned by our discovery and buy calls.

### Keep capabilities current

**What it is.** Update capability, account, and Product declarations whenever
the upstream system changes what your Agent can do.

**Why buyers care.** Current declarations prevent a buyer from selecting
inventory that has changed, disappeared, or no longer supports a required
feature.

**How we measure it.** We use the current responses from our calls and compare
them with the behaviour we observe. If the two disagree, the declarations-match-
behaviour requirement applies.

For the full build and launch path, see [Build and launch a Sales Agent](/v2/storefront/inventory-sources/build-sales-agent).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.