Skip to main content

Troubleshooting: voice call park orbit

Call park works like a bank of numbered parking spots: your organization gets nine shared slots, numbers 1 through 9, and every parked call occupies one until someone retrieves it. A softphone taps “Park” and the call lands in a slot with on-hold music; any agent in the organization retrieves it by picking that slot’s number in the softphone or dialing *N on a registered device. This page maps each park-orbit error code to the slot state that produced it and the fix you own. All rejects below are pre-bridge guards — the caller still hears hold music and no party is dropped.

Code matrix — which slot state fired

Find the error code first, then jump to its section below.

All slots occupied — PARK_SLOTS_FULL

409 PARK_SLOTS_FULL fires when a park attempt leaves slot selection unspecified — the normal “Park” button path — and the auto-pick finds no free slot between 1 and 9. The error body carries details.occupiedSlots, the sorted list of slot numbers holding live calls, so you can see which slots are stuck. Fix it by freeing a slot, releasing by hand, or parking explicitly:
  1. List the live parked calls (GET /api/v1/voice/park, or the parked-calls panel in the softphone). Each entry shows the slot, the caller, and parkedForSeconds — a very large value marks the entries nobody picked up.
  2. Retrieve a stale entry. A teammate retrieves the slot (POST /api/v1/voice/park/retrieve with { "slot": N }, or dials *N from a registered device). Once a slot frees, retry the park.
  3. Or release by park ID. An operator clears a stale entry with POST /api/v1/voice/park/{parkId}/release — the timed-out-for-days case where the eventual retrieve never happened.
  4. Or park into a named slot. If a slot in the range 1 to 9 is free and the reject fired anyway, the client picked an occupied slot from a stale pre-park list — re-list and name a slot the fresh list does not show (POST /api/v1/voice/park with { "callSid": "...", "slot": N }).
The model is per-organization and staffed by the whole team’s org-wide roster: every member’s parked call draws from the same nine slots, so the fix is always “free a slot,” not “clear your own.”

Requested slot occupied — PARK_SLOT_OCCUPIED

409 PARK_SLOT_OCCUPIED fires when the park body named an explicit slot and that slot already holds a live call. The response carries details.occupant — the slot’s current occupant view with caller number, parked-at time, and elapsed seconds — so the client can render “Slot 4 busy since 0:42 (Alice, +14155…)” instead of a bare 409. Two park races into the same slot within a few milliseconds resolve through this same code — the second attempt is the lost race against the live-slot uniqueness guard, so retrying it never makes it through. Fix it by parking into a different slot:
  1. Read details.occupant — the occupant answers “who is on this slot?”
  2. Re-issue the park with slot omitted and let auto-pick take the lowest free one, or name a slot the occupant view does not hold.

Retrieve rejects — PARK_SLOT_EMPTY / PARK_ALREADY_RETRIEVED

Retrieve path (POST /api/v1/voice/park/retrieve or the dial *N) is idempotent against slot state — calling it when nothing live sits in the slot returns one of two codes:
  • 404 PARK_SLOT_EMPTY — no live call is currently parked in the slot. The slot was never parked, or it freed earlier and nobody picked anything back into it. Re-list (GET /api/v1/voice/park) and retrieve the slot the list shows as live.
  • 410 PARK_ALREADY_RETRIEVED — the slot’s most recent parked call was already claimed. details.prior carries the retrieval metadata (retrieved-by user ID, retrieved-at time, elapsed parked seconds), so the client can answer “Who took it?” — usually the teammate across the floor who dialed *N at the same moment you tapped.
Fix both at the side that answered first — the 410 tells you the call has an owning agent, and the details.prior.retrievedByUserId names them.

Release rejects — PARK_ALREADY_RELEASED / PARK_NOT_FOUND

POST /api/v1/voice/park/{parkId}/release clears a specific parked-call record by its park_… identifier:
  • 410 PARK_ALREADY_RELEASED — the record exists but already carries a retrievedAt stamp (it was retrieved or previously released). Safe to treat as success — the slot is free.
  • 404 PARK_NOT_FOUND — no parked call exists under that identifier, usually because the ID came from an earlier list response that another release has since aged off the live view. Re-list before re-issuing the release.

Server-side maps — what the records look like

A parked call is one park_… record with the slot, the parking user’s ID, the caller’s E.164 number and display name, a parkedAt timestamp, and a server-computed parkedForSeconds the dashboard renders as the elapsed badge. The record flips retrievedAt exactly once — retrieve, release, the park-timeout sweeper, or the caller hanging up — so the live view is at most nine rows, sorted newest first. Auto-pick parks scan slot 1 through 9 and land in the first free one; a fixed-slot park goes straight to the requested slot. Either way, a concurrent pair of parks of the same slot resolves to the occupant-code 409 above, never to a double-parked slot. A 409 or 410 in this class is not a server misread — it reports a true slot-state conflict, so if the dial keeps firing them after every fix above, work the checklist below before escalating.

Do you legitimately need more slots?

The orbit is bounded to nine slots on purpose — that’s the retro-telephone affordance *1..*9 mapped onto single-digit DTMF retrieval codes, and adding a 10th slot breaks both the dial codes and the muscle memory the feature takes its name from. The orbit model trades slot-capacity for familiarity. The nine-slot cap clears itself when retrieval or release happens promptly. When your org consistently parks more than nine live calls at once, the fix is coordination, not a call for a capacity flag — this is a shared bank, and every new claim on it steals one from someone else’s retrieval list. Where the need is real, these alternatives pick up the load:
  • Use the queue path for inbound volume — inbound callers waiting for an agent belong in a queue, which holds hundreds; park slots are the shared operator lobby, not the customer inbound queue.
  • Ring a named teammate — target a specific user or device with a transfer instead of the shared-lobby model.
  • Flag a sustained saturation pattern to support — if your org genuinely fills all nine slots day after day, that says the traffic moved past the lobby pattern; scope the right model with support rather than retraining the team on new dial codes.

What to capture before escalating

Do the fixes above first. If the code still fires — the slot genuinely was free, or a retrieve returned PARK_ALREADY_RETRIEVED for a slot still showing in the live list — open a ticket with:
  1. The full error body — code, message, and the details object (occupant or prior-retrieval view when present).
  2. The slot number and the park or retrieve identifier involved.
  3. The park-id of any stuck entries and the parkedForSeconds the live list shows for them.
  4. Your organization ID (Settings → Organization, or organizationId from GET /api/v1/me).

What not to do

  • Do not retry the same explicit-slot park in a loop. The occupant-code 409 resolves to whom you should call — do that instead.
  • Do not treat 409/410 as a crash. They are pre-bridge slot guards; the caller stays on hold music through every one of them.
  • Do not re-map the dial codes. *1..*9 is the fixed affordance, and slot 0 is not part of the orbit.

See also