This page covers v2 REST endpoints. MCP tool errors follow the ADCP error
spec and
are returned in
structuredContent rather than HTTP status codes.Error envelope
Every non-success response hasdata: null and a populated error object:
Successful responses have the inverse shape:
{ "data": <result>, "error": null }. List endpoints add a meta block (see Pagination).
Throughout the Buyer and Storefront task references, the
## Response examples
show the data payload only — the inner result. On the wire it is always
wrapped in the envelope above: { "data": <payload>, "error": null }, plus a
meta block on list endpoints. Read the result from response.data.HTTP status codes
Common error codes
Domain-specific codes you may encounter on campaign, currency, and reporting endpoints:
Buyer identity and storefront account errors
Buyer operations that need a seller-side account fail closed when the buyer has no confirmed operator identity. REST returns HTTP409 with
BUYER_SETUP_REQUIRED; for a missing identity, branch on this exact structured
payload:
interchange.io as a buyer operator. Doing so
would collapse unrelated buyers into one seller-side natural-key account.
Complete Buyer Setup and retry instead. Historical media buys are not
rewritten: status, creative re-sync, update, cancellation, and delivery keep
using the seller account persisted on the buy.
A legacy buyer profile that explicitly contains the platform domain returns
the same BUYER_SETUP_REQUIRED code with details.reason: "platform_operator_not_allowed". This is observable on the same buyer
surfaces as missing_operator_identity; replace the platform domain in
Buyer Setup rather than retrying it.
The Storefront MCP surface uses the ADCP error envelope rather than the REST
envelope above. When a request supplies an opaque account_id that is not
authorized for the authenticated buyer on that storefront, it returns:
account_context_not_found, and do not retry with an
account ID discovered by another buyer. Use list_accounts as the current
buyer or complete the seller’s account-linking flow.
Buyer-actionable errors on create_media_buy, update_media_buy, sync_creatives
Buy-flow errors carry an optional buyer_reason: { code, message } sub-object on
the ADCP error envelope (AdCP 3.2 core/error.json). It classifies the failure
in a way the buyer can act on — creative-validation issues, budget/product/
permission problems — with a message that is safe to render directly (no
vendor identifiers, no internal IDs, no stack traces). When buyer_reason is
present, the envelope’s recovery (transient | correctable | terminal) is
set alongside per spec and MUST agree with the buyer_reason’s classification.
code uses the AdCP enums/error-code.json vocabulary
(BUDGET_TOO_LOW, PRODUCT_NOT_FOUND, PRODUCT_UNAVAILABLE,
PERMISSION_DENIED, ACCOUNT_SETUP_REQUIRED) or an X_{VENDOR}_{CODE}
extension (Apostra extensions: X_SCOPE3_CREATIVE_SIZE_MISMATCH,
X_SCOPE3_CREATIVE_MISSING_CLICK_URL,
X_SCOPE3_CREATIVE_VALIDATION_FAILED_GENERIC). Receivers MUST preserve
unknown codes for forward compatibility — a newer AdCP standard code or a
seller-defined extension is passed through end-to-end.
buyer_reason may also appear on each entry of details.per_source
so a per-leg diagnosis is available when a fan-out mixed causes; each entry
carries its own recovery peer. On sync_creatives, buyer_reason +
recovery appear on each failed creative row’s errors[] entry when the
source classified the batch failure.
Prefer reading the structured buyer_reason.code for programmatic routing
(retry, mutate-and-resubmit, escalate) and use buyer_reason.message for
what to show a human.
Flight window elapsed on an approval-gated storefront
Some publisher storefronts hold an incomingcreate_media_buy for a human
operator to approve before it reaches the seller. Approval and dispatch are
separate moments: the buy waits in the operator’s queue, and only once it is
approved does the platform send it on. That wait can be long — minutes while
an operator reviews, or longer if the publisher has paused intake.
If a buy’s end_time has passed by the time it is dispatched, no seller can
accept it. The platform stops before contacting any seller and fails the buy’s
task with details.forward_error.code of flight_window_elapsed:
create_media_buy with a current
end_time. Retrying the same buy will not help and is not attempted on your
behalf: this media buy is finished, and the platform will not send it to a
seller under any later condition. The recovery of correctable refers to the
request — a corrected flight window succeeds — not to the buy that failed.
Why you may not have seen it before. A buy in this state used to be retried
against the seller for several hours, rejected each time, and then left
approved but never placed with no reason recorded. It now fails on the first
dispatch attempt with the message above, so the flight window is the thing you
fix rather than something you have to infer from silence.
A
start_time that has merely slipped into the past is not an error. The
platform re-reads the flight window at dispatch and sends a past start as
"asap" — start on acceptance — so a buy whose start passed while it sat in
the approval queue still runs. Only an elapsed end_time ends the buy.MCP Page capability errors
Host-only Page aliases return the ADCPACCESS_DENIED tool error when their
session-bound capability is missing, expired, belongs to another Page or
session, or the requested arguments fall outside that Page’s exact allowlist.
The error is returned in MCP structuredContent, not the REST envelope.
For an expired capability, the Page caller makes one renewal attempt with the
tool associated with the same Page resource and, if renewal succeeds, retries
the unchanged operation once. If renewal fails, preserve the original denial.
Do not retry a wrong-resource or off-policy call through another Page. In
Teach, ACCESS_DENIED also prevents the Page from rejecting a
destination-owned Material candidate. Teach presents that candidate as a
handoff. A direct MCP client may record rejection with public save_material;
acceptance still goes through the candidate’s named canonical typed owner.
Validation errors
When request validation fails,code is VALIDATION_ERROR and details.issues enumerates every problem Zod found, with dotted field paths:
field is set instead:
Handling errors in client code
Retrying safely
RATE_LIMITED, INTERNAL_ERROR, SERVICE_UNAVAILABLE, and FX_RATE_UNAVAILABLE are transient — retry GETs with exponential backoff. For creation/mutation requests after a 5xx, prefer to surface the error rather than auto-retry, since duplicate-create protection isn’t enforced server-side.
VALIDATION_ERROR, NOT_FOUND, FORBIDDEN, ACCESS_DENIED, CONFLICT, and SPEND_DENOMINATION_UNRESOLVED are terminal — don’t retry until the input or state changes.
CONFLICT has two narrow automatic-retry reasons, and they are machine-readable rather than a matter of reading the message. name_creation_in_progress means another writer holds that name and has not finished. adoption_in_progress means the source-identity state is still settling: another adoption is completing or its blocker changed during classification. Retry the identical request shortly for either reason. Every other adoption reason requires a changed input or state: name_taken and name_creation_abandoned require choosing another name or archiving the blocker; adoption_abandoned means a stale creation marker never cleared (an asset row may already exist), and adoption_asset_unavailable means the finalized asset is no longer usable; adoption_name_mismatch requires using the existing creative name or renaming it separately; and source_delivery_revoked or source_deletion_requested requires uploading a new source. MCP classifies those action-required cases as correctable, not transient. Branch on details.reason, never on the message.