Errors
Every error, across the whole API, uses one envelope, so you can handle failures with a single code path.
Error shape
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request was invalid.",
"details": [
{ "path": "declaredNumber", "message": "must be E.164" }
]
}
}The HTTP status matches the code. details is present only for validation errors.
Error codes
| Code | HTTP | When |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid key. |
| FORBIDDEN | 403 | The key lacks the required scope. |
| NOT_FOUND | 404 | No such resource on your account. |
| VALIDATION_FAILED | 400 | The request body or query was invalid. |
| CONFLICT | 409 | The action conflicts with current state (e.g. sending from a number that is not connected). |
| RATE_LIMITED | 429 | Too many requests. Slow down and retry. |
| OUTBOUND_LIMIT | 429 | The cold-outbound floor: this number started too many NEW conversations this hour. Replies are never counted. Wait for Retry-After, and keep first-contact volume moderate. |
| SLOT_REQUIRED | 402 | Connecting a number needs a free slot. |
| ACCOUNT_SUSPENDED | 402 | The whole account is on hold. Every route answers this except sign-in and billing. The key is valid and retrying will not help: contact us to restore access. |
| TRANSPORT_UNAVAILABLE | 503 | The connection service is briefly unavailable. Retry shortly. |
| TRANSPORT_BUSY | 503 | The messaging service is busy. Wait for the Retry-After header, then try again; anything already issued stays valid. |
| BILLING_UNAVAILABLE | 503 | This deployment has no payment processor configured. |
| INTERNAL | 500 | An unexpected error on our side. |
Backing off
Three codes tell you to wait, and they mean different things. RATE_LIMITED means slow down. OUTBOUND_LIMIT means stop messaging new people for a while - replies still go through, and the budget belongs to the number rather than to your key. TRANSPORT_BUSY means the messaging service is throttling us: it is load, never a defect in your request, and anything already issued stays valid. All three carry Retry-After in seconds where a wait is known; wait at least that long.
{
"error": {
"code": "OUTBOUND_LIMIT",
"message": "This number has started too many new conversations in the last hour."
}
}Validation errors
For a VALIDATION_FAILED error, details lists each offending field by path so you can surface the problem next to the right input.
const res = await fetch("https://api.sendveo.com/v1/numbers", options)
if (!res.ok) {
const { error } = await res.json()
if (error.code === "VALIDATION_FAILED") {
for (const d of error.details ?? []) showFieldError(d.path, d.message)
} else {
showBanner(error.message)
}
}