sphoroVOICEdocs
Markdown

Concurrency

How many calls your account carries at once, what happens to the ones that arrive after that, and why an outbound list has to be paced rather than fired. Worth reading before a campaign, not after it produces a wall of at_capacity.

Two numbers, not one

Your account has a ceiling on calls that are talking, and a second, separate one on calls that are waiting. Past both, a caller is turned away immediately.

arriving call
     │
     ├─ a slot is free ─────────────▶ admitted        the greeting, as always
     ├─ full, room in the queue ────▶ held            the hold line, on repeat
     └─ full, and the queue is full ▶ turned away     the busy line, then hangup

Why an inbound call cannot simply be "queued"

The caller is already connected. There is no buffer to put them in — "queued" can only mean "answered and held", and that is not free: the carrier leg is up and billing.

What holding does save is the expensive part. A held call has no speech recogniser, no model and no synthesis behind it, which is where both the money and the memory go. So holding trades a little carrier spend for a lot of headroom, which is the trade a contact-centre queue has always made.

A queue with no bound is a slower way to run out of memory. That is why the second number exists. A cap that refuses cleanly is worth more than a cap that is merely documented — the failure mode of no cap is not "call 501 is refused", it is every call in progress degrading at once.

Fairness

Waiting calls are queued per account and admitted round-robin, and one account may hold at most half the queue plus one. Without that, a single runaway campaign fills a shared queue and every other account's callers wait behind it — one customer's incident becomes everybody's.

Outbound: pacing, not queueing

An outbound call that does not fit is not held, because nobody is waiting on the line yet. It simply fails with end_reason: at_capacity and is yours to retry.

This is the single most common way a first campaign goes wrong. A loop that fires a thousand POST /v1/calls with no gap fills the ceiling in seconds; everything after that comes back at_capacity, and you are left reconciling hundreds of failures that were never dialled. Nothing was wrong with the numbers, the agent, or the carrier.

Two ways to avoid it:

ApproachWhat it does
A campaignPaces the dialling for you, against your own ceiling, and can be paused and resumed. This is the answer for a list of any size.
Your own loop with a gapFine for tens of calls. Derive the Idempotency-Key from the list and the number so re-running it is safe. See placing calls.

What one call costs in capacity

Kind of callTakes a slot
A conversation agent, inbound or outboundYes — recogniser, model and voice
A keypad agentYes, but a much cheaper one: nothing in a turn is a network call
A normal agentOnly until it transfers. After the handover the call is between the caller and the person.
A held inbound callA queue place, not a talking slot
A webrtc callEffectively nothing after the introduction — the audio runs directly between the two clients

Knowing where you are

Both of these read the same window; the first is the number that matters, the second is the list of calls behind it.

Shell
# How many calls did not go out because you were full?
curl -s "https://voice.sphoro.com/v1/analytics/overview?from=2026-09-01&to=2026-09-08" \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
| jq '.end_reasons.at_capacity'

# Which ones were they?
curl -s "https://voice.sphoro.com/v1/calls?limit=100" \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
| jq '[.data[] | select(.end_reason == "at_capacity") | .to]'

Concurrency shows up two ways: as at_capacity on individual calls, and as a number in analytics. The second is the one to watch — a rising count of at_capacity over a week is a ceiling you have grown into, not a bad afternoon.

Raising the ceiling is a conversation, not a setting. Concurrency is provisioned on your account. If your at_capacity count is climbing, or a campaign needs headroom it does not have, tell us before the campaign rather than during it.