Errors

Ugrat uses conventional HTTP response codes to indicate the success or failure of a request. Error responses share the same envelope:

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "rate limit exceeded",
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

request_id identifies the request in our logs — include it when contacting support. Validation errors additionally carry a fields array with a stable machine-readable code per field (and optional meta, e.g. the maximum length):

{
  "success": false,
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "request validation failed",
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "fields": [
      { "field": "recipient", "code": "invalid_phone" },
      { "field": "body", "code": "too_long", "meta": { "max": 800 } }
    ]
  }
}
CodeMeaning
400 BAD_REQUESTMalformed JSON, unknown field, or an invalid path parameter
400 VALIDATION_FAILEDOne or more fields failed validation — see fields
401 UNAUTHORIZEDAPI key missing, invalid, or deactivated
403 ACCOUNT_PENDINGThe account has not been approved yet
403 ACCOUNT_BLOCKEDThe account is blocked
403 MESSAGING_NOT_ENABLEDFree-text messaging is not enabled for this account (OTP still works)
403 FORBIDDENThe key is valid but lacks permission for this operation
404 NOT_FOUNDResource does not exist or is not visible to the caller
409 CONFLICTThe request collides with existing data — e.g. reusing a reference_id you already used
422 UNPROCESSABLE_ENTITYThe request is well-formed but cannot be completed — e.g. an OTP code that is wrong, expired, or out of attempts
429 RATE_LIMITEDRate limit exceeded — retry after the Retry-After seconds
500 INTERNAL_ERRORUnexpected error on our side — retry, and report the request_id if it persists
503 SERVICE_UNAVAILABLENo delivery route is available right now, or the service is briefly unavailable — retry after the Retry-After seconds when present