API reference
API keys
List and create API keys, rotate and revoke them, and read the log of every request a key has made.
An API key is what a program of yours uses to reach the REST API. This group mints keys, changes what they may do, rotates them on a schedule and kills them when something has gone wrong — and shows you, request by request, what each one has actually been doing. The advice on scoping is in API keys.
A console surface
Every endpoint on this page is the console's own, under /api/tracking, called with the session token of a signed-in owner or admin. A key cannot manage keys. There is no route on the REST API for any of this, so a leaked key can never mint itself a wider one, and a compromised integration cannot cover its tracks by reading or clearing the request log.
https://api.buy3.io/api/tracking- Every request may carry an optional
agencyId(a UUID, in the query string or the body). Leave it out when you administer one workspace. - Creating, editing and rotating need a plan that includes API access. Revoking never does — "upgrade to revoke your leaked key" is not a sentence this platform says.
- How many keys a workspace may hold is a plan limit; see pricing. It counts keys that have not been revoked, so a rotated key inside its grace window does not hold a slot and a workspace at its limit can always rotate.
- Every write here is recorded in the activity log under the kind
api_key, at high severity. A rotated production key is indistinguishable from a stolen one until somebody can point at the entry that says who rotated it.
What you can do
| Operation | Notes |
|---|---|
| List keys | Each key's label, prefix, scopes, restrictions, when it was last used and from where. Never the key itself. |
| Create a key | A label, its scopes, and optionally an address allowlist and a slower rate limit. The key is in that response only. |
| Update a key | The label, the scopes, the allowlist and the rate limit. Only an active key can be edited. |
| Rotate a key | Mints a successor and starts a grace window on the old one, so an integration can be moved over without a gap. |
| Revoke a key | The key stops working at once, grace window and all. |
| Read the request log | Every request a key made, refusals included — one key at a time, or all of them together. |
The key object
| Field | Type | Meaning |
|---|---|---|
id | uuid | The key's id. It is not the key. |
label | string | Your name for it. Name it after the program that holds it, so a request log reads as an accusation of something specific. |
keyPrefix | string | b3_live_ or b3_sandbox_ and the first four characters of the secret — enough to recognise a key in a log, useless as a credential. |
scopes | string[] | What it may do. See below. |
env | string | live or sandbox. It cannot be changed. |
state | string | active, grace or revoked. See below. |
allowCidrs | string[] | The addresses it may be used from. An empty list means anywhere. |
rateLimitPerMinute | integer | null | Its own per-minute limit. null means the platform default. |
useCount | integer | How many requests it has ever made. |
graceUntil | ISO 8601 | null | When a rotated key stops answering. |
rotatedFromId | uuid | null | The key this one succeeded. |
lastUsedAt, lastUsedIp | ISO 8601, string | null | When and from where it was last seen. The fastest way to find out whether anything still holds a key you want to kill. |
revokedAt | ISO 8601 | null | When it was revoked. A rotated key has this set while it is still in grace. |
createdBy, createdAt | uuid | null, ISO 8601 | Who minted it and when. |
The three states
state | Answers requests | How it got there |
|---|---|---|
active | Yes. | It was created, and nothing has happened to it. |
grace | Yes, until graceUntil, and not a second longer. | It was rotated. Revoked on paper, still answering, so the integration holding it has time to move. |
revoked | No. | It was revoked, or its grace window ran out. |
active key can be edited. Editing a key on its way out would be widening a credential somebody has already decided to retire, so it answers 409 and points at the successor.Scopes
A scope reads resource:verb. There are two implications and no more: * is every scope, and x:write includes x:read. A route that accepts several scopes accepts any of them. GET /api-keys returns the whole vocabulary beside the page of keys, each scope with the sentence that describes it, so an editor never has to hardcode the list.
| Scope | Opens |
|---|---|
calls:read | List calls and the calls in progress, read one call with its routing waterfall and recordings, and export the call log as CSV. |
calls:write | Report a conversion, add tags to a call, and adjust its recorded revenue or payout. |
campaigns:read | Read campaigns and their routing plans. |
campaigns:write | Change a campaign's routing plan, and pause or resume a campaign. |
targets:read | Read targets, their caps and their live counters. |
targets:write | Change a target's price, caps, hours and states, pause or resume it, and reset its cap counters. |
buyers:read | Read buyers, their caps and their targets. |
publishers:read | Read publishers and the payout terms on each campaign. |
numbers:read | Read tracking numbers and what each is assigned to. |
reports:read | Run summary and time-series reports. |
pings:read | Read the ping log: inbound pings from publishers and outbound pings to buyers. |
blocklist:write | List, add and remove blocked callers. |
* | Everything this API can do, including scopes added later. |
The request log
Every request a recognised key makes is written down, refusals included: the method, the path, the parsed query string, the status, the error code, how long it took, the source address and the user agent. That is what answers "why has my integration been getting 401s since Tuesday" — because it is still sending the key you rotated.
- Bodies are never stored. There is nothing in this log that needs redacting.
- A request that never presented a recognisable key is not logged at all — there is no key to file it under.
- A refusal thrown before the key was recognised has no row either. A refusal thrown after it was — a scope, a rate limit — has one, with its
errorCode.
Endpoints
- GET
/api/tracking/api-keysList keys - POST
/api/tracking/api-keysCreate a key - PATCH
/api/tracking/api-keys/:idUpdate a key - POST
/api/tracking/api-keys/:id/rotateRotate a key - POST
/api/tracking/api-keys/:id/revokeRevoke a key - GET
/api/tracking/api-keys/:id/requestsRead one key's requests - GET
/api/tracking/api-requestsRead every key's requests
List keys
/api/tracking/api-keysThe workspace's keys, newest first, with the scope vocabulary beside them in scopes. Changes nothing, and never returns a key.
Query parameters
| Name | Type | Description |
|---|---|---|
envOptional | string | Only keys for this environment.live or sandbox |
statusOptional | string | active means not revoked. revoked includes keys still inside a rotation grace window.active or revoked |
qOptional | string | Matches part of the label or the prefix, ignoring case.up to 200 characters |
pageOptional | integer | The page to return.from 1, default 1 |
pageSizeOptional | integer | Rows per page. A larger value is clamped, not refused.1–200 · default 25 |
Example request
curl "https://api.buy3.io/api/tracking/api-keys?env=live&status=active" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200A page of keys, with
scopescarrying the whole vocabulary and its descriptions. The example abbreviates that list.
{
"data": [
{
"id": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_Qx7m",
"scopes": [
"calls:read",
"reports:read"
],
"env": "live",
"state": "active",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 18204,
"graceUntil": null,
"rotatedFromId": null,
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": "2026-09-20T14:02:51.000Z",
"lastUsedIp": "203.0.113.24",
"revokedAt": null,
"createdAt": "2026-08-02T11:20:41.000Z"
}
],
"page": 1,
"pageSize": 25,
"total": 1,
"totalPages": 1,
"scopes": [
{
"scope": "calls:read",
"description": "List calls and the calls in progress, read one call with its routing waterfall and recordings, and export the call log as CSV."
},
{
"scope": "reports:read",
"description": "Run summary and time-series reports."
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | env or status is not one of the allowed words. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | An agencyId that is not a workspace you administer answers the same 404. |
Create a key
/api/tracking/api-keysMints a key and returns it in this response only. Only a hash of it is stored, so nothing — not the console, not our staff — can show it again. Recorded in the activity log (api_key, high severity) with the scopes it was given.
Request body
| Name | Type | Description |
|---|---|---|
labelRequired | string | Your name for the key. Name the program that will hold it.up to 120 characters |
scopesRequired | string[] | What the key may do. Give it the narrowest set that works: a reporting job needs no write scope.at least one, at most 20 · each one a scope from the table above |
envOptional | string | Which environment the key belongs to. It cannot be changed afterwards.live or sandbox · default live |
allowCidrsOptional | string[] | The source addresses the key may be used from. An empty list allows any address. An entry that cannot be parsed is refused here rather than discovered when the integration goes dark.at most 50 entries · an IPv4 address, an IPv4 range such as 203.0.113.0/24, or a single IPv6 address · default [] |
rateLimitPerMinuteOptional | integer | A per-minute limit of your own, lower or higher than the default. A key can be slowed down but never set free: the range starts at 1.1–1200 · default: the platform's own limit |
Example request
curl -X POST "https://api.buy3.io/api/tracking/api-keys" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "BI warehouse sync",
"scopes": ["calls:read", "reports:read"],
"allowCidrs": ["203.0.113.0/24"],
"rateLimitPerMinute": 60
}'Responses
- 201The key object, the key itself, and a note to store it.
{
"apiKey": {
"id": "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_Qx7m",
"scopes": [
"calls:read",
"reports:read"
],
"env": "live",
"state": "active",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 0,
"graceUntil": null,
"rotatedFromId": null,
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": null,
"lastUsedIp": null,
"revokedAt": null,
"createdAt": "2026-09-20T14:00:02.000Z"
},
"key": "b3_live_Qx7mV0c1rP8dLt2fSn6gJw4hEy0bKz9u",
"note": "Store this key now — it is shown only once and cannot be recovered."
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | label or scopes is missing, or a field has the wrong type or is too long. |
| 400 | bad_request | scopes is empty or names something that is not a scope — details.scopes lists the ones it could have been. Or an allowCidrs entry is not an IPv4 address, an IPv4 range or a single IPv6 address — details.allowCidrs names the entry. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active. Reads stay open to a lapsed workspace; writes do not. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking (details.feature is callTracking), or does not include API access (details.feature is apiAccess). It is also the answer when the workspace already holds as many keys as its plan allows — then details.feature is maxApiKeys and details carries limit and current beside it. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | An agencyId that is not a workspace you administer answers the same 404. |
Update a key
/api/tracking/api-keys/:idChanges the label, the scopes, the address allowlist or the rate limit of an active key. env is fixed for life. Scopes and the allowlist are replaced outright, not merged. Recorded in the activity log (api_key) — at high severity when the scopes or the allowlist change, medium when only the label does, because what a key may do and from where is its security posture and a label is a label.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The key's id. |
Request body
| Name | Type | Description |
|---|---|---|
labelOptional | string | A new name.up to 120 characters |
scopesOptional | string[] | The new scope list, replacing the old one.at least one, at most 20 · a scope the key already holds from an older vocabulary may stay, so renaming a key does not force a scope change |
allowCidrsOptional | string[] | The new allowlist. Send [] to allow any address again.at most 50 entries · an IPv4 address or range, or a single IPv6 address |
rateLimitPerMinuteOptional | integer | A new per-minute limit.1–1200 |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/api-keys/9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"scopes": ["calls:read", "reports:read", "pings:read"]
}'Responses
- 200The key as it now stands.
{
"apiKey": {
"id": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_Qx7m",
"scopes": [
"calls:read",
"reports:read",
"pings:read"
],
"env": "live",
"state": "active",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 18204,
"graceUntil": null,
"rotatedFromId": null,
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": "2026-09-20T14:02:51.000Z",
"lastUsedIp": "203.0.113.24",
"revokedAt": null,
"createdAt": "2026-08-02T11:20:41.000Z"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A field has the wrong type or is out of range. |
| 400 | bad_request | The body is empty ("Nothing to update"). scopes is empty or names something that is not a scope — details.scopes lists the ones it could have been. Or an allowCidrs entry is not an IPv4 address, an IPv4 range or a single IPv6 address — details.allowCidrs names the entry. |
| 409 | conflict | The key has been revoked or rotated. Edit its successor instead. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active. Reads stay open to a lapsed workspace; writes do not. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking (details.feature is callTracking), or does not include API access (details.feature is apiAccess). It is also the answer when the workspace already holds as many keys as its plan allows — then details.feature is maxApiKeys and details carries limit and current beside it. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No such key in the workspace. Another workspace's key and an id that is not a UUID answer exactly as a missing one does. An agencyId that is not a workspace you administer answers the same 404. |
Rotate a key
/api/tracking/api-keys/:id/rotateRotation is a successor, not an edit. The old key is revoked now and keeps answering until graceUntil; the new one inherits the label, scopes, environment, allowlist and rate limit, and is returned once. Deploy the new key inside the window, then let the old one lapse — or end it early by revoking it. Recorded in the activity log (api_key, high severity) with the prefix it succeeded.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The key's id — the one being retired. |
Request body
| Name | Type | Description |
|---|---|---|
graceMinutesOptional | integer | How long the old key keeps answering. 0 kills it immediately, which is what you want if it has leaked.0–10080 (seven days) · default 1440 (one day) |
Example request
curl -X POST "https://api.buy3.io/api/tracking/api-keys/9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f/rotate" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"graceMinutes": 60
}'Responses
- 201The successor, the key itself, the retired key with its state, and when it stops.
{
"apiKey": {
"id": "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_7Tn4",
"scopes": [
"calls:read",
"reports:read"
],
"env": "live",
"state": "active",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 0,
"graceUntil": null,
"rotatedFromId": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": null,
"lastUsedIp": null,
"revokedAt": null,
"createdAt": "2026-09-20T14:00:02.000Z"
},
"previous": {
"id": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_Qx7m",
"scopes": [
"calls:read",
"reports:read"
],
"env": "live",
"state": "grace",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 18204,
"graceUntil": "2026-09-21T14:00:02.000Z",
"rotatedFromId": null,
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": "2026-09-20T14:02:51.000Z",
"lastUsedIp": "203.0.113.24",
"revokedAt": "2026-09-20T14:00:02.000Z",
"createdAt": "2026-08-02T11:20:41.000Z"
},
"key": "b3_live_7Tn4pQ2vHd9sMx1cRb6yJz0wLk8eFg3a",
"graceUntil": "2026-09-21T14:00:02.000Z",
"note": "Store this key now — it is shown only once. The previous key keeps working until 2026-09-21T14:00:02.000Z, then 401s."
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | graceMinutes is not an integer between 0 and 10080. |
| 409 | conflict | The key has already been rotated — rotate its successor instead — or it is revoked, or somebody rotated or revoked it a moment before you did. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active. Reads stay open to a lapsed workspace; writes do not. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking (details.feature is callTracking), or does not include API access (details.feature is apiAccess). It is also the answer when the workspace already holds as many keys as its plan allows — then details.feature is maxApiKeys and details carries limit and current beside it. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No such key in the workspace. Another workspace's key and an id that is not a UUID answer exactly as a missing one does. An agencyId that is not a workspace you administer answers the same 404. |
Revoke a key
/api/tracking/api-keys/:id/revokeKills the key at once and clears any grace window it was in. Takes no body. This is the emergency route, so it is never gated on the plan: a workspace that has changed plan, or whose subscription has lapsed, can still stop a key that has leaked. Recorded in the activity log (api_key, high severity).
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The key's id. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/api-keys/9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f/revoke" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The key, now revoked.
{
"apiKey": {
"id": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_Qx7m",
"scopes": [
"calls:read",
"reports:read"
],
"env": "live",
"state": "revoked",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 18204,
"graceUntil": null,
"rotatedFromId": null,
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": "2026-09-20T14:02:51.000Z",
"lastUsedIp": "203.0.113.24",
"revokedAt": "2026-09-20T14:12:30.000Z",
"createdAt": "2026-08-02T11:20:41.000Z"
}
}Errors
| Status | Code | When |
|---|---|---|
| 409 | conflict | The key is already revoked. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active. Reads stay open to a lapsed workspace; writes do not. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No such key in the workspace. Another workspace's key and an id that is not a UUID answer exactly as a missing one does. An agencyId that is not a workspace you administer answers the same 404. |
Read one key's requests
/api/tracking/api-keys/:id/requestsEvery request this key made, newest first, refusals included, with the key itself beside the page so a screen has its label without a second call. Changes nothing.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The key's id. |
Query parameters
| Name | Type | Description |
|---|---|---|
statusOptional | string | A status class, not one status: what somebody debugging asks is "show me the failures".2xx, 3xx, 4xx or 5xx |
methodOptional | string | Only requests made with this method.GET, POST, PUT, PATCH or DELETE |
errorCodeOptional | string | Only requests refused with this code, such as rate_limited or insufficient_scope.up to 80 characters |
qOptional | string | Matches part of the path, ignoring case.up to 200 characters |
fromOptional | date | Only requests made at or after this moment. |
toOptional | date | Only requests made at or before this moment. |
pageOptional | integer | The page to return.from 1, default 1 |
pageSizeOptional | integer | Rows per page. A larger value is clamped, not refused.1–200 · default 50 |
Example request
curl "https://api.buy3.io/api/tracking/api-keys/9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f/requests?status=4xx&from=2026-09-19T00:00:00Z" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200A page of requests.
queryis the parsed query string;statusanderrorCodeare what the caller was answered with;requestIdis the id in the reply'sX-Request-Idheader and in any error body.
{
"data": [
{
"id": "48211904",
"apiKeyId": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"keyPrefix": "b3_live_Qx7m",
"keyLabel": "BI warehouse sync",
"method": "POST",
"path": "/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/conversion",
"query": null,
"status": 403,
"errorCode": "insufficient_scope",
"latencyMs": 6,
"ip": "203.0.113.24",
"userAgent": "warehouse-sync/1.4",
"requestId": "5d1c0d2e-8a4f-4c0b-9e1d-2f3a4b5c6d7e",
"at": "2026-09-20T14:03:02.000Z"
},
{
"id": "48211903",
"apiKeyId": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"keyPrefix": "b3_live_Qx7m",
"keyLabel": "BI warehouse sync",
"method": "GET",
"path": "/api/v1/calls",
"query": {
"outcome": "converted",
"pageSize": "100"
},
"status": 200,
"errorCode": null,
"latencyMs": 84,
"ip": "203.0.113.24",
"userAgent": "warehouse-sync/1.4",
"requestId": "5d1c0d2e-8a4f-4c0b-9e1d-2f3a4b5c6d7e",
"at": "2026-09-20T14:02:51.000Z"
}
],
"page": 1,
"pageSize": 50,
"total": 2,
"totalPages": 1,
"apiKey": {
"id": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"label": "BI warehouse sync",
"keyPrefix": "b3_live_Qx7m",
"scopes": [
"calls:read",
"reports:read"
],
"env": "live",
"state": "active",
"ownerAgencyId": "8d6f2b1c-0a94-4e73-b5d8-1c2e3f4a5b60",
"allowCidrs": [
"203.0.113.0/24"
],
"rateLimitPerMinute": 60,
"useCount": 18204,
"graceUntil": null,
"rotatedFromId": null,
"createdBy": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"lastUsedAt": "2026-09-20T14:02:51.000Z",
"lastUsedIp": "203.0.113.24",
"revokedAt": null,
"createdAt": "2026-08-02T11:20:41.000Z"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | status is not a status class, method is not a method, or from or to is not a date. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No such key in the workspace. Another workspace's key and an id that is not a UUID answer exactly as a missing one does. An agencyId that is not a workspace you administer answers the same 404. |
Read every key's requests
/api/tracking/api-requestsThe same log across every key in the workspace, each row naming the key that made it. This is the page you open on "what is failing anywhere", and you ask which key only once you know something is. Changes nothing.
Query parameters
| Name | Type | Description |
|---|---|---|
apiKeyIdOptional | uuid | Narrow to one key. A key id from another workspace simply matches nothing. |
statusOptional | string | A status class, not one status: what somebody debugging asks is "show me the failures".2xx, 3xx, 4xx or 5xx |
methodOptional | string | Only requests made with this method.GET, POST, PUT, PATCH or DELETE |
errorCodeOptional | string | Only requests refused with this code, such as rate_limited or insufficient_scope.up to 80 characters |
qOptional | string | Matches part of the path, ignoring case.up to 200 characters |
fromOptional | date | Only requests made at or after this moment. |
toOptional | date | Only requests made at or before this moment. |
pageOptional | integer | The page to return.from 1, default 1 |
pageSizeOptional | integer | Rows per page. A larger value is clamped, not refused.1–200 · default 50 |
Example request
curl "https://api.buy3.io/api/tracking/api-requests?status=5xx&pageSize=100" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200A page of requests across the workspace's keys.
{
"data": [
{
"id": "48211904",
"apiKeyId": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"keyPrefix": "b3_live_Qx7m",
"keyLabel": "BI warehouse sync",
"method": "POST",
"path": "/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/conversion",
"query": null,
"status": 403,
"errorCode": "insufficient_scope",
"latencyMs": 6,
"ip": "203.0.113.24",
"userAgent": "warehouse-sync/1.4",
"requestId": "5d1c0d2e-8a4f-4c0b-9e1d-2f3a4b5c6d7e",
"at": "2026-09-20T14:03:02.000Z"
},
{
"id": "48211902",
"apiKeyId": "3c4d5e6f-7081-4293-a4b5-c6d7e8f90a1b",
"keyPrefix": "b3_live_2Vb9",
"keyLabel": "CRM postback",
"method": "POST",
"path": "/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/tags",
"query": null,
"status": 200,
"errorCode": null,
"latencyMs": 41,
"ip": "203.0.113.24",
"userAgent": "warehouse-sync/1.4",
"requestId": "5d1c0d2e-8a4f-4c0b-9e1d-2f3a4b5c6d7e",
"at": "2026-09-20T14:02:51.000Z"
}
],
"page": 1,
"pageSize": 50,
"total": 2,
"totalPages": 1
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | apiKeyId is not a UUID, status is not a status class, method is not a method, or from or to is not a date. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | An agencyId that is not a workspace you administer answers the same 404. |
