#Errors
The API uses conventional HTTP status codes to signal success or failure, and returns a consistent JSON error object on every failed request. Read the status code first to decide what to do, then read the error body for detail.
#The error object
Every error response has the same shape: a top-level error object with a machine-readable type and a human-readable message.
{
"error": {
"type": "invalid_request",
"message": "field 'to' must be an E.164 phone number"
}
}type— a stable string you can branch on in code (e.g.invalid_request). Match on this, not on the message text.message— a description meant for logs and developers. It is not localized and may change; never parse it.
Branch your code on the HTTP status code and on error.type. Treat error.message as a
human-readable hint only — its wording is not part of the contract.
#Status codes
| Status | Meaning | When it occurs |
|---|---|---|
200 OK | Success | A GET succeeded, e.g. retrieving a message or contact. |
201 Created | Success | A resource was created, e.g. a message was accepted for delivery or a contact was created. |
400 Bad Request | Client error | The request was malformed — invalid JSON, or a missing/unsupported Content-Type. |
401 Unauthorized | Auth error | The API key is missing, malformed, or revoked. See Authentication. |
404 Not Found | Client error | The resource ID does not exist or is not on your account (e.g. an unknown message ID). |
422 Unprocessable Entity | Validation error | The request was well-formed but failed validation — e.g. a non-E.164 number or an empty body. |
429 Too Many Requests | Rate limited | You exceeded your request rate. Back off and retry — see Rate limits. |
500–503 | Server error | Something went wrong on our side. These are transient; retry with backoff. |
A 2xx for POST /messages means the message was accepted for delivery, not that it has
reached the handset. Final delivery is reported asynchronously via the message status field
and delivery-receipt webhooks.
#Example responses
A validation failure on POST /messages returns 422 with a validation_error type:
{
"error": {
"type": "validation_error",
"message": "field 'to' is required"
}
}A missing or revoked key returns 401:
{
"error": {
"type": "unauthorized",
"message": "missing or invalid API key"
}
}Requesting an unknown ID from GET /messages/{id} returns 404:
{
"error": {
"type": "not_found",
"message": "no message with id 'msg_does_not_exist'"
}
}#Handling errors
Check the status code, then read error.type for cases you handle explicitly. The pattern below treats 4xx as caller mistakes to fix, 429 and 5xx as retryable, and surfaces the error object on everything else.
async function sendMessage(payload) {
const res = await fetch("https://api.netexem.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NETEXEM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
const { error } = await res.json();
if (res.status === 401) {
throw new Error(`Auth failed: ${error.message}`);
}
if (res.status === 422 || res.status === 400) {
// Caller mistake — fix the request, do not retry as-is.
throw new Error(`Invalid request (${error.type}): ${error.message}`);
}
if (res.status === 429 || res.status >= 500) {
// Transient — safe to retry with backoff. See /docs/rate-limits.
throw new Error(`Retryable error (${res.status}): ${error.message}`);
}
throw new Error(`Unexpected ${res.status}: ${error.message}`);
}#Which errors to retry
- Do not retry
400,401,404, or422— the request will fail again until you fix it. - Retry
429and5xxwith exponential backoff. For429, honor theRetry-Afterheader. See Rate limits for the recommended loop.
Log the full error object (type and message) alongside the request ID from your own
system. The type lets you aggregate failures; the message speeds up debugging.
#Next steps
- Rate limits — handling
429and backing off correctly - Authentication — avoiding
401errors - API reference — try requests live and see real responses