Reason codes

Every reason a call was not routed and every reason a target was skipped — the code, its label, the sentence the console shows, whose problem it is and how to fix it.

After this page you can read any refusal the router made, say whose problem it is, and turn a code into a filter, a report row or an alert. Whenever the router declines to do something it records a reason code and a sentence. The code is short, stable and never renamed, so you can filter, group and page on it; the sentence is what a person reads.

There are two families. An unrouted reason ends the call: a gate failed before any target was dialled, or every target was tried and none took it. A call has at most one, and it is stored on the call itself. A skip reason passes over one entry in the routing plan while the call carries on down it; one call can collect many, and they live in the call's waterfall.

Unrouted reasonSkip reason
How many per callAt most oneOne per target considered
Where it is storedOn the call — reasonCode and reasonSentenceIn the call's waterfall, against that target
What it did to the callEnded itNothing — the call moved to the next target
Can a report group by itYes: groupBy=reasonNo
Can the call log filter on itYes: reason=No — a skip code answers 400
Which event carries itcall.unroutedNone; read the waterfall

Unrouted reasons

These are listed in the order the router tests them, which is also the order of decreasing blast radius: your account, then the campaign, then the caller, then the plan. The first one that applies is the one the call gets, so a call on a campaign that is both closed and capped reads campaign_closed.

CodeLabelThe sentenceWhose
workspace_inactiveSubscription inactiveThe workspace's subscription is not active, so its numbers are not routing calls. Restart the plan under Wallet & billing.Workspace
workspace_unfundedWallet emptyThe workspace wallet is empty, so the call was not routed. Add funds under Wallet & billing and routing resumes on the next call.Workspace
campaign_not_liveCampaign not liveThe campaign this number belongs to is not live. Set the campaign live to start routing its calls.Campaign
caller_blockedCaller blockedThe caller's number is on the blocked callers list.Workspace
anonymous_blockedAnonymous callerThe caller hid their number, and this campaign does not accept anonymous callers.Campaign
campaign_closedOutside campaign hoursThe call arrived outside the campaign's hours of operation.Campaign
campaign_cappedCampaign cap reachedThe campaign has reached its call cap.Campaign
repeat_blockedRepeat callerThis caller has called the campaign before, and the campaign blocks repeat callers.Campaign
no_targetsNo targets in the planThe campaign's routing plan has no active targets. Add at least one target to the routing plan.Campaign
no_eligible_targetNo eligible targetEvery target in the routing plan was skipped, so nobody could take this call. The waterfall lists the reason for each one.Campaign
all_targets_failedNo target answeredEvery eligible target was dialled and none of them answered.Target
Eleven codes, and only these eleven. outcome=unrouted on the call log is exactly the set of calls that ended on one of them.

What to do about each one

CodeFirst thing to check
workspace_inactiveThe plan under Wallet & funding → Plan & billing. Nothing routes until the subscription is live again; see Billing and usage.
workspace_unfundedThe wallet balance. Routing resumes on the next call after funds arrive — there is nothing to switch back on.
campaign_not_liveThe campaign's status. A draft or paused campaign keeps its numbers and refuses their calls.
caller_blockedThe blocked callers list. If it was a mistake, remove the number; the next call from it routes normally.
anonymous_blockedThe campaign's anonymous-caller policy. Some traffic sources withhold caller ID by default, so check the publisher before you blame the caller.
campaign_closedThe campaign's hours of operation and the time zone they are read in. A campaign whose hours are set in the wrong zone turns away a whole shift.
campaign_cappedThe campaign's caps. The stored sentence names the window and the figure, so you can tell an hourly cap from a monthly one without opening the campaign.
repeat_blockedThe campaign's repeat-caller policy. This is a campaign-level rule, and it is not the same thing as a target's duplicate window.
no_targetsThe routing plan. An empty plan, or one whose every entry is inactive, refuses before it dials anything.
no_eligible_targetThe waterfall. Every target has its own skip reason beside it, and that is where the real cause is — a cap, a filter or a state rule.
all_targets_failedThe dial attempts on the waterfall and their SIP results. Targets answered the phone network and did not answer the call.

Skip reasons

Why one target on the routing plan was passed over. The call carried on to the next entry, so a call full of skips can still connect — and usually does. A skip is only a problem when every entry has one, which is no_eligible_target.

