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": "Description of what went wrong"}
The HTTP status code indicates the error category. The error string provides specifics.
Status code categories
| Code | Meaning | Retryable |
|---|---|---|
400 | Bad Request — Invalid input, missing required fields, or validation failure. | No — fix the request. |
401 | Unauthorized — Missing, malformed, or invalid API key. | No — check your API key. |
403 | Forbidden — Valid key but insufficient permissions or suspended tenant. | No — check role and tenant status. |
404 | Not Found — The requested resource does not exist or is not accessible. | No — verify the resource ID. |
413 | Payload Too Large — Request body exceeds 100 KB. | No — reduce payload size. |
429 | Too Many Requests — Rate limit exceeded. | Yes — wait for Retry-After seconds. |
500 | Internal 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.
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:
{"error": "Missing or invalid API key"}
{"error": "Invalid API key"}
Validation errors
Validation errors return 400 with a message describing exactly which field or rule failed:
{"error": "to, from, and body are required"}
{"error": "to and from must be valid E.164 phone numbers"}
{"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":
{"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
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 retryawait new Promise(r => setTimeout(r, (retryAfter || 60) * 1000));return sendSms(to, from, body);}if (response.status >= 500) {// Retry with backoffthrow new Error("Server error, retry later: " + error.error);}// 400, 401, 403, 404 — do not retrythrow new Error("Request failed: " + error.error);}
Related docs
- Error Reference — Complete table of all error messages
- Rate Limits — 429 handling and Retry-After
- Authentication — 401 error details