Simulate a call
Ask the router what a caller, a set of tags and a moment you choose would get: every gate with a tick or a cross, the plan in dialling order and the reason each skipped target was skipped — without placing a call.
After this guide you will be able to prove a campaign routes the way you think it does before a real caller finds out, read the answer the way you read a call waterfall, and know the four things a simulation deliberately cannot tell you.
The simulator answers one question: what would happen if this person called? It runs the campaign's own gates and resolves its routing plan with the same code a live call uses — and then throws the answer away. Nothing is dialled, nobody is pinged, no counter moves, nothing is written and nothing is charged.
What you give it
On a campaign's detail page, the Test a call panel takes four things. All four are optional.
| Field | Console label | Why it matters |
|---|---|---|
callerId | Caller | Their state comes from the area code, which is what a target's state list reads. It also decides whether they are blocked, a repeat caller or inside a target's duplicate window. Leave it empty to ask about a caller who hid their number. |
tags | Tags | What a publisher's ping or your number-insertion snippet would have supplied. Tag filters read these. Up to 40 keys. |
publisherId | Arriving from | A publisher with a lane on this campaign, so per-publisher pricing rules apply. Empty means owned media with no publisher. |
at | At | The moment to ask about, read on the workspace's clock. Empty means now. This is how you ask "what happens at nine on Sunday?" |
callerId accepts ten digits, eleven beginning with 1, or international format — 8 to 15 digits in all. Anything shorter is read as no caller at all, which is the same as leaving it empty.curl -X POST "https://api.buy3.io/api/tracking/campaigns/6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d/simulate" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"callerId": "+12145550122",
"tags": {
"utm_source": "search",
"age_band": "65plus"
},
"at": "2026-09-20T21:30:00.000Z"
}'Simulating is a console route. It takes a signed-in owner's or admin's session, and an API key cannot call it — a key's job is to run traffic, not to test configurations.
What you get back
The answer is a verdict, not a story: there are no timestamps in it, because nothing happened. The console draws it in the same visual language as a real call's waterfall — one node per decision, failures in red.
| Field | What it holds |
|---|---|
wouldRoute | true only when no gate failed and at least one target is eligible. |
reason | The code and sentence a real call would have ended on, or null when it would have routed. |
firstChoice | The target that would be dialled first, or null. |
mode | The campaign's routing mode, which is the order plan is in. |
plan | The eligible targets in dialling order. Each carries its buyer, destination, priority, weight, revenue and duration threshold. |
skipped | Every target that was passed over, each with a reason code and a sentence naming the target and the figure. |
gates | checks — every gate with passed and its label; failed — the first one that failed, or null; passed — the codes that were cleared. |
caller | e164, the state worked out from the area code and anonymous. |
tags, publisherId, at | What the simulation was run with, echoed back. |
Worked example: a Texas caller at half past four
A Medicare campaign in priority mode with four targets on its plan. You want to know what a Dallas caller gets on a weekday afternoon, carrying the tags a search visitor would have.
{
"callerId": "+12145550122",
"tags": {
"utm_source": "search",
"age_band": "65plus"
},
"at": "2026-09-20T21:30:00.000Z"
}{
"wouldRoute": true,
"reason": null,
"firstChoice": {
"linkId": "0f7a3c52-6d1e-4b8a-9c2f-5e4d3c2b1a09",
"targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"targetName": "Acme Health — Dallas floor",
"targetKind": "static",
"buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"buyerName": "Acme Health",
"deliveryMode": "pstn_did",
"destination": "+12145550188",
"priority": 1,
"weight": 3,
"revenueCents": 4200,
"durationThresholdSeconds": 90,
"source": "static",
"wouldPing": false
},
"mode": "priority",
"plan": [
{
"targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"targetName": "Acme Health — Dallas floor",
"buyerName": "Acme Health",
"priority": 1,
"weight": 3,
"revenueCents": 4200,
"source": "static",
"wouldPing": false
},
{
"targetId": "d3e4f5a6-b7c8-4d9e-8f1a-2b3c4d5e6f7a",
"targetName": "BlueSky Legal — RTB",
"buyerName": "BlueSky Legal",
"priority": 2,
"weight": 1,
"revenueCents": null,
"source": "rtb",
"wouldPing": true
}
],
"skipped": [
{
"targetId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
"targetName": "BlueSky Legal — Intake",
"buyerId": "e2d3c4b5-a697-4887-9bac-1d2e3f4a5b6c",
"buyerName": "BlueSky Legal",
"reason": "closed",
"sentence": "The call arrived outside the hours of the target BlueSky Legal — Intake."
},
{
"targetId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"targetName": "Acme Health — Overflow",
"buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"buyerName": "Acme Health",
"reason": "capped",
"sentence": "The target Acme Health — Overflow has reached its daily cap of 40 calls."
}
],
"gates": {
"passed": [
"workspace_inactive",
"workspace_unfunded",
"campaign_not_live",
"caller_blocked",
"anonymous_blocked",
"campaign_closed",
"campaign_capped",
"repeat_blocked"
],
"failed": null,
"checks": [
{
"code": "workspace_inactive",
"label": "Subscription inactive",
"passed": true,
"sentence": null,
"owner": "workspace"
},
{
"code": "campaign_closed",
"label": "Outside campaign hours",
"passed": true,
"sentence": null,
"owner": "campaign"
}
]
},
"caller": {
"e164": "+12145550122",
"state": "TX",
"anonymous": false
},
"tags": {
"utm_source": "search",
"age_band": "65plus"
},
"publisherId": null,
"at": "2026-09-20T21:30:00.000Z"
}Read from the top:
wouldRouteistrueandfirstChoiceis Acme Health — Dallas floor, so the console's banner reads "This call would ring Acme Health — Dallas floor" with "1 more target is behind it as failover."- Every gate passed. The eight codes in
gates.passedare the eight gates, each drawn with a tick and its label — Subscription inactive, Wallet empty, Campaign not live, Caller blocked, Anonymous caller, Outside campaign hours, Campaign cap reached, Repeat caller. A gate is named by the code it would have failed with, so a tick beside "Outside campaign hours" means the campaign is open. - Two targets are in the plan. Dallas floor is first, marked Dialled first. BlueSky Legal — RTB is second, marked Failover 1 and Pinged for a bid: its
revenueCentsisnullandwouldPingistrue. - Two were skipped, each with the sentence the waterfall would have carried: BlueSky's Intake line is outside its own hours, and Acme's overflow target has filled its daily cap.
- The caller's state is
TX, from the 214 area code. That is what put Dallas floor ahead of a target that only takes Texas.
Ask the same question at nine on a Sunday and the campaign's own hours refuse it before any target is looked at:
{
"wouldRoute": false,
"reason": {
"code": "campaign_closed",
"sentence": "The call arrived outside the campaign's hours of operation."
},
"firstChoice": null,
"mode": "priority",
"plan": [],
"skipped": [],
"gates": {
"passed": [
"workspace_inactive",
"workspace_unfunded",
"campaign_not_live",
"caller_blocked",
"anonymous_blocked"
],
"failed": {
"code": "campaign_closed",
"sentence": "The call arrived outside the campaign's hours of operation."
},
"checks": [
{
"code": "campaign_closed",
"label": "Outside campaign hours",
"passed": false,
"sentence": "The call arrived outside the campaign's hours of operation.",
"owner": "campaign"
}
]
}
}The console prints "This call would not reach a buyer" with that sentence under it, and the caller would get the campaign's unrouted action — the message or the forward you set, or a hang-up if you set neither.
What a simulation cannot know
- Whether a buyer would answer. Nothing is dialled. A target that is eligible on paper can still ring out — use Testing a target to check the destination itself.
- What a bidding buyer would pay. An external RTB target is deliberately not pinged: its price is whatever it bids on the day, and teaching a buyer's endpoint about a caller who does not exist would spend their capacity for nothing. Such a target appears with
wouldPing: true, no price, and — inhighest_bidmode — last in the order, because a price that is not known yet sorts behind every price that is. - Which target a weighted draw would pick. In
weightedmode, and inside a tier inprioritymode, the order is drawn afresh for every call. A simulation shows you one draw. Run it a few times to watch the order change. - Whether a slot will still be free. Caps are read, not reserved. Between a simulation and a real call, somebody else's call may take the last line.
When to run one
- Before going live. The readiness checklist says a plan exists; a simulation says it ends where you meant.
- After any change to hours, caps, filters or the plan — the effect is visible immediately, instead of after the next caller.
- When a buyer asks why they got no calls. Simulate one of the callers they expected and read the reason against their target.
capped,closed,state,duplicateandtag_filterare four different conversations. - When a publisher asks where their calls went. Simulate with their lane selected under Arriving from and the tags they send.
- Before a schedule change. Set At to next Sunday morning and see what your weekend callers will meet.
For a call that has already happened, the call waterfall is the record of what really occurred, with timings and every dial attempt. The simulator is the same picture drawn ahead of time.
