Documentation Index
Fetch the complete documentation index at: https://agenticadvertisingorg-changeset-release-main.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Sync advertiser accounts with a seller for one or more brand/operator pairs. The seller provisions or links accounts, returning per-account status and any setup instructions. Brands are identified by a brand object containing domain + optional brand_id, resolved via /.well-known/brand.json.
sync_accounts is used across all seller protocols: media buy agents, signals agents, governance agents, and creative agents. It declares the buyerβs intent β the seller provisions or links accounts internally. For implicit accounts (require_operator_auth: false), use natural keys (brand + operator) on subsequent requests. For explicit accounts (require_operator_auth: true), discover seller-assigned account IDs via list_accounts. For sandbox on implicit accounts, include sandbox: true in the account entry β the seller provisions a test account with no real spend. For explicit accounts, sandbox accounts are pre-existing test accounts discovered via list_accounts.
Response Time: ~1s. Account provisioning is synchronous; credit and legal review may require human action (indicated by status: "pending_approval" with a setup.url).
Request Schema: /schemas/v3/account/sync-accounts-request.json
Response Schema: /schemas/v3/account/sync-accounts-response.json
Quick start
Sync a single advertiser account and check the resulting status:
import { testAgent } from "@adcp/sdk/testing";
import { SyncAccountsResponseSchema } from "@adcp/sdk";
const result = await testAgent.syncAccounts({
accounts: [
{
brand: { domain: "acme-corp.com" },
operator: "acme-corp.com",
billing: "operator",
},
],
});
if (!result.success) {
throw new Error(`Request failed: ${result.error}`);
}
const validated = SyncAccountsResponseSchema.parse(result.data);
if ("errors" in validated && validated.errors) {
throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}
for (const account of validated.accounts) {
console.log(`${account.brand.domain}: ${account.status}`);
if (account.status === "pending_approval" && account.setup?.url) {
console.log(` Complete setup at: ${account.setup.url}`);
}
}
Request parameters
| Parameter | Type | Required | Description |
|---|
accounts | array | Yes | Array of account entries to sync (see below). |
delete_missing | boolean | No | When true, accounts previously synced by this agent but not in this request are deactivated. Scoped to the authenticated agent. Default: false. |
dry_run | boolean | No | When true, preview what would change without applying. Default: false. |
push_notification_config | object | No | Webhook for async notifications when account status changes (e.g., pending_approval transitions to active). |
Account entry fields:
| Field | Type | Required | Description |
|---|
brand | object | Yes | Brand reference identifying the advertiser. Contains domain (house domain where brand.json is hosted) and optional brand_id (for multi-brand houses). See brand-ref. |
operator | string | Yes | Domain of the entity operating on the brandβs behalf (e.g. pinnacle-media.com). When the brand operates directly, set to the brandβs domain. Verified against the brandβs authorized_operators in brand.json. |
billing | string | Yes | Who should be invoiced: operator, agent, or advertiser. Check get_adcp_capabilities for supported_billing to see what the seller accepts at the capability level. The seller must either accept this billing model or reject the request. Sellers MAY additionally reject a value the seller-wide capability accepts when the calling buyer agentβs commercial relationship does not permit it β e.g., a buyer agent onboarded as passthrough-only (no payments relationship β only the operator can be invoiced). The two gates use distinct error codes β BILLING_NOT_SUPPORTED for the seller-wide capability gate, BILLING_NOT_PERMITTED_FOR_AGENT for the per-buyer-agent gate β so agents can dispatch on autonomous-retry vs human-onboarding without parsing prose. See Buyer-agent identity and Billing and Account Setup. |
billing_entity | object | No | Structured business entity details for the party responsible for payment. Contains legal_name (required), plus optional vat_id, tax_id, registration_number, address, contacts, and bank. Bank details are write-only β included in requests but never echoed in responses. See billing entity and invoice recipient. |
payment_terms | string | No | Payment terms for this account: net_15, net_30, net_45, net_60, net_90, or prepay. The seller must either accept these terms or reject the account β terms are never silently remapped. When omitted, the seller applies its default terms. |
sandbox | boolean | No | When true, set up a sandbox account with no real platform calls or billing. Only applicable to implicit accounts (require_operator_auth: false). For explicit accounts, sandbox accounts are pre-existing test accounts discovered via list_accounts. |
notification_configs | array | No | Account-level webhook subscribers for events that outlive any single media buy: creative lifecycle notifications and wholesale feed change webhooks. Omit to leave existing subscribers unchanged; send [] to remove all subscribers; send a full array to replace. |
Natural key: The tuple (brand, operator, sandbox) uniquely identifies an account relationship. {brand: {domain: "acme-corp.com"}, operator: "acme-corp.com"} (direct) is a different account from {brand: {domain: "acme-corp.com"}, operator: "pinnacle-media.com"} (via agency). Adding sandbox: true provisions a sandbox account for the same brand/operator pair β no real platform calls or billing.
Response
Success response:
Returns an accounts array with per-account results. Individual accounts may be pending, rejected, or failed even when the operation succeeds.
Error response:
errors β Array of operation-level errors (auth failure, service unavailable). No accounts array is present.
Note: Responses use discriminated unions β you get either accounts OR errors, never both.
Per-account fields:
| Field | Description |
|---|
brand | Echoed from request. Object with domain and optional brand_id. |
operator | Echoed from request. |
name | Sellerβs display name for the account. |
action | What happened: created, updated, unchanged, or failed. |
status | Current state of the account (see Account status). |
billing | Billing model applied. Matches the requested value. |
billing_entity | Business entity details for the invoiced party, echoed from the request. Sellers may add fields the agent omitted (e.g., registration_number from a credit check) but must not return data from a different entity. Bank details are omitted (write-only). |
account_scope | How the seller scoped this account: operator (shared across brands for this operator), brand (shared across operators for this brand), operator_brand (dedicated to this operator+brand pair), or agent (the agentβs default account). See account scope. |
setup | Present when status: "pending_approval". Contains url for completing credit or legal setup, message explaining whatβs needed, and optional expires_at. |
rate_card | Seller-assigned rate card identifier (when applicable). |
payment_terms | Payment terms agreed for this account: net_15, net_30, net_45, net_60, net_90, or prepay. When the account is active, these are the binding terms for all invoices. |
credit_limit | Maximum outstanding balance as {amount, currency}. |
errors | Per-account errors (only present when action: "failed"). |
warnings | Non-fatal notices. |
sandbox | Whether this is a sandbox account, echoed from the request. Only present for implicit accounts. |
authorization | Optional. The calling agentβs scope grant for this account β allowed_tasks, field_scopes, scope_name, read_only. Applies to every vendor agent type (media-buy, signals, governance, creative, brand) β the Accounts Protocol surface is shared. Present on created, updated, and unchanged results; omitted on failed results. Vendor agents that support scope introspection SHOULD populate this; media-buy sales agents claiming the attestation_verifier standard scope MUST populate it. Absence means the vendor agent does not advertise introspectable scope; callers MUST NOT infer access from absence. See Caller authorization for the full shape. |
Account status
| Status | Meaning | Next step |
|---|
active | Ready to use | Use account reference in protocol operations |
pending_approval | Seller reviewing | Human may need to visit setup.url to complete credit or legal process. Poll list_accounts for updates. |
rejected | Seller declined the request | Review rejection reason in warnings, adjust and retry, or contact seller |
payment_required | Credit limit reached or funds depleted | Add funds or increase credit limit. Route spend to other accounts. |
suspended | Was active, now paused | Contact seller to resolve |
closed | Was active, now terminated | β |
Async notifications
When push_notification_config is provided and the seller returns pending_approval, the seller sends a webhook notification when the account status changes (e.g., approved β active, declined β rejected).
The notification payload includes the (brand, operator) natural key so the buyer can correlate it to the original sync request. For explicit accounts (require_operator_auth: true), the notification also includes the seller-assigned account_id once provisioned.
{
"brand": { "domain": "nova-brands.com", "brand_id": "glow" },
"operator": "pinnacle-media.com",
"status": "active",
"account_id": "acc_glow_001"
}
If the buyer did not provide push_notification_config, poll list_accounts to check for status changes.
Two modes: provisioning vs. settings-update
Each per-account entry uses one of two key shapes, never both:
- Provisioning mode β flat
brand + operator + billing at the entry root. The seller provisions or upserts accounts. Used for implicit accounts (require_operator_auth: false). This is the shape AdCP 3.0 shipped with.
- Settings-update mode β
account (an AccountRef) at the entry root, with brand/operator/billing absent. The seller updates the accountβs settable state β no provisioning side effects. Used for explicit accounts (require_operator_auth: true) where accounts are pre-provisioned and discovered via list_accounts. Implicit-account sellers MAY also accept this mode for settings updates against accounts they previously provisioned.
Schema enforces the exclusivity via oneOf β sending both shapes on the same entry is a validation error. Sellers that donβt implement settings-update mode reject account-keyed entries with UNSUPPORTED_PROVISIONING; sellers that donβt provision (most explicit-account platforms) reject natural-key entries with the same code.
Account-level webhook subscriptions
notification_configs[] carries account-level webhook subscribers for notifications whose lifecycle outlives any single media buy β creative.status_changed, creative.purged, wholesale feed change webhooks (product.*, signal.*, wholesale_feed.bulk_change), and future account-scoped events. Distinct from push_notification_config on this task (account-status webhook), which is one-shot transport for sync_accountsβs own async result.
For these event types, βwholesale feedβ means the sellerβs buyable wholesale product and signals feeds returned by get_products or get_signals; it is not the buyer-provided feeds managed by sync_catalogs.
Permitted in both provisioning and settings-update modes. Declarative semantics: omit to leave existing subscribers unchanged; send [] to remove all subscribers; send a full array to replace.
Each entry has:
subscriber_id β buyer-supplied identifier, unique within the account; echoed on every fire so multi-subscriber accounts can route by endpoint
url β HTTPS endpoint URL. For wholesale feed subscribers, sellers MUST complete an endpoint activation challenge or equivalent proof-of-control before treating the subscriber as active.
event_types[] β types the subscriber wants. Only account-anchored types are permitted (today: creative.status_changed, creative.purged, product.created, product.updated, product.priced, product.removed, signal.created, signal.updated, signal.priced, signal.removed, wholesale_feed.bulk_change). Sellers MUST reject any media-buy-anchored type (scheduled, final, delayed, adjusted, impairment) as a per-account validation failure with INVALID_REQUEST or VALIDATION_ERROR in accounts[].errors[], and error.field MUST point at the invalid event_types entry β those events belong on a media buyβs push_notification_config.
authentication (optional) β legacy Bearer or HMAC-SHA256. Omit to use the default RFC 9421 webhook profile. When present, the same signed-registration downgrade-resistance rules as push_notification_config.authentication apply. Credentials are write-only β sellers omit them on reads.
active (default true) β set false to pause a subscriber without removing the registration. For wholesale feed subscribers, sellers MAY persist the config as inactive until endpoint proof-of-control completes.
Example β register a buyer-side endpoint plus an audit bus on an explicit account:
{
"idempotency_key": "f2c4b7d9-6789-49bc-defa-2345678901bc",
"accounts": [
{
"account": { "account_id": "acc_acme_pinnacle" },
"notification_configs": [
{
"subscriber_id": "buyer-primary",
"url": "https://buyer.example/webhooks/adcp/creative",
"event_types": ["creative.status_changed", "creative.purged"],
"active": true
},
{
"subscriber_id": "audit-bus",
"url": "https://audit.buyer.example/adcp/ingest",
"event_types": ["creative.status_changed", "creative.purged"],
"active": true
}
]
}
]
}
Example β register a wholesale feed mirror subscriber for wholesale product and signal changes:
{
"idempotency_key": "a8af8cf1-89bd-41f3-b27d-7ee7e9f8d2e4",
"accounts": [
{
"account": { "account_id": "acc_acme_pinnacle" },
"notification_configs": [
{
"subscriber_id": "wholesale-feed-sync",
"url": "https://buyer.example/webhooks/adcp/wholesale-feed",
"event_types": [
"product.created",
"product.updated",
"product.priced",
"product.removed",
"signal.created",
"signal.updated",
"signal.priced",
"signal.removed",
"wholesale_feed.bulk_change"
],
"active": true
}
]
}
]
}
Governance agents registered via sync_governance are not implicitly subscribed to these webhooks. If your governance agent should also receive creative-lifecycle fires, register its URL as a separate notification_configs[] entry β explicit, auditable, with its own event_types[] filter.
Verify applied state via list_accounts β the response carries notification_configs[] per account with credentials redacted.
Wholesale feed notifications are registered here, not through a separate subscription task. The webhook body is wholesale-feed-webhook.json: it carries the changed product, signal, or bulk-change summary plus the post-change wholesale_feed_version. Sellers MUST apply the same per-subscriber authorization and scope predicate used by the corresponding wholesale read before emitting each webhook. Receivers MAY apply the payload to local mirrors; use get_products / get_signals with if_wholesale_feed_version to repair missed or distrusted pushes and before binding spend or authority. See wholesale_feed_webhooks for capability declaration and event semantics.
Common scenarios
Agency syncing multiple brands
import { testAgent } from "@adcp/sdk/testing";
import { SyncAccountsResponseSchema } from "@adcp/sdk";
const result = await testAgent.syncAccounts({
accounts: [
{
brand: { domain: "nova-brands.com", brand_id: "spark" },
operator: "pinnacle-media.com",
billing: "operator",
},
{
brand: { domain: "nova-brands.com", brand_id: "glow" },
operator: "pinnacle-media.com",
billing: "operator",
},
],
});
if (!result.success) {
throw new Error(`Request failed: ${result.error}`);
}
const validated = SyncAccountsResponseSchema.parse(result.data);
if ("errors" in validated && validated.errors) {
throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}
for (const account of validated.accounts) {
if (account.status === "active") {
console.log(`Ready: ${account.brand.domain}/${account.brand.brand_id} β ${account.status}`);
} else if (account.status === "pending_approval") {
console.log(`Setup required for ${account.brand.brand_id}: ${account.setup?.url}`);
// Poll list_accounts until status becomes active
}
}
Direct brand purchase
import { testAgent } from "@adcp/sdk/testing";
import { SyncAccountsResponseSchema } from "@adcp/sdk";
const result = await testAgent.syncAccounts({
accounts: [
{
brand: { domain: "acme-corp.com" },
operator: "acme-corp.com",
billing: "operator",
},
],
});
if (!result.success) {
throw new Error(`Request failed: ${result.error}`);
}
const validated = SyncAccountsResponseSchema.parse(result.data);
if ("errors" in validated && validated.errors) {
throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}
const account = validated.accounts[0];
if (account.status === "active") {
console.log(`Ready: ${account.brand.domain} β ${account.status}`);
} else if (account.status === "pending_approval") {
console.log(`Setup required: ${account.setup?.url}`);
// Poll list_accounts until status becomes active
}
Handling rejection
When a seller declines a request, the account entry has status: "rejected":
import { testAgent } from "@adcp/sdk/testing";
import { SyncAccountsResponseSchema } from "@adcp/sdk";
const result = await testAgent.syncAccounts({
accounts: [
{
brand: { domain: "acme-corp.com", brand_id: "clearance" },
operator: "acme-corp.com",
},
],
});
if (!result.success) {
throw new Error(`Request failed: ${result.error}`);
}
const validated = SyncAccountsResponseSchema.parse(result.data);
if ("errors" in validated && validated.errors) {
throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}
for (const account of validated.accounts) {
if (account.status === "rejected") {
console.log("Account request was rejected");
if (account.warnings?.length) {
console.log(`Reason: ${account.warnings.join(", ")}`);
}
}
}
Error handling
| Error Code | Description | Resolution |
|---|
ACCOUNT_NOT_FOUND | Referenced account does not exist or is not accessible | Check account_id or re-sync |
BILLING_NOT_SUPPORTED | Seller-wide capability gate (supported_billing does not include the value) or per-account-relationship gate; see Billing and Account Setup | Check get_adcp_capabilities for supported_billing, adjust or omit billing; inspect error.details.scope to disambiguate capability vs account scope |
BILLING_NOT_PERMITTED_FOR_AGENT | Seller-wide capability accepts the value, but the calling buyer agentβs commercial relationship does not (e.g., passthrough-only β no payments relationship); see Buyer-agent identity | Retry with error.details.suggested_billing (typically operator) when present; when absent, surface to a human β the agent cannot extend its own commercial relationship |
PAYMENT_TERMS_NOT_SUPPORTED | Seller does not accept the requested payment terms | Omit payment_terms to accept the sellerβs default, or negotiate offline |
ACCOUNT_PAYMENT_REQUIRED | Account has an outstanding balance requiring payment | Resolve outstanding balance or route to another account |
ACCOUNT_SUSPENDED | Account is suspended | Contact seller to resolve |
BRAND_REQUIRED | Billable operation attempted without brand reference | Include brand in the request |
Next steps