get with kind: "agent") links to a section here. It is for the developer of
the agent. Create an agent in a Developer account
lists every finding code and what it means.
We use evidence from our own calls to your endpoint: the
get_adcp_capabilities, sync_accounts and get_products calls we make, and
the requests we send while discovering inventory and handling buys. We measure
the response your agent sends, its timing, its task progress and whether the
products it returns honour the request. A self-reported claim is not proof.
How certification works explains what makes an agent ready,
what each use case judges and what every result means.
Required declarations
AdCP 3
What it is. Yourget_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.
AdCP 3 is enough to certify today. From 1 February 2027, four capabilities
become required. See Getting ready for AdCP 4.0
for the schedule, the checks we run and what to change.
A supported billing model
What it is. Declareagent, 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. We then check that your Agent accepts the billing models it declares:
a sync_accounts request for a declared model that your Agent refuses shows as a
Declarations match behaviour finding.
Takes buyer account requests
What it is. Your Agent must list bothsync_accounts and list_accounts
and must not require operator authentication
(account.require_operator_auth: true). Buyers declare their own accounts:
sync_accounts receives a buyer’s account request, and list_accounts lets us
read the result back.
Why buyers care. Every buy runs on an account. A buyer can only start
buying from you once your Agent can open an account for them.
How we measure it. We read the tool list from your
get_adcp_capabilities response. The Agent page shows the result as Takes
buyer account requests in its required capability declarations.
What happens when it is missing. When your Agent is the only source behind
a storefront, we cannot open an account for a new buyer, so:
- Certification skips with
seller_sandbox_account_failed(orseller_operator_auth_unsupported), because it sets up its test account the same way. - An advertiser that does not already have an active account with you no longer sees your products in discovery and cannot book with you.
- Advertisers whose accounts you already accepted keep discovering and buying as before.
Creative formats we supply
What it is. At least one Product must declare a format. It can declare a canonicalformat_options[].format_kind that we support (display_tag,
video_vast, or audio_daast), or legacy format_ids.
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.
Behaviour findings and how to fix them
Each row in the Agent’s Agent-wide behaviour list links to one section below. Every section gives the threshold, the usual causes, and the next step. We apply each threshold to every Source bound to the Agent separately, over its own seven days. The Agent-wide row shows the worst result among those Sources and adds up their counts. So “20 handoffs were still unfinished across one Source” means that Source has 20, not that the Agent has 20 in total. Open Source detail to see which Source needs work. A finding that says Not yet evaluated has no matching calls in the last seven days, so it is neither passing nor failing. It clears after we make the kind of call named in its section.Responsiveness finding
Threshold. For each Source, over seven days:- Blocker: more than 5% of at least 20
get_productscalls timed out. A call times out when it returns neither a result nor a task handle before the response deadline. By default that deadline is 45 seconds, counted from the moment we send the request. A buyer can set a shorter or longer budget. - Flag: the p95 time to answer
get_productsis over 10 seconds, and at least one call that took more than 10 seconds returned no task handle.
- The Agent builds the whole catalog during the request, for example by calling
the upstream ad server live on every
get_products. - The upstream ad server is slow or rate limiting the Agent.
- Connection setup and cold starts use part of the deadline.
- The Agent does not use a task handle, so a slow request can only succeed or time out.
get_products calls in your own logs
for the window. If the Agent cannot answer within about 10 seconds, return
submitted or working with a task handle straight away and finish the work in
the background. Where you can, serve the catalog from a cache you refresh on a
schedule.
Async completion finding
Threshold. A handoff is stale when your Agent answeredsubmitted,
working or pending, and five minutes after our request it had not reported a
final status. Per Source: one or two stale handoffs is a flag; three or more
is a blocker. The five minutes is fixed today and is not a per-task deadline
you can set.
A final status is completed, failed, rejected, canceled or
input-required. A working update does not count. A task that finishes late
stops counting at the next evaluation, once we have recorded its final status.
Usual causes.
- The job was lost after the Agent returned the task handle, for example because of a restart, a deploy, or a queue that drops work.
- The Agent finished the work but never recorded the final status where polling reads it, or never sent it to the webhook.
- The webhook call failed, for example on a wrong URL or an expired credential, and was not retried.
- The work routinely takes longer than five minutes and only sends
workingupdates.
input-required, failed or completed as early as you can
rather than only working.
Call success rate finding
Threshold. Over seven days, for each Source, the share of our calls that failed for a reason other than a timeout. A call fails when your Agent returns an error, rejects the request, or cancels it. Flag above 2%. Blocker above 20%, or when every call failed. This finding is Not yet evaluated until we have made at least one call. Usual causes.- Validation that is stricter than AdCP, so your Agent rejects a valid request.
- Credentials for the upstream ad server that have expired or lost access.
- An account that does not exist or is not active on your side.
- An unhandled error inside the Agent, returned as a server error.
Honours request finding
Threshold. We do not calculate this finding yet, so it never shows Flag or Blocker today. When we do, any breach will be a flag and three or more within the seven days will be a blocker. Usual causes. Products returned outside the property list, channels or countries we sent, for example because the Agent ignores a filter it does not support but did declare. Next step. No action is needed for this finding today. Make sure returned Products respect the property list, channels and countries in each request, and that you declare only the targeting you honour.Account references resolve to one account finding
Threshold. Per Source:ACCOUNT_AMBIGUOUS returned once or more is a flag.
It is a blocker when it appears on three or more separate days in the seven
days.
Usual causes.
- Two accounts on your side share the same brand domain and operator, often
because
sync_accountscreated a duplicate (see Account sync is idempotent finding). - The lookup matches on brand alone and ignores the operator or the sandbox flag.
account_id on each account from sync_accounts; we then
address the account by id instead of brand and operator.
Payment terms finding
Threshold. Counted for accounts we requested with agent billing andpayment_terms. A missing billing or payment_terms, or an existing account
kept on different terms, is a flag. A new account returned active on
different terms is a blocker. The full rules are in
Return and honour payment terms on agent-billed accounts.
Usual causes.
- The Agent gives every new account its own default terms and ignores the terms we request.
- The account API your Agent calls cannot store payment terms.
- The response leaves out
billingorpayment_terms.
payment_terms or reject the account. Never return it active on
other terms. Return billing and payment_terms on every such account in
sync_accounts and list_accounts.
Account sync is idempotent finding
Threshold. Per Source: a repeatsync_accounts that creates a second account
for the same brand domain, operator and sandbox flag, or a list_accounts
roster that lists one key more than once, is a flag. It is a blocker when
this appears on three or more separate days in the seven days.
Usual causes.
- The Agent keys accounts on a generated id, or on brand alone, instead of brand domain, operator and sandbox flag.
- A retry after a timeout runs the create again.
- The sandbox flag is left out of the key, so a sandbox account and a live account collide, or the reverse.
action: unchanged or
updated and the same account_id. Merge the duplicates you already hold. Once
each key is listed once, the finding clears as the seven-day window moves on.
Declarations match behaviour finding
Threshold. Today this finding checks billing. We count eachsync_accounts
request we sent in the seven days for a billing model you list in
supported_billing. A request your Agent refused with
BILLING_NOT_PERMITTED_FOR_AGENT or BILLING_NOT_SUPPORTED is a mismatch, whether
the code came on the call itself or on the account in your answer. Any mismatch
is a flag. A mismatch seen on three or more separate days is a blocker.
When we sent no request for a billing model you declare, the finding is not
evaluated.
Usual causes.
supported_billinglists a billing model, such asagent, that your platform is not set up to accept for buyers.- The billing model is accepted only for some buyers or operators, but is declared for all.
supported_billing, or
remove the ones you refuse. Buyers choose a billing model from your declaration,
so a model you declare and then refuse leaves their account unopened. Once your
Agent stops refusing a declared model, the finding clears as the seven-day window
moves on. See Keep capabilities current.
Machine-readable errors finding
Threshold. Over seven days, any error we receive without a recognised AdCP error code is a flag. This finding is never a blocker on its own. Timeouts do not count here. Usual causes.- The Agent returns plain text, an HTTP status or a stack trace in place of an AdCP error.
- The Agent passes through the upstream ad server’s error, or uses its own code that AdCP does not define.
Practices behind the findings
Return and honour payment terms on agent-billed accounts
What it is. When we ask for an account withbilling: "agent" and
payment_terms (for example net_60), do one of two things: accept those
terms, or reject the account. Never return the account as active on
different terms. On every account in your sync_accounts and list_accounts
responses for an account we requested this way, return billing and
payment_terms so we can see what you hold. AdCP states that the seller
“must either accept these terms or reject the account — terms are never
silently remapped.”
Why buyers care. The buyer’s organization chose its payment terms. An
account on other terms means invoices that don’t match what they agreed.
What Apostra does when an Agent doesn’t. We refuse a new account that
comes back on other terms. It is not activated, and it is held for the buyer
to review the seller’s terms. An existing account stays usable, and the buyer
sees a warning that names both the requested terms and the terms your Agent
kept.
How we measure it. We compare the accounts in your sync_accounts and
list_accounts responses over the last seven days with the terms we
requested. Only an account we requested with agent billing and payment_terms
in that window counts. An account we requested without terms is on your default
terms, as AdCP says, and isn’t counted. An account we never requested in the
window isn’t counted either, and a response from before our request doesn’t
count against the account. We don’t assume what your default terms are. For a
counted account, a missing billing or payment_terms is a flag, and an
active account on different terms is a blocker when your Agent created it or
activated it on those terms, and a flag when your Agent already held it. Your
Agent already held it when its sync_accounts response reports
action: unchanged, or action: updated after an earlier response held the
account active. A response that reports action: created or no action counts
as a new account. An account that was a blocker at any point in the seven days
stays one. A counted account whose listing has no billing counts as missing
billing. We
count accounts, not responses: an account is identified by its brand domain,
operator and sandbox flag (and its brand id and operator unit, when you return
them), however often it appears in your responses. An
account counts as missing a field only while its latest response in the window
lacks it, so it stops counting once your next response is complete. From
list_accounts we read only your most recent roster, which is every page of
your latest complete listing, so an account that has dropped out of it isn’t
counted. An account
returned on different terms stays counted for the seven days. This
check reports today and does not yet stop new buys.
Sync accounts idempotently
What it is. The account key is the brand domain, the operator and the sandbox flag. When we sendsync_accounts for a key your Agent already holds,
return that same account with action: unchanged (or updated, if something
changed) and the same account_id. Create an account only the first time the
key appears. list_accounts lists each key once.
Why buyers care. A buyer syncs the same account again whenever their setup
changes. If each call creates a new account, the buyer’s buys, approvals and
invoices spread across accounts nobody asked for, and the seller cannot tell
which one is current.
How we measure it. Over the last seven days we look for two things: two or
more sync_accounts responses that report action: created for one key under
different account_id values, and a list_accounts response that holds more
than one account for the same key and sandbox flag. We read a list_accounts
roster as every page of one listing, joined by pagination.cursor, and use the
latest listing whose last page returns has_more: false; a listing that stops
early isn’t used. Seeing either is a flag.
Seeing it on three or more days is a blocker. A sandbox account and a live
account for the same domain are different keys. Once your Agent merges the
duplicates and lists each key once, the roster signal clears. This check
reports today and does not yet stop new buys.
Declare AdCP channels on every Product
What it is. Every Product declares the AdCP media channels it sells inchannels, 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 (storefront readiness reports it as the check Product channels declared), the agent read shows it as the advisory finding declaration.product_channels, 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>. This is a best practice: a
missing channel produces a warning and never blocks your Agent or fails its
certification. We do not refuse an external Agent’s Products for missing
channels. AdCP is considering making channels required on every Product in
AdCP 4.0
(adcontextprotocol/adcp#7869);
if it does, AdCP compliance will require it.
Source reporting health
What it is. Every inventory source that uses your agent is expected to deliver its delivery reports on time. Apostra reads each source’s reporting state as one of five values:
Why buyers care. Buyers reconcile spend and delivery from these reports.
A report that does not arrive leaves the buy open.
How we measure it. We count the states across the agent’s authorized
inventory sources. The finding
reporting.sources is an attention finding when
any source is action_required, and passes when none is. Nothing stops: running
buys continue. The inventory sources page shows each source’s own state. See
Reliable Reporting
for the capability that makes reports dependable.
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.What a behaviour finding does
A flag is anattention finding: it tells you what we observed and how to
fix it. A blocker is blocking only for a check that Apostra enforces, and
only while enforcement is on for your account. For every other check it is an
attention finding, and running buys continue. get with kind: "agent" shows
each finding’s severity, and
Findings: what needs your attention
says what each severity means. How certification treats live traffic is in
How certification works.