Error reference
Every error from the voepy API returns the same shape:
{
"error": {
"code": "balance_insufficient",
"message": "Tenant balance is exhausted; top up to place new calls."
}
}
The HTTP status mirrors the category. Use code for branching logic —
message is human-friendly but not stable across releases.
Categories
| Status | Category | What to do |
|---|
| 400 | Client malformed the request | Read message, fix the body, retry. |
| 401 | Missing / bad credentials | Re-check your API key. |
| 402 | Payment required | Top up the balance. |
| 403 | Authorized but not allowed | Scope, quota, or compliance issue — read the code. |
| 404 | Resource doesn't exist | The ID is wrong, deleted, or belongs to another tenant. |
| 409 | State conflict | The resource isn't in the state the action requires. |
| 422 | Semantically invalid | Body parses but is logically wrong. |
| 429 | Rate-limited | Back off; respect Retry-After. |
| 500 | Server bug | Retry; if it persists, file a ticket. |
| 502 | Upstream carrier failed | Often retriable. Check message for hint. |
| 503 | Service degraded | Back off, retry. |
Auth & access
| Code | HTTP | Cause |
|---|
authentication_required | 401 | No Authorization header, or the header isn't Bearer <key>. |
invalid_credentials | 401 | Key not found, revoked, or expired. |
tenant_suspended | 403 | Your tenant is paused (unpaid invoice, KYC issue, etc.). |
forbidden | 403 | Key is valid but lacks the required scope (e.g. you used a calls:read key for POST /v1/calls). |
Billing
| Code | HTTP | Cause |
|---|
balance_insufficient | 402 | Your balance + credit limit ≤ 0 for the requested action. Top up. |
daily_spend_cap_exceeded | 403 | You hit your tenant's daily spend cap. Resets at UTC midnight. |
concurrent_call_cap_exceeded | 403 | You hit your tenant's concurrent-call cap. Wait for in-flight calls to end. |
auto_recharge_failed | 402 | The last auto-recharge attempt failed (usually a card decline). Re-attach a working card. |
payment_method_required | 400 | Action needs a saved card and you have none. |
subscription_invalid | 422 | Plan slug doesn't exist or isn't public. |
topup_amount_too_low | 400 | amount_cents below the policy minimum. |
Calls
| Code | HTTP | Cause |
|---|
from_number_not_owned | 403 | from isn't a tenant-owned active DID and caller_id_forwarding isn't enabled. |
country_blocked | 403 | Destination country is on your quota's blocked list. |
country_not_allowed | 403 | Destination country isn't on your allow-list (when an allow-list is configured). |
prefix_blocked | 403 | Destination prefix matches your blocked-prefix list. |
call_already_ended | 409 | You tried an action on a terminal call. |
call_not_in_required_state | 409 | The action's "valid from" status doesn't match (e.g. bridge on a ringing call). |
recording_not_ready | 409 | Recording exists but status != available — try again later. |
idempotency_conflict | 409 | Same Idempotency-Key used for a different request body. Pick a new key. |
unprocessable | 422 | Body fields are valid types but logically conflict. |
upstream_error | 502 | Carrier-side failure. Retry with backoff; check the message. |
Phone numbers
| Code | HTTP | Cause |
|---|
no_pricing_tier_configured | 422 | We don't have pricing for that country / number-type combination. Email support. |
insufficient_balance | 402 | Order NRC can't be covered by your balance. Top up first. |
order_invalid | 400 | Numbers aren't reservable (already sold, withdrawn, etc.). |
number_not_yet_active | 409 | You tried to PATCH a pending number. Wait for activation. |
messaging_profile_not_provisioned | 409 | messaging_enabled: true but your tenant has no messaging profile. Contact support. |
number_not_found | 404 | DID id is wrong or doesn't belong to your tenant. |
release_not_permitted | 409 | Number is the last DID on an active porting order or anchor for emergency-address compliance. |
Webhooks
| Code | HTTP | Cause |
|---|
invalid_target_url | 400 | URL isn't HTTPS or isn't a valid URL. |
event_types_required | 400 | You sent event_types: []. Use ["*"] to subscribe to everything. |
subscription_not_found | 404 | Subscription id is wrong or belongs to another tenant. |
signing_secret_unavailable | 404 | You're trying to read a signing_secret post-creation — it's only returned once. Delete and recreate. |
Porting
| Code | HTTP | Cause |
|---|
porting_not_supported | 422 | The losing-carrier doesn't support hosted porting for these numbers. |
csr_mismatch | 422 | Customer Service Record fields don't match what you submitted. |
documents_required | 422 | LOA / bill upload missing. |
porting_in_progress | 409 | Another porting order already exists for one of the numbers. |
Conferences
| Code | HTTP | Cause |
|---|
conference_not_found | 404 | Conference id is wrong. |
conference_full | 409 | Participant cap reached. |
participant_not_found | 404 | Participant id isn't in the conference. |
conference_action_invalid | 409 | Action isn't valid in the conference's current state (e.g. mute on a left participant). |
Recordings / streams / transcription
| Code | HTTP | Cause |
|---|
recording_not_ready | 409 | status != available. |
recording_already_stopped | 409 | stop_recording on a call with no active recording. |
streaming_already_active | 409 | Tried to start a second stream with the same id on one leg. |
streaming_not_found | 404 | stream_id doesn't match an active stream. |
transcription_already_active | 409 | Already running on this leg. |
transcription_not_active | 409 | transcription_stop with nothing running. |
Generic
| Code | HTTP | Cause |
|---|
invalid_request | 400 | Body malformed, unknown field, or missing required. Read message. |
not_found | 404 | Resource missing. |
conflict | 409 | Generic state conflict not covered by a more specific code. |
rate_limited | 429 | Slow down. Inspect Retry-After header. |
service_unavailable | 503 | Service is degraded. Retry with backoff. |
internal_error | 500 | Our bug. Retry and file a ticket if it persists. |
upstream_error | 502 | Underlying carrier failed. Retry; check message. |
Retry policy
| Status | Retriable? | Strategy |
|---|
| 400, 401, 403, 404, 409, 422 | No | Fix the request. |
| 402 | After top-up | Top up balance, then retry. |
| 429 | Yes | Honor Retry-After; exponential backoff. |
| 500, 502, 503 | Yes | Exponential backoff with full jitter, max ~10 minutes. |
Always send Idempotency-Key on retried mutations so retries don't
double-charge or double-dial.
Where to look when something breaks
- Reproduce against the OpenAPI playground at
https://api.voepy.com/api/docs/ — confirms request shape.
- Inspect webhook deliveries at
GET /v1/webhook-deliveries if
the issue is "I'm not receiving an event."
- Check audit log at
GET /v1/audit for state-change history on
your tenant.
- Look at the live balance + quota at
GET /v1/account and GET /v1/quota for any 402 / 403 from spend-cap or balance.