Error Handling

Gravity SMS uses a consistent error format across all endpoints. Understanding the error structure and HTTP status codes enables reliable error handling in your integration.

Error format

All error responses return a JSON object with a single error key containing a human-readable message:

Error response format
{
"error": "Description of what went wrong"
}

The HTTP status code indicates the error category. The error string provides specifics.

Status code categories

CodeMeaningRetryable
400Bad Request — Invalid input, missing required fields, or validation failure.No — fix the request.
401Unauthorized — Missing, malformed, or invalid API key.No — check your API key.
403Forbidden — Valid key but insufficient permissions or suspended tenant.No — check role and tenant status.
404Not Found — The requested resource does not exist or is not accessible.No — verify the resource ID.
413Payload Too Large — Request body exceeds 100 KB.No — reduce payload size.
429Too Many Requests — Rate limit exceeded.Yes — wait for Retry-After seconds.
500Internal Server Error — Unexpected server-side failure.Yes — retry with exponential backoff.

Retryable vs. permanent errors

Only 429 and 500 errors should be retried. All other status codes indicate a problem with the request itself that must be fixed before retrying.

For 429 responses, always read the Retry-After header to determine how long to wait. For 500 responses, use exponential backoff starting at 1 second.

Authentication errors

The gateway returns two distinct 401 errors depending on the failure:

401 No Authorization header or wrong format
json
{
"error": "Missing or invalid API key"
}
401 Key provided but not found in the system
json
{
"error": "Invalid API key"
}

Validation errors

Validation errors return 400 with a message describing exactly which field or rule failed:

400 Missing fields
json
{
"error": "to, from, and body are required"
}
400 Invalid format
json
{
"error": "to and from must be valid E.164 phone numbers"
}
400 Length violation
json
{
"error": "body must be 1-1600 characters"
}

Message-level errors

Outbound messages that fail during delivery have an error field on the message object (separate from the HTTP error format). Check this field when a message has status: "failed":

Failed message status
{
"messageId": "msg_a1b2c3d4e5f6",
"status": "failed",
"error": "RingCentral not connected for this tenant",
"to": "+15559876543",
"from": "+15551234567",
"body": "Hello",
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-01T10:00:01.000Z"
}

Common message-level errors include:

  • RingCentral not connected for this tenant — No active RC connection.
  • Phone number +15551234567 is not registered in this RingCentral account — The "from" number is not SMS-enabled or not in the RC account.

Error sanitization

In production, 500 errors always return the generic message "Internal server error". No stack traces, internal paths, or implementation details are exposed.

Request IDs

Every API response includes an X-Request-Id header. When reporting issues, include this ID so the problem can be traced through system logs.

Recommended error handling pattern

Error handling example
async function sendSms(to, from, body) {
const response = await fetch(API_BASE_URL + "/v1/sms/send", {
method: "POST",
headers: {
"Authorization": "Bearer " + API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ to, from, body }),
});
if (response.ok) {
return await response.json();
}
const error = await response.json();
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
// Wait and retry
await new Promise(r => setTimeout(r, (retryAfter || 60) * 1000));
return sendSms(to, from, body);
}
if (response.status >= 500) {
// Retry with backoff
throw new Error("Server error, retry later: " + error.error);
}
// 400, 401, 403, 404 — do not retry
throw new Error("Request failed: " + error.error);
}

Related docs