CodeLabelThe sentenceWhose
pausedTarget pausedThe target is paused.Target
buyer_pausedBuyer pausedThe buyer that owns this target is paused.Buyer
no_destinationNo destinationThe target has no phone number or SIP address to send calls to.Target
suppressedTemporarily switched offThe target was switched off for a short time after repeated failures. It is retried automatically.Target
closedOutside target hoursThe call arrived outside the target's hours of operation.Target
stateState not acceptedThe caller's state is not one this target accepts.Target
concurrencyAt concurrency limitThe target is already on its maximum number of simultaneous calls.Target
cappedTarget cap reachedThe target has reached its call cap.Target
buyer_cappedBuyer cap reachedThe buyer has reached its call cap.Buyer
spend_cappedRevenue cap reachedThe target has reached its daily revenue cap.Target
duplicateDuplicate callerThis caller already reached the target inside its duplicate window.Target
tag_filterFiltered by tagsThe call's tags did not pass the tag filters for this target.Target
no_bidNo bidThe buyer's bidding endpoint declined to bid on this call.Buyer
timeoutBid timed outThe buyer's bidding endpoint did not answer in time.Buyer
errorBid request failedThe buyer's bidding endpoint returned an error.Buyer
The last three belong to an external RTB target: the buyer was asked for a price and did not give one. They are call-routing reasons, not the numeric vocabulary a ping is refused with — that is Reject codes.

What to do about each one

  • paused, buyer_paused and no_destination are configuration. Somebody switched the target or its buyer off, or saved a target with nothing to dial. Nothing recovers on its own.
  • suppressed is the platform protecting you: a destination that failed repeatedly is stood down briefly and tried again without anybody doing anything. Repeated suppression is a conversation to have with the buyer.
  • closed, state, capped, buyer_capped, spend_capped and concurrency are the terms you agreed with the buyer, working. If they skip more traffic than you expected, the terms are wrong, not the router.
  • duplicate is the target's duplicate window, not the campaign's repeat-caller policy. The two are different settings with different owners — see Repeat and blocked callers.
  • tag_filter names the tag that failed in the stored sentence, so you can see which rule fired without reading the whole filter set. See Filters and tags.
  • no_bid, timeout and error are the buyer's endpoint. The ping ledger has the request that was sent and the answer that came back, which is what their engineer will ask for.

The sentence is written at the time

Each table above shows the context-free sentence: the one that is true on its own, with no call in front of it. What the router actually stores is richer, because it knows the names and the figures at the moment it refuses. Those stored sentences never change afterwards — a cap you raise tomorrow does not rewrite what yesterday's call says about itself.

CodeContext-freeAs stored on a real call
campaign_cappedThe campaign has reached its call cap.The campaign Acme Health — Inbound has reached its daily cap of 500 calls.
cappedThe target has reached its call cap.The target BlueSky Legal — Intake has reached its hourly cap of 25 calls.
spend_cappedThe target has reached its daily revenue cap.The target BlueSky Legal — Intake has reached its daily revenue cap of $2,500.00.
concurrencyThe target is already on its maximum number of simultaneous calls.The target Acme Health — Dallas floor is already on its maximum of 6 simultaneous calls.
stateThe caller's state is not one this target accepts.The target BlueSky Legal — Intake does not accept calls from NV.
duplicateThis caller already reached the target inside its duplicate window.This caller already reached the target Acme Health — Dallas floor in the last 30 days.
repeat_blockedThis caller has called the campaign before, and the campaign blocks repeat callers.This caller already called the campaign on 2026-08-14, and the campaign blocks repeat callers.
all_targets_failedEvery eligible target was dialled and none of them answered.3 targets were dialled and none of them answered.
timeoutThe buyer's bidding endpoint did not answer in time.The bidding endpoint of the buyer BlueSky Legal did not answer within 1,500 ms.
errorThe buyer's bidding endpoint returned an error.The bidding endpoint of the buyer BlueSky Legal returned HTTP 503: upstream unavailable.
A code with nothing extra to say — paused, no_destination, campaign_closed — stores its context-free sentence unchanged.

A code the platform does not recognise still produces a sentence rather than an error, because this runs inside a live call: an unknown code reads "The call was not routed (<code>)." Treat an unknown code the same way — as "not routed" — and show the sentence, which always arrives beside it.

Whose problem it is

Every code carries an owner, which is the console's way of sending a reader to the right screen. It is one of five words, and it is stable — an integration can route an alert on it without parsing English.

ownerMeansWhere to go
workspaceYour account: the subscription, the wallet, the blocklist.Wallet, Billing and usage, blocked callers.
campaignThe campaign's own settings: status, hours, caps, policies, routing plan.Campaigns overview.
targetOne destination: its status, hours, caps, states, duplicate window, or the phone at the other end.Targets.
buyerThe company behind the target: paused, capped, or a bidding endpoint that would not answer.Buyers.
platformOnly ever returned for a code the platform does not recognise. Tell us.

What a publisher is told

A publisher sees the calls they sent you, and they see why a call was not connected — but not in your words. Your sentences name your wallet, your plan and your buyers' caps, and none of that is a partner's business. So a publisher's call page is built from a different table, never filtered from yours: nothing the router stored is copied into a publisher's answer.

What a publisher needs to know is whether the fault was theirs — the caller, the hour, the cap they were given — or not. That is exactly what these sentences say, and no more.

