API reference
RTB ping and post
The endpoints a publisher's platform calls to sell you a call in real time: ping for a bid, post to claim it, release it and look the call up — and, for the buyer, post a conversion.
This is the surface a publisher's system talks to. It is authenticated with a publisher key — one publisher on one campaign — rather than with your API key, and it answers in its own envelope: ok: true with the result, or ok: false with an error code and a message. The walkthrough is in Ping and post; this page is the field-by-field reference.
Base URL and authentication
https://api.buy3.io/api/rtb/v1- Send the publisher key as
Authorization: Bearer b3k_live_…. The key identifies the publisher and the campaign, so neither appears in a request. - Bodies are JSON, at most 32 KB.
- If the key requires it, sign each request with the
X-Buy3-Timestamp,X-Buy3-NonceandX-Buy3-Signatureheaders — see Keys and security. - Every request, accepted or refused, is a row in the ping ledger that you and the publisher can both read.
Two per-call keys
A claimed call has two keys, one for each side of the trade, and neither opens what the other does.
| Key | Who holds it | What it opens |
|---|---|---|
Lookup key, b3p_… | The publisher. It comes back from /post as lookup.key, with lookup.url. | Look a call up — and nothing else. Presented to the conversion endpoint it is refused with 403 lookup_key_cannot_convert. |
Conversion key, b3c_… | The buyer the call was sent to. It travels only on the buyer's own leg of the call: the X-Buy3-Postback-Key SIP header, and {{postback.key}} (with {{postback.url}}) in the buyer's custom headers and screen pop. | Post a conversion, and look the call up. It is minted for this call and the target it was sent to, so only the buyer the call ended up with can convert it. |
Rate limits
Pings are limited per key, per minute, hour and day. Responses carry RateLimit-Policy and RateLimit headers; over the limit the answer is 429 with the reject code 1024 and a Retry-After header. The defaults are in Limits and can be changed on each key.
Endpoints
- POST
/api/rtb/v1/pingPing for a bid - POST
/api/rtb/v1/postPost to claim a bid - POST
/api/rtb/v1/releaseRelease a bid - POST
/api/rtb/v1/conversionPost a conversion - GET
/api/rtb/v1/whoamiCheck a key - GET
/api/rtb/v1/calls/:refLook a call up
Ping for a bid
/api/rtb/v1/pingDescribe a call and get back what it is worth. A win reserves a slot for that caller until the bid expires; it does not return a destination — claim the bid with a post for that. The same request is also accepted as a GET with the fields as query parameters, for platforms that cannot send a body. A GET is not a way round signing: on a key that requires signatures it is signed over its raw query string, exactly as it was sent, without the leading ?.
Authorization: Bearer <publisher key> — the key issued for one publisher on one campaign.Request body
| Name | Type | Description |
|---|---|---|
callerIdOptional | string | The caller's number. Required when the campaign requires a caller ID; a bid made without one carries a warning.E.164, such as +12145550142 |
stateOptional | string | The caller's state, as a two-letter code. |
zipOptional | string | The caller's ZIP code. |
cityOptional | string | The caller's city. |
sipOkOptional | boolean | Whether you can deliver the call over SIP. When true, the post may answer with a sipAddress. |
exposeCallerIdOptional | boolean | Whether the caller's number may be passed on to the buyer. |
publisherRefOptional | string | Your own reference for this lead. It is stored with the call and shown in your portal. |
externalCallIdOptional | string | Your phone system's id for the call. |
testOptional | boolean | Marks the ping as a test. The response carries a warning saying so. |
tagsOptional | object | Key-value pairs describing the caller. Any other top-level scalar field you send is kept as a tag too, so an existing payload usually works unchanged.at most 40 tags · key up to 64 characters, value up to 512 |
Example request
curl -X POST "https://api.buy3.io/api/rtb/v1/ping" \
-H "Authorization: Bearer b3k_live_your_publisher_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-d '{
"callerId": "+12145550142",
"state": "TX",
"zip": "75201",
"sipOk": true,
"publisherRef": "lead-88231",
"tags": { "utm_source": "search", "age_band": "65plus" }
}'Responses
- 200A bid.
expiresInSecondsis how long the price and the reserved slot are held. - 200 (no bid)No bid.
reject.codeis one of the reject codes and never changes meaning.
{
"ok": true,
"auctionId": "0b9c6c1e-7f0a-4a55-9d0e-3c1b8a2f4d10",
"bidId": "0f8fad5b-d9cb-469f-a165-70867728950e",
"payout": 38.5,
"payoutCents": 3850,
"currency": "USD",
"expiresInSeconds": 30,
"expiresAt": "2026-09-20T14:03:57.000Z",
"expiresAtEpochMs": 1789913037000,
"nextStep": "post",
"terms": [
{
"code": 101,
"description": "Call must connect"
},
{
"code": 100,
"description": "Minimum connected duration 90 seconds",
"seconds": 90
}
],
"campaign": {
"id": "c2b7e1a0-6f3d-4b1e-8a55-0d9c4e7f1a23",
"name": "Medicare — inbound"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No Authorization: Bearer header. |
| 401 | invalid_key | The key is not recognised, or has been revoked and its grace period is over. |
| 401 | 4000–4003 | The key requires signed requests and the signature, timestamp or nonce did not check out. See Keys and security. |
| 403 | wrong_protocol | The key is not enabled for this protocol. |
| 403 | publisher_not_live | The publisher is not live. |
| 403 | campaign_not_live | The campaign is not live. |
| 403 | ip_not_allowed | The request came from outside the key's IP allowlist. |
| 400 | malformed_request | The body is not valid JSON. |
| 409 | bid_in_flight | An identical ping is being scored right now. Wait for it instead of sending it again. |
| 409 | caller_already_reserved | A live bid is already held for this caller on this campaign. |
| 429 | rate_limited | The key is over one of its ping limits. Retry-After says how long to wait. |
Post to claim a bid
/api/rtb/v1/postClaim a winning bid before it expires and receive the destination to send the caller to. The Idempotency-Key header is required: a retried post must never create a second call for the same caller, and you cannot detect that from your side. If the winning target has filled since the ping, the platform tries the runners-up before giving up, and says so with failedOver.
Authorization: Bearer <publisher key> — the key issued for one publisher on one campaign.Request body
| Name | Type | Description |
|---|---|---|
bidIdRequired | uuid | The bidId from the ping. |
callerIdOptional | string | The caller's number. If the ping carried one, this must match it.up to 32 characters |
externalCallIdOptional | string | Your phone system's id for the call.up to 120 characters |
tagsOptional | object | Further tags learned since the ping. |
Example request
curl -X POST "https://api.buy3.io/api/rtb/v1/post" \
-H "Authorization: Bearer b3k_live_your_publisher_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1b4e28ba-2fa1-41d2-883f-0016d3cca427" \
-d '{
"bidId": "0f8fad5b-d9cb-469f-a165-70867728950e",
"callerId": "+12145550142",
"externalCallId": "pbx-5531902"
}'Responses
- 201The bid is claimed. Send the caller to
sipAddressif you can deliver over SIP, otherwise tophoneNumber. On a call tracking campaignphoneNumberis always a number of the platform's own — your lane's tracking number, or the campaign's ingress number — never the buyer's line, because a caller who reaches the buyer directly is a call the platform never sees and nobody is paid for.lookupis yours: the call's lookup URL and a read-only per-call key for Look a call up. It cannot report a conversion — that is the buyer's, on a key only the buyer is sent.
{
"ok": true,
"callId": "5f0c1c8e-2a44-4d0a-9d0f-7b3f0a6c9e12",
"ref": "CL-204817",
"bidId": "0f8fad5b-d9cb-469f-a165-70867728950e",
"payout": 38.5,
"payoutCents": 3850,
"sipAddress": "sip:0f8fad5bd9cb469fa16570867728950e@sip.example.net",
"phoneNumber": "+18335550119",
"phoneNumberNoPlus": "18335550119",
"expiresInSeconds": 30,
"expiresAt": "2026-09-20T14:04:27.000Z",
"terms": [
{
"code": 101,
"description": "Call must connect"
},
{
"code": 100,
"description": "Minimum connected duration 90 seconds",
"seconds": 90
}
],
"lookup": {
"url": "https://api.buy3.io/api/rtb/v1/calls/CL-204817",
"key": "b3p_4f1d2c3b4a5e6f70"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No Authorization: Bearer header. |
| 401 | invalid_key | The key is not recognised, or has been revoked and its grace period is over. |
| 401 | 4000–4003 | The key requires signed requests and the signature, timestamp or nonce did not check out. See Keys and security. |
| 403 | wrong_protocol | The key is not enabled for this protocol. |
| 403 | publisher_not_live | The publisher is not live. |
| 403 | campaign_not_live | The campaign is not live. |
| 403 | ip_not_allowed | The request came from outside the key's IP allowlist. |
| 400 | idempotency_key_required | No Idempotency-Key header was sent. |
| 400 | validation_error | bidId is missing or is not a UUID. |
| 404 | unknown_bid | No such bid for this key. |
| 409 | bid_already_claimed | The bid has already been claimed and is no longer open. A straightforward repeat of a post that succeeded is not this error — it answers 201 with the original body again. |
| 409 | caller_id_mismatch | The callerId does not match the one the bid was made for. |
| 409 | no_capacity_at_post | The winning target filled before the post, and no runner-up could take the call. |
| 409 | no_destination | A call tracking lane with no tracking number of the platform's own and no SIP address to send the caller to. Refused before a slot is taken or a call is written, rather than answered with a destination that would not be tracked. Carries reject: 1018 and warning 250. |
| 410 | bid_expired | The bid's expiresInSeconds had passed. |
Release a bid
/api/rtb/v1/releaseGive a bid back when the caller is not coming — they hung up, or you sold the call elsewhere. It frees the reserved slot immediately instead of when the bid expires. Releasing a bid that is already closed is not an error.
Authorization: Bearer <publisher key> — the key issued for one publisher on one campaign.Request body
| Name | Type | Description |
|---|---|---|
bidIdRequired | uuid | The bid to release. |
reasonOptional | string | Why, in your own words. Shown in the ping ledger.up to 120 characters |
Example request
curl -X POST "https://api.buy3.io/api/rtb/v1/release" \
-H "Authorization: Bearer b3k_live_your_publisher_key" \
-H "Content-Type: application/json" \
-d '{ "bidId": "0f8fad5b-d9cb-469f-a165-70867728950e", "reason": "caller_hung_up" }'Responses
- 200The bid was released.
- 200 (already closed)The bid had already been claimed, released or expired.
statussays which.
{
"ok": true,
"released": true
}Errors
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No Authorization: Bearer header. |
| 401 | invalid_key | The key is not recognised, or has been revoked and its grace period is over. |
| 401 | 4000–4003 | The key requires signed requests and the signature, timestamp or nonce did not check out. See Keys and security. |
| 403 | wrong_protocol | The key is not enabled for this protocol. |
| 403 | publisher_not_live | The publisher is not live. |
| 403 | campaign_not_live | The campaign is not live. |
| 403 | ip_not_allowed | The request came from outside the key's IP allowlist. |
| 404 | unknown_bid | No such bid for this key. |
Post a conversion
/api/rtb/v1/conversionThe buyer's postback: tell the seller that a call they received converted. It takes no publisher key and is authorised by the buyer's own per-call conversion key, b3c_…, which is sent only on the buyer's leg of the call — the X-Buy3-Postback-Key SIP header, or {{postback.key}} in the buyer's custom headers and screen pop. The key is minted for this call and the target it was sent to, so it converts the call only for the buyer the call ended up with. The lookup key /post returns to the publisher cannot report a conversion. A call takes one conversion.
Authorization header. The request authenticates itself with a value in its body or URL, described below.Request body
| Name | Type | Description |
|---|---|---|
keyRequired | string | The buyer's per-call conversion key, b3c_…, as it arrived on their leg of the call.up to 80 characters |
callIdRequired | uuid | The call being reported on. |
convertedOptional | boolean | Whether the call converted. Defaults to true. |
valueOptional | number | What the conversion was worth, in dollars.0 or more |
dispositionOptional | string | Your outcome label, such as policy_sold.up to 60 characters |
reasonOptional | string | Free-text detail.up to 200 characters |
occurredAtOptional | date | When the conversion happened. Defaults to now. |
Example request
curl -X POST "https://api.buy3.io/api/rtb/v1/conversion" \
-H "Content-Type: application/json" \
-d '{
"key": "b3c_7d41e0a29c5b3f86d2e17a0c94b6f25e8d3a1c07",
"callId": "5f0c1c8e-2a44-4d0a-9d0f-7b3f0a6c9e12",
"converted": true,
"value": 420.00,
"disposition": "policy_sold"
}'Responses
- 200The conversion was recorded and the call re-settled.
{
"ok": true,
"callId": "5f0c1c8e-2a44-4d0a-9d0f-7b3f0a6c9e12",
"ref": "CL-204817",
"billable": true,
"payoutCents": 3850
}Errors
| Status | Code | When |
|---|---|---|
| 400 | malformed_request | The body is not valid JSON. |
| 400 | validation_error | key or callId is missing, or callId is not a UUID. |
| 403 | lookup_key_cannot_convert | The key is the publisher's lookup key for this call — the one /post returns. It can only read the call back; a conversion is reported by the buyer, with the key sent on the buyer's own leg. |
| 404 | unknown_call | No call has that callId, or the key is not the conversion key of the buyer the call ended up with. Any other wrong key answers 404, not 403, so the endpoint cannot be used to discover which call ids exist. |
| 409 | already_converted | The call already has a conversion. convertedAt says when. |
| 410 | window_closed | Conversions close 72 hours after the call ends. |
Check a key
/api/rtb/v1/whoamiEverything an integrator would otherwise have to ask you: which publisher and campaign the key belongs to, whether both are live, the key's limits, whether it must sign requests, its IP allowlist and the exact URLs to call. endpoints.lookup is the call lookup the per-call key from /post opens; the conversion endpoint is not listed, because it is the buyer's. A recognised but blocked key answers 200 with ok: false and the problem named; only an unknown key is an error.
Authorization: Bearer <publisher key> — the key issued for one publisher on one campaign.Example request
curl "https://api.buy3.io/api/rtb/v1/whoami" \
-H "Authorization: Bearer b3k_live_your_publisher_key"Responses
- 200The key's identity and limits.
{
"ok": true,
"problem": null,
"protocol": "rtb_v1",
"environment": "live",
"publisher": {
"id": "7d3c2b1a-0f9e-4d8c-b7a6-5e4d3c2b1a09",
"name": "Brightpath Media",
"status": "active"
},
"campaign": {
"id": "c2b7e1a0-6f3d-4b1e-8a55-0d9c4e7f1a23",
"name": "Medicare — inbound",
"status": "live",
"requireCallerId": false,
"requiredTags": []
},
"key": {
"prefix": "b3k_live_9f2a",
"label": "Production",
"requireSignature": false,
"ipAllowlist": []
},
"limits": {
"perMinute": 120,
"perHour": 5000,
"perDay": 50000,
"maxLiveBidsPerCaller": 1,
"bidTtlSeconds": 30
},
"endpoints": {
"ping": "https://api.buy3.io/api/rtb/v1/ping",
"post": "https://api.buy3.io/api/rtb/v1/post",
"release": "https://api.buy3.io/api/rtb/v1/release",
"lookup": "https://api.buy3.io/api/rtb/v1/calls/{ref}"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No Authorization: Bearer header. |
| 401 | invalid_key | The key is not recognised. |
Look a call up
/api/rtb/v1/calls/:refFetch a call's details, whatever a phone system stripped from the SIP headers on the way. Two keys open it: the publisher's lookup key, b3p_…, that /post returned as lookup.key, and the buyer's conversion key, b3c_…, that travelled on their leg of the call. Because both sides can read it, the response carries only what both may see: the winning bid is named by its id and its duration term, and never by its amount. The caller's number is included only when the campaign exposes caller ID and the call has connected.
Authorization header. The request authenticates itself with a value in its body or URL, described below.Path parameters
| Name | Type | Description |
|---|---|---|
refRequired | string | The call's public reference, such as CL-204817, or its id. |
Query parameters
| Name | Type | Description |
|---|---|---|
keyOptional | string | The lookup key or the buyer's conversion key, when it cannot be sent as Authorization: Bearer <key>. |
Example request
curl "https://api.buy3.io/api/rtb/v1/calls/CL-204817" \
-H "Authorization: Bearer b3p_4f1d2c3b4a5e6f70"Responses
- 200The call, as much of it as both sides of the trade may read.
{
"ok": true,
"ref": "CL-204817",
"callId": "5f0c1c8e-2a44-4d0a-9d0f-7b3f0a6c9e12",
"status": "completed",
"startedAt": "2026-09-20T14:03:21.000Z",
"answeredAt": "2026-09-20T14:03:27.000Z",
"caller": {
"number": "+12145550142",
"state": "TX"
},
"campaign": {
"id": "c2b7e1a0-6f3d-4b1e-8a55-0d9c4e7f1a23",
"name": "Medicare — inbound"
},
"publisherRef": "lead-88231",
"tags": {
"utm_source": "search",
"age_band": "65plus"
},
"bid": {
"id": "0f8fad5b-d9cb-469f-a165-70867728950e",
"durationSeconds": 90
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No key was sent in the header or the query. |
| 404 | unknown_call | No call matches that reference, or the key is neither its lookup key nor the conversion key of the buyer it ended up with. The two answer the same, so a guessed reference learns nothing. |
