API reference
Ringba-compatible ping
A single-step ping URL per publisher lane that takes Ringba's parameters and answers in Ringba's field names.
A publisher already integrated with another platform has a system that pings one URL, with its credential in the path, and expects a price and a number back in a single answer. This door speaks that dialect. One request runs the auction and claims the winning bid, and the answer uses field names their platform already parses. The background is in Ringba-compatible ping; the native two-step protocol, which is better in every way you can control, is RTB ping and post.
It is a dialect, not a second engine. The same auction runs, the same bid rows are written, the same call and postback key are minted, and every request lands in the same ping ledger. What a publisher gets through this door and what they would get through the native one differ only in spelling.
The URL is the credential
https://api.buy3.io/api/rtb/compat/ringba/:token.json:tokenis the lane's compatibility token, minted in the console for one publisher on one campaign. A trailing.jsonis ignored, because that is how these URLs are usually written.- It is separate from the publisher key, so the URL can be pasted into somebody else's platform without handing over the key, and either can be rotated on its own.
- Rotating the publisher key moves the URL to its successor rather than breaking it.
- The door is off until it is switched on for that key. A real token on a switched-off lane answers
403 compat_disabled. - The key's
protocolsetting is deliberately not consulted — this door has its own switch. Its address allowlist, its rate limits and its signature requirement all are.
One request, two steps
Behind the door, a request runs the ping and then immediately claims the bid it won. That has one consequence worth planning for: a 200 with a bidAmount means the caller is already expected. The destination is held, the call row exists, and a caller who never arrives is a bid that expires against the publisher's record. Send the caller, or do not ping.
- Parameter names are case-insensitive —
CID,cidandCidare one parameter. - A
GETtakes them in the query string. APOSTtakes them in a JSON body, the query string, or both; where the two disagree, the body wins. - A body is at most 32 KB.
publisherInboundCallIdis the idempotency key. Without it, anIdempotency-Keyheader is honoured instead.- Every request — refused ones too — is a row in the ping ledger. The ledger records the door's name, never the token.
Parameters
| Parameter | Meaning |
|---|---|
CID | The caller's number, as ten digits, eleven digits or E.164. |
zipcode | The caller's ZIP code. |
state | The caller's state. Two letters drive state targeting. |
exposeCallerId | Whether the caller's number may be passed to the buyer. |
sipOk | Whether you can take the call over SIP. |
publisherInboundCallId | Your id for the call, and the idempotency key. |
| anything else | Becomes a tag on the call. |
What comes back
| Field | Type | Meaning |
|---|---|---|
bidId | uuid | The bid, already claimed. On a no-bid this is the auction's id, or null. |
bidAmount | number | Dollars, not cents — what you are paid. It is never what the buyer pays. 0 means no bid. |
expireInSeconds | integer | How long the destination is held. |
bidExpireDT | ISO 8601 | The same moment as a timestamp. |
bidExpireEpoch | integer | The same moment in milliseconds since 1970. |
phoneNumber, phoneNumberNoPlus | string | Where to send the caller, with and without the leading plus. It is the lane's own tracking number when it has one, else the campaign's shared number, else the destination the claim returned. |
sipAddress | string | Where to send the caller over SIP. Present only when you sent sipOk and a SIP domain is configured. |
bidTerms | object[] | { code, message } — what the payout depends on. See below. |
warnings | object[] | { code, description }. Advisory only: a warning never changes the outcome. Code 250 means the bid won but the lane has no number or SIP address to send the caller to — nobody can be delivered until that is fixed. |
callId, ref | uuid, string | Beyond the dialect, and safe for a client that does not know them to ignore. They are the handles a support conversation starts from. |
auctionId | uuid | The auction, for the ping ledger. |
postback | object | { url, key } — the per-call conversion postback, the same one the native protocol returns. |
rejectReason | string | On a no-bid only: the reason and its code in one string, because that is the one field these platforms show an operator. |
bidTerms code | Means |
|---|---|
101 | The call must connect. |
100 | The call must last at least the stated number of seconds. message names it. |
102 | The call must be marked converted. |
110 | A duplicate caller inside the stated window is not paid. |
A no-bid is a 200
As the dialect expects. rejectReason reads "<description> (Code: <code>)", and the code is one of the reject codes — numbers that never change meaning. That includes the two cases peculiar to a single-step door: a claim that lost its capacity between the two halves of the request (code 1000), and a publisher lane that is at its concurrency limit (code 1013).
Three refusals are not a bid of zero, because a client that ignores them will keep making the same mistake:
| Status | Body | When |
|---|---|---|
409 | The no-bid shape, (Code: 1025) or (Code: 1003) | An identical request is still being scored, or that caller already has a live bid on this campaign. |
429 | The no-bid shape, (Code: 1024), plus Retry-After | The key is over one of its rate limits. |
400, 401, 403 | { ok: false, error, message } | The request could not be parsed, or the door refused it. A bad token, a switched-off URL or an address off the allowlist is an error, not a price of zero. |
Rate limits
The publisher key's own per-minute, per-hour and per-day limits apply. Every answer carries RateLimit-Policy with all three windows and RateLimit with what is left of the current minute; a 429 adds Retry-After in seconds. The defaults are in Limits.
Endpoints
- GET
/api/rtb/compat/ringba/:tokenPing and claim, with query parameters - POST
/api/rtb/compat/ringba/:tokenPing and claim, with a JSON body
Ping and claim, with query parameters
/api/rtb/compat/ringba/:tokenRuns the auction and claims the winning bid in one request, with the parameters in the query string. This is the form most platforms send. A win reserves the destination and creates the call, so send the caller. Every request writes a ping-ledger row for both sides to read; a win also writes the bid and the call.
Authorization header. The request authenticates itself with a value in its body or URL, described below.Path parameters
| Name | Type | Description |
|---|---|---|
tokenRequired | string | The lane's compatibility token. A trailing .json is ignored.8–128 characters of letters, digits, - and _ |
Query parameters
| Name | Type | Description |
|---|---|---|
CIDOptional | string | The caller's number. Ten digits, eleven digits or E.164 are all accepted. A campaign may require one, and a bid made without one carries a warning.up to 32 characters |
zipcodeOptional | string | The caller's ZIP code. It is kept on the call and passed to buyers that ask for it.up to 12 characters |
stateOptional | string | The caller's state. A two-letter code drives state targeting; anything else is kept as a tag called state rather than refused, because it is still something you said about the caller.up to 40 characters |
exposeCallerIdOptional | string | Whether the caller's number may be passed on to the buyer.yes/no, true/false, 1/0, on/off or y/n |
sipOkOptional | string | Whether you can take the call over SIP. When you can, and a SIP domain is configured, the answer may carry a sipAddress.the same flag spellings as exposeCallerId |
publisherInboundCallIdOptional | string | Your own id for the inbound call — and the idempotency key. Send it: a retried request replays the first answer byte for byte instead of selling one caller twice.up to 120 characters |
anything elseOptional | scalar | Kept as a tag on the call, with the source publisher. This is where sub_id, a keyword or a campaign name goes, and it is what tag filters and tag: reports read.at most 40 tags · key up to 64 characters, value up to 512 |
Example request
curl "https://api.buy3.io/api/rtb/compat/ringba/9f2a61c07be34d5580aa1c9e2d7f4b6e.json?CID=4155550142&state=CA&zipcode=94103&publisherInboundCallId=nw-88231&sub_id=fb-42"Responses
- 200A bid, already claimed. Send the caller to
phoneNumberbeforebidExpireDT. - 200 (no bid)No bid.
bidAmountis0andrejectReasoncarries the reason and its code.
{
"bidId": "7c6b5a49-3827-4615-9048-372615049382",
"bidAmount": 27.5,
"expireInSeconds": 30,
"bidExpireDT": "2026-09-20T14:03:41.000Z",
"bidExpireEpoch": 1789913021000,
"phoneNumber": "+18885550100",
"phoneNumberNoPlus": "18885550100",
"bidTerms": [
{
"code": 101,
"message": "Call must connect"
},
{
"code": 100,
"message": "Minimum connected duration 90 seconds"
}
],
"warnings": [],
"callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
"ref": "CA-482137",
"auctionId": "3f2e1d0c-9b8a-4765-8432-10fedcba9876",
"postback": {
"url": "https://api.example.com/api/rtb/v1/conversion",
"key": "b3p_4f19c2a70e5b8d63"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | malformed_request | A POST body was sent and it is not valid JSON. Query parameters are still read on a GET, so this only reaches a POST. |
| 401 | invalid_key | The token in the path is not 8–128 characters of letters, digits, - and _, or it is not a token this platform knows, or the key behind it has been revoked and its grace window is over. |
| 401 | signature_missing | The key requires signed requests and one of X-Buy3-Timestamp, X-Buy3-Nonce or X-Buy3-Signature was absent. The body also carries code: 4000. |
| 401 | signature_invalid | The signature did not match. code: 4001. |
| 401 | signature_stale | The timestamp is more than five minutes from ours. code: 4002. |
| 401 | nonce_replay | That nonce has been used already. code: 4003. |
| 403 | compat_disabled | The token is real but the single-step URL is switched off for that lane. This is deliberately distinguishable from an unknown token: somebody reading the ping ledger has to be able to tell "wrong URL" from "you turned this off". |
| 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 an address that is not on the key's allowlist. |
| 409 | (no-bid shape) | An identical request is still being scored (1025), or that caller already holds a live bid on this campaign (1003). Wait rather than sending it again. |
| 429 | (no-bid shape) | The key is over one of its rate limits (1024). Retry-After says how long to wait. |
Ping and claim, with a JSON body
/api/rtb/compat/ringba/:tokenExactly the same request, the same parameters and the same answers as the GET, with the parameters in a JSON body instead. A query string may be sent as well; where the two name the same parameter, the body wins. Use this form when a value would be awkward in a URL.
Authorization header. The request authenticates itself with a value in its body or URL, described below.Path parameters
| Name | Type | Description |
|---|---|---|
tokenRequired | string | The lane's compatibility token. A trailing .json is ignored.8–128 characters of letters, digits, - and _ |
Request body
| Name | Type | Description |
|---|---|---|
CIDOptional | string | The caller's number. Ten digits, eleven digits or E.164 are all accepted. A campaign may require one, and a bid made without one carries a warning.up to 32 characters |
zipcodeOptional | string | The caller's ZIP code. It is kept on the call and passed to buyers that ask for it.up to 12 characters |
stateOptional | string | The caller's state. A two-letter code drives state targeting; anything else is kept as a tag called state rather than refused, because it is still something you said about the caller.up to 40 characters |
exposeCallerIdOptional | string | Whether the caller's number may be passed on to the buyer.yes/no, true/false, 1/0, on/off or y/n |
sipOkOptional | string | Whether you can take the call over SIP. When you can, and a SIP domain is configured, the answer may carry a sipAddress.the same flag spellings as exposeCallerId |
publisherInboundCallIdOptional | string | Your own id for the inbound call — and the idempotency key. Send it: a retried request replays the first answer byte for byte instead of selling one caller twice.up to 120 characters |
anything elseOptional | scalar | Kept as a tag on the call, with the source publisher. This is where sub_id, a keyword or a campaign name goes, and it is what tag filters and tag: reports read.at most 40 tags · key up to 64 characters, value up to 512 |
Example request
curl -X POST "https://api.buy3.io/api/rtb/compat/ringba/9f2a61c07be34d5580aa1c9e2d7f4b6e.json" \
-H "Content-Type: application/json" \
-d '{
"CID": "4155550142",
"state": "CA",
"zipcode": "94103",
"publisherInboundCallId": "nw-88231",
"sub_id": "fb-42"
}'Responses
- 200A bid, already claimed. The
warningslist is where an integration problem shows up before it becomes a support ticket — here, a lane with nowhere to send the caller. - 200 (no bid)No bid, in the same shape as on the
GET. - 400 (bad body)The body could not be parsed. This is the platform's own envelope, not a bid of zero.
{
"bidId": "7c6b5a49-3827-4615-9048-372615049382",
"bidAmount": 27.5,
"expireInSeconds": 30,
"bidExpireDT": "2026-09-20T14:03:41.000Z",
"bidExpireEpoch": 1789913021000,
"bidTerms": [
{
"code": 101,
"message": "Call must connect"
},
{
"code": 100,
"message": "Minimum connected duration 90 seconds"
}
],
"warnings": [
{
"code": 250,
"description": "The bid was won but this lane has no number or SIP address to send the caller to — ask your account manager to attach one"
}
],
"callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
"ref": "CA-482137",
"auctionId": "3f2e1d0c-9b8a-4765-8432-10fedcba9876",
"postback": {
"url": "https://api.example.com/api/rtb/v1/conversion",
"key": "b3p_4f19c2a70e5b8d63"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | malformed_request | A POST body was sent and it is not valid JSON. Query parameters are still read on a GET, so this only reaches a POST. |
| 401 | invalid_key | The token in the path is not 8–128 characters of letters, digits, - and _, or it is not a token this platform knows, or the key behind it has been revoked and its grace window is over. |
| 401 | signature_missing | The key requires signed requests and one of X-Buy3-Timestamp, X-Buy3-Nonce or X-Buy3-Signature was absent. The body also carries code: 4000. |
| 401 | signature_invalid | The signature did not match. code: 4001. |
| 401 | signature_stale | The timestamp is more than five minutes from ours. code: 4002. |
| 401 | nonce_replay | That nonce has been used already. code: 4003. |
| 403 | compat_disabled | The token is real but the single-step URL is switched off for that lane. This is deliberately distinguishable from an unknown token: somebody reading the ping ledger has to be able to tell "wrong URL" from "you turned this off". |
| 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 an address that is not on the key's allowlist. |
| 409 | (no-bid shape) | An identical request is still being scored (1025), or that caller already holds a live bid on this campaign (1003). |
| 429 | (no-bid shape) | The key is over one of its rate limits (1024). Retry-After says how long to wait. |
