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 — 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.
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.postbackis for the buyer — see the conversion endpoint below.
{
"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
}
],
"postback": {
"url": "https://api.buy3.io/api/rtb/v1/conversion",
"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 — the buyer never holds one — and is authorised by the per-call key that came back from the post and travelled to the buyer with the call. 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 per-call postback key, b3p_…. |
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": "b3p_4f1d2c3b4a5e6f70",
"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. |
| 404 | unknown_call | No call matches that callId and key. A 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. 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",
"conversion": "https://api.buy3.io/api/rtb/v1/conversion"
}
}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/:refFor the buyer: fetch a call's details with the per-call postback key, whatever their phone system stripped from the SIP headers on the way. The caller's number is included only when the campaign exposes caller ID and the call has connected — a buyer who never took the call has no claim on the number.
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 per-call postback 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 the buyer is entitled to.
{
"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",
"amountCents": 4400,
"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 and key. |
