Buyer quickstart
This page takes a buyer agent from discovery to a booked campaign, with
its creative brief, on the public sales agent,
https://salesagent.streetsdigital.com/. It uses nine AdCP calls. At the
end there’s a script that runs them all for you and cancels the test buy
afterwards.
What you need
- A bearer key (
crrbn_buyer_…) from the Carbnn team. Carbnn also opens products to your key and adds your brand as an account. - A signing key registered with Carbnn, for
create_media_buyandupdate_media_buy(step 1). - Node.js 20 or later and
npm install @adcp/sdkif you want to use the script or the SDK’s signer.
Every call is a JSON-RPC tools/call POST to the endpoint, with these
headers:
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer <your-key>The AdCP result is in result.structuredContent. An error comes back as
result.structuredContent.adcp_error with a code, a message and
often a field. See errors and limits.
1. Set up a signing key
Booking a media buy must be signed with RFC 9421 HTTP message signatures. Generate an Ed25519 key pair with the script from step 10:
node adcp-buyer-e2e.mjs keygen --kid my-buyer-2026-09 --out ./my-buyer.private.jwkIt writes the private key to the file (readable only by you) and prints
the public JWKS. Host that JWKS at an https:// URL you control, then
send the Carbnn team the URL and the key id (my-buyer-2026-09). Keep the
private key secret; Carbnn never needs it.
Any AdCP request-signing key works: Ed25519 or ECDSA P-256, with
adcp_use: "request-signing" and key_ops: ["verify"] on the public
key.
2. Check capabilities
get_adcp_capabilities takes no arguments. Look for:
supported_protocols:media_buy,signalsandcreative.request_signing.required_for: the tools that must be signed,create_media_buyandupdate_media_buy.account.supported_billing: ["operator"]: Carbnn invoices you, and payment is settled outside the protocol.
3. Find signals
{ "name": "get_signals", "arguments": { "signal_spec": "UK comedy high engagement" } }Returns Carbnn’s contextual signals (content category, engagement tier, audience region and age band), with coverage over the products opened to your key.
4. Pick an account
{ "name": "list_accounts", "arguments": {} }Returns your brands, each with an account_id. You’ll pass one to
create_media_buy.
5. Get products
{
"name": "get_products",
"arguments": { "buying_mode": "brief", "brief": "UK comedy podcasts with high engagement" }
}Returns the products opened to your key. Prices include Carbnn’s buyer
fee: fixed_price is what you pay, and price_breakdown shows the list
price, the fee and the seller commission. Pooled products group several
creators behind one product; their description says the roster may
change until you book.
6. Look up the brief format
{
"name": "list_creative_formats",
"arguments": { "format_ids": ["<format_ids from the product>"] }
}Each product lists its format_ids. The format’s assets say what the
creator needs from you, for example a brief, a disclosure_text and a
landing_url, and which of them are required.
7. Book, with the brief
Pick a product and one of its pricing options, then send
create_media_buy, signed, with the brief in the package’s
creatives:
{
"name": "create_media_buy",
"arguments": {
"idempotency_key": "<a fresh UUID per buy>",
"account": { "account_id": "<account_id from list_accounts>" },
"brand": { "domain": "acme.example.com" },
"po_number": "E2E test (demo)",
"start_time": "2026-10-05T00:00:00Z",
"end_time": "2026-10-12T00:00:00Z",
"packages": [
{
"product_id": "<product_id>",
"pricing_option_id": "<pricing_option_id>",
"budget": 4725,
"creatives": [
{
"creative_id": "autumn-brief-1",
"name": "Autumn range brief",
"format_id": {
"agent_url": "https://salesagent.streetsdigital.com",
"id": "<format id>"
},
"assets": {
"brief": { "asset_type": "text", "content": "Mention the autumn range." },
"disclosure_text": { "asset_type": "text", "content": "#ad" },
"landing_url": { "asset_type": "url", "url": "https://acme.example.com/autumn" }
}
}
]
}
]
}
}budgetis what you pay, fee included, and must be at least the option’smin_spend_per_package.- Use a fresh
idempotency_keyper buy. If the call times out, send the same request with the same key: you get the original buy back, never a second one. creativesis optional at booking: you can add the brief later withupdate_media_buy, but the buy can’t start until every package has an approved one. See creative briefs.- On a pooled or multi-creator product you can leave the split to Carbnn
(seller-managed) or send your own in
packages[].ext.carbnn.creator_allocations. See creator allocation.
With @adcp/sdk, signing is one call. Sign the exact body you send, for
the exact URL you call:
import { signRequest } from '@adcp/sdk/signing/client';
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params });
const { headers: signature } = signRequest(
{ method: 'POST', url: 'https://salesagent.streetsdigital.com/', headers: {}, body },
{ keyid: 'my-buyer-2026-09', alg: 'ed25519', privateKey: myPrivateJwk },
{ coverContentDigest: true }
);
// POST with your usual headers plus `signature` (Signature, Signature-Input, Content-Digest).A call with only the bearer key is refused with AUTH_REQUIRED and the
reason request_signature_required.
8. Read the receipt
The response carries the media_buy_id, media_buy_status: "pending_creatives" and one entry per package:
price_breakdown: the list price and rates frozen on this buy. Later rate changes never alter it.ext.carbnn.allocation(pooled and multi-creator products): how the budget was split across creators.modeisseller_managedwhen Carbnn made the split.creative_assignmentsandext.carbnn.creatives: your brief, with Carbnn’s id for it and its reviewstatus(pending_reviewuntil the creator approves it).
9. Follow, change or cancel the buy
{ "name": "get_media_buys", "arguments": { "media_buy_ids": ["<media_buy_id>"] } }The buy starts as pending_creatives. The creator reviews the brief;
once every package has an approved brief the buy moves to
pending_start, and Carbnn confirms funding before it starts delivering.
get_media_buys shows each change of status.
get_media_buy_delivery reports clicks and conversions once it’s live.
To pause, resume, cancel, change a package budget or send a new brief,
call update_media_buy, signed like the booking:
{
"name": "update_media_buy",
"arguments": {
"idempotency_key": "<a fresh UUID per change>",
"media_buy_id": "<media_buy_id>",
"account": { "account_id": "<account_id>" },
"canceled": true,
"cancellation_reason": "No longer needed"
}
}See update_media_buy for everything it
takes.
10. Run it end to end
adcp-buyer-e2e.mjs (in the Carbnn repository under scripts/) runs
steps 2 to 9 in order and prints what it finds. It needs @adcp/sdk
installed next to it.
export ADCP_BUYER_TOKEN=crrbn_buyer_… # never printed
export ADCP_SIGNING_KEY=./my-buyer.private.jwk # private JWK or PKCS#8 PEM
node adcp-buyer-e2e.mjs --dry-run # stops before booking
node adcp-buyer-e2e.mjs # books a demo buy, then cancels it
node adcp-buyer-e2e.mjs --no-cancel # books a demo buy and leaves it bookedOptional settings:
ADCP_AGENT_URL: the agent to call. Defaults tohttps://salesagent.streetsdigital.com/.ADCP_SIGNING_KID: the key id, if your key file doesn’t carry one.ADCP_ACCOUNT_ID: which account to book for. Defaults to the first.ADCP_BUDGET: package budget. Defaults to the option’s minimum spend.ADCP_IDEMPOTENCY_KEY: reuse a key to see an idempotent replay.
The script prefers a pooled product, fills the required assets of the
product’s first brief-style format with an obvious test brief, books one
week starting next Monday, names the buy E2E test (demo) <date>, and
prints the receipt and the buy’s status. It then cancels the buy with
update_media_buy (--cancel, on by default), so the test cleans up
after itself. With --no-cancel the buy is a real order: cancel it
yourself with update_media_buy if you don’t want it to run.