CodeWhat the publisher reads
workspace_inactiveThe campaign was not accepting calls when this call arrived.
workspace_unfundedThe campaign was not accepting calls when this call arrived.
campaign_not_liveThe campaign was not accepting calls when this call arrived.
caller_blockedThis caller's number is blocked on the campaign.
anonymous_blockedThe caller hid their number, and this campaign does not accept anonymous callers.
campaign_closedThe call arrived outside the campaign's hours of operation.
campaign_cappedThe campaign had reached its call cap.
repeat_blockedThis caller has called the campaign before, and the campaign does not accept repeat callers.
no_targetsNo buyer was available to take this call.
no_eligible_targetNo buyer was available to take this call.
all_targets_failedNo buyer answered this call.
Anything else — including a code added after a publisher's integration was written — reads "The call was not connected to a buyer."
  • The first three codes collapse into one sentence on purpose. "The wallet is empty" is a thing a seller would much rather tell a partner itself, and a lapsed subscription is nobody else's business.
  • The codes that are genuinely about the traffic — a blocked caller, a withheld number, the hour, a repeat caller — are passed through in full, because a publisher can act on them.
  • Skip reasons are never shown to a publisher at all. They are about your buyers, and a publisher does not see your buyers.
  • Your own call page still shows the full sentence. The two are different views of one call, not two records; see The call waterfall.

Reading a code out of the API

A call carries reasonCode and reasonSentence, and both are null on a call that routed. reasonSentence is the sentence the router stored at the time, falling back to the context-free one if none was stored.

An unrouted call in the call log
{
  "id": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
  "ref": "CA-482137",
  "outcome": "unrouted",
  "status": "no_answer",
  "reasonCode": "campaign_capped",
  "reasonSentence": "The campaign Acme Health — Inbound has reached its daily cap of 500 calls."
}

The call.unrouted event carries the code as reason, the stored sentence beside it, and action — what the caller then heard.

A call.unrouted delivery
{
  "id": "req_5f0c2a91b7d34e10",
  "event": "call.unrouted",
  "createdAt": "2026-09-20T14:03:12.418Z",
  "data": {
    "callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
    "ref": "CA-482137",
    "status": "rejected",
    "campaignId": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
    "campaignName": "Acme Health — Inbound",
    "reason": "workspace_unfunded",
    "sentence": "The workspace wallet is empty, so the call was not routed. Add funds under Wallet & billing and routing resumes on the next call.",
    "action": "hangup"
  }
}

Worked example: which refusal is costing the most

Yesterday was quiet on Acme Health — Inbound and nobody knows why. Ask for the calls behind one suspicion, then ask the whole day to rank itself. This needs a key with calls:read and reports:read; see API keys.

One reason, then every reason
# Every call yesterday that a full campaign cap turned away
curl -G "https://api.buy3.io/api/v1/calls" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  --data-urlencode "reason=campaign_capped" \
  --data-urlencode "from=2026-09-19T05:00:00Z" \
  --data-urlencode "to=2026-09-20T05:00:00Z"

# And the same window grouped by reason, worst first
curl -G "https://api.buy3.io/api/v1/reports/summary" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  --data-urlencode "groupBy=reason" \
  --data-urlencode "from=2026-09-19T05:00:00Z" \
  --data-urlencode "to=2026-09-20T05:00:00Z"
200 OK (one row shown)
{
  "groupBy": ["reason"],
  "rows": [
    {
      "dimensions": [{
        "dimension": "reason",
        "key": "campaign_closed",
        "label": "Outside campaign hours",
        "sentence": "The call arrived outside the campaign's hours of operation.",
        "owner": "campaign"
      }],
      "calls": 61,
      "connected": 0,
      "converted": 0,
      "unrouted": 61,
      "connectRate": 0,
      "conversionRate": null,
      "revenueCents": 0
    }
  ]
}

Sixty-one calls arrived outside the campaign's hours, and the row hands you the label, the sentence and the owner without a second lookup. owner is campaign, so the fix is the campaign's hours — most often a schedule saved in the wrong time zone. Note that a report grouped by reason counts only unrouted calls: routed calls have no reason and are left out of the rows, though they are still in totals.

Using the codes

  • Filter the call log by reason to see every call a closed campaign or a full cap cost you, then export it — see Exports.
  • Group a report by reason to find the one worth fixing first. Rank by calls, not by how loud the complaint was.
  • Alert on them: subscribe a webhook to call.unrouted and page somebody when workspace_unfunded or no_eligible_target turns up. The first is money; the second is a plan that has quietly stopped having anyone in it.
  • Branch on the code, print the sentence. The code is the contract and the sentence is the copy. Writing your own English for a code means maintaining a second copy of this page.
  • Expect new codes. They are only ever added, never renamed or removed. An integration that treats an unknown code as "not routed" and shows its sentence never needs changing.

Next steps