{
  "openapi": "3.1.0",
  "info": {
    "title": "SMSRay API",
    "version": "1.0.0",
    "summary": "SMS and OTP API.",
    "description": "The SMSRay v1 REST API, generated from https://smsray.com/docs (updated 2026-10-10). Numbers: E.164 recommended; national format accepted for your workspace's country.",
    "contact": {
      "name": "SMSRay developer support",
      "email": "support@lacspace.com",
      "url": "https://smsray.com/docs/support"
    },
    "termsOfService": "https://smsray.com/legal/terms"
  },
  "servers": [
    {
      "url": "https://api.smsray.com/api/sms/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "SMS"
    },
    {
      "name": "OTP"
    }
  ],
  "paths": {
    "/sms/send": {
      "post": {
        "operationId": "send",
        "summary": "Send an SMS",
        "description": "Queue one SMS to one mobile number, as free text or from an approved template. The cost is reserved from your workspace balance at accept time and the message goes straight into delivery.\n\n- The reply always says queued. Follow the message with GET /sms/messages/:id or, better, a message.status webhook. No webhook is sent for the initial queued.\n- Every custom message must use a template approved by SMSRay. Approval protects deliverability and keeps spam off the networks; we aim to review new templates within one business day. OTPs sent with POST /sms/otp/send use SMSRay's built-in template, so OTP works on day one.\n- Messages are delivered over the recipient's mobile network, whichever one they are on, with automatic retries. If delivery finally fails, the message ends as failed and the reserved cost is refunded. undelivered is not refunded.\n- Numbers are stored in E.164, so to in lookups and webhooks is always +<country code><number>.\n- Send an Idempotency-Key on every send so network retries never double-send or double-bill.\n- On a key in test mode the message is accepted and recorded with status test, but never sent and never charged.",
        "tags": [
          "SMS"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–200 printable ASCII characters. Safe retries for 24 h. See Idempotency.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string",
                    "description": "Recipient. E.164 is recommended (+9779801234567); the national format is accepted for your workspace's country (for a Nepal workspace 98XXXXXXXX, 97… or 96…, with or without 977). Must match ^\\+?\\d{7,15}$. See Numbers & countries."
                  },
                  "text": {
                    "type": "string",
                    "description": "Free text. Send exactly one of text or templateId. At least 1 character, at most 6 segments (GSM-7: 918 characters, UCS-2: 402 code units). When your workspace requires templates (the default) the text must match one of your approved templates of the same type."
                  },
                  "templateId": {
                    "type": "string",
                    "description": "An approved template of your workspace. SMSRay renders it with variables. Send exactly one of text or templateId."
                  },
                  "variables": {
                    "type": "object",
                    "description": "Values for the template's {{variables}}, as strings or numbers. Every variable is required; each value is 1–100 characters with no line break."
                  },
                  "type": {
                    "type": "string",
                    "description": "transactional (default), otp or promotional. Must be one of your workspace's allowedTypes; promotional is available on Enterprise only. With templateId, the template's type is used when omitted."
                  },
                  "senderId": {
                    "type": "string",
                    "description": "Stored as from on the message. Not validated in v1 — sender-ID approval is not available yet."
                  }
                },
                "required": [
                  "to"
                ]
              },
              "example": {
                "to": "+9779801234567",
                "text": "Your order #1042 has shipped."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messageId": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Use it with GET /sms/messages/:id and to match webhook events."
                    },
                    "status": {
                      "type": "string",
                      "description": "Always queued in this reply."
                    },
                    "segments": {
                      "type": "integer",
                      "description": "How many SMS parts the text needs."
                    },
                    "cost": {
                      "type": "number",
                      "description": "Your rate for type × segments, reserved from the balance."
                    },
                    "encoding": {
                      "type": "string",
                      "description": "GSM7 or UCS2."
                    },
                    "from": {
                      "type": "string",
                      "description": "The sender recorded for the message: your senderId if given, otherwise the default sender."
                    }
                  },
                  "required": [
                    "messageId",
                    "status",
                    "segments",
                    "cost",
                    "encoding",
                    "from"
                  ]
                },
                "examples": {
                  "example_1": {
                    "value": {
                      "success": true,
                      "messageId": "2b1f6c1e-8d4f-4c55-9a51-0d2b0b7f3a11",
                      "status": "queued",
                      "segments": 1,
                      "cost": 0.5,
                      "encoding": "GSM7",
                      "from": "SMSRay"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: Validation failed (see details), both or neither of text and templateId were sent, or the text needs more than 6 segments. `invalid_number`: The number can't receive SMS — for example a landline, or a number that fails its country's mobile rules. `invalid_variables`: With templateId: a variable is missing, unknown, empty, longer than 100 characters or contains a line break.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "invalid_request",
                            "invalid_number",
                            "invalid_variables"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "Invalid request",
                  "code": "invalid_request",
                  "details": [
                    {
                      "path": [
                        "to"
                      ],
                      "message": "Required",
                      "code": "invalid_type"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The x-api-key header is missing (\"Missing x-api-key\") or the key is unknown (\"Invalid API key\").",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_balance`: Your workspace balance is lower than the cost of this message.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled`: The API client that owns this key has been disabled in the dashboard. `forbidden`: This message type is not allowed on your plan or workspace. `template_required`: The text doesn't match an approved template of the same type, or templateId is not an approved template of this workspace. `workspace_pending`: Your workspace has not been activated yet, so it cannot send. `workspace_suspended`: Your workspace is suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "client_disabled",
                            "forbidden",
                            "template_required",
                            "workspace_pending",
                            "workspace_suspended"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "This message doesn't match an approved template. Submit the template for approval in your SMSRay dashboard, or send with an approved templateId.",
                  "code": "template_required"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_conflict`: The same Idempotency-Key was used with a different body, or the first request is still running (Retry-After: 1).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "idempotency_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`destination_not_supported`: A valid number in a country your workspace can't send to yet. details is { country, allowed }; show \"We don't deliver to <country> yet\".",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "destination_not_supported"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "We don't deliver to India yet. This workspace can send to: Nepal.",
                  "code": "destination_not_supported",
                  "details": {
                    "country": "IN",
                    "allowed": [
                      "NP"
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: You exceeded your per-client request rate (default 20 requests per second). Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait, when known.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "rate_limited"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "`server_error`: Something went wrong on our side. The body never contains a stack trace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "server_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "`unavailable`: Delivery is temporarily unavailable. Nothing was charged; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "externalDocs": {
          "url": "https://smsray.com/docs/send-sms"
        }
      }
    },
    "/sms/otp/send": {
      "post": {
        "operationId": "otpSend",
        "summary": "Send an OTP",
        "description": "Generate a 6-digit code, send it as a branded one-segment SMS, and store only its SHA-256 hash. The code is valid for 5 minutes.\n\n- The SMS reads: <App>: 482913 is your verification code. Valid for 5 minutes. Do not share it. <App> is your API client's brand name (GSM-7, up to 30 characters, set in the dashboard).\n- The message uses SMSRay's built-in OTP template, so no template approval is needed. It is billed at your OTP rate and always fits in one GSM-7 segment.\n- The code is never stored or shown in plain text. In the dashboard and in GET /sms/messages/:id the text appears as (OTP hidden).\n- The per-number limit counts across every SMSRay customer, so one phone number cannot be flooded with codes from many apps.\n- OTPs are sent immediately and are never deferred.",
        "tags": [
          "OTP"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–200 printable ASCII characters. Safe retries for 24 h. See Idempotency.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string",
                    "description": "Recipient. E.164 is recommended (+9779801234567); the national format is accepted for your workspace's country (for a Nepal workspace 98XXXXXXXX, 97… or 96…, with or without 977). Must match ^\\+?\\d{7,15}$. See Numbers & countries."
                  },
                  "purpose": {
                    "type": "string",
                    "description": "Up to 50 characters. Defaults to default. Codes are scoped to (your client, to, purpose), so login and reset-password never collide."
                  },
                  "ip": {
                    "type": "string",
                    "description": "Your end user's IP address, up to 64 characters. Enables the per-IP limit (10 OTPs per 10 minutes)."
                  },
                  "senderId": {
                    "type": "string",
                    "description": "Optional; same meaning as on /sms/send."
                  }
                },
                "required": [
                  "to"
                ]
              },
              "example": {
                "to": "+9779801234567",
                "purpose": "login",
                "ip": "203.0.113.7"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "otpId": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Id of the code record. You never need it to verify."
                    },
                    "messageId": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The SMS that carries the code; track it like any message."
                    },
                    "expiresInSeconds": {
                      "type": "integer",
                      "description": "Always 300 (5 minutes)."
                    }
                  },
                  "required": [
                    "otpId",
                    "messageId",
                    "expiresInSeconds"
                  ]
                },
                "examples": {
                  "example_1": {
                    "value": {
                      "success": true,
                      "otpId": "9d5c0b7e-3a1f-4e62-8b1d-6a4f2c9e0d17",
                      "messageId": "4f8a2c61-0b9e-4d3a-a7c5-1e6f9b2d8c40",
                      "expiresInSeconds": 300
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: Validation failed (see details). `invalid_number`: The number can't receive SMS — for example a landline, or a number that fails its country's mobile rules.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "invalid_request",
                            "invalid_number"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The x-api-key header is missing (\"Missing x-api-key\") or the key is unknown (\"Invalid API key\").",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_balance`: Not enough balance for one OTP-rate segment.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled`: The API client that owns this key has been disabled in the dashboard. `forbidden`: OTP messages are not allowed on your plan or workspace. `workspace_pending`: Your workspace has not been activated yet, so it cannot send. `workspace_suspended`: Your workspace is suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "client_disabled",
                            "forbidden",
                            "workspace_pending",
                            "workspace_suspended"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_conflict`: The same Idempotency-Key was used with a different body, or the first request is still running (Retry-After: 1).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "idempotency_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`destination_not_supported`: A valid number in a country your workspace can't send to yet. details is { country, allowed }; show \"We don't deliver to <country> yet\".",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "destination_not_supported"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Resend cooldown (60 s per number and purpose), 3 OTPs per number per 10 minutes, 10 per end-user IP per 10 minutes, 60 requests per minute per calling IP, or your per-client rate. Retry-After is set whenever it is known.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait, when known.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "rate_limited"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "Please wait before requesting another code",
                  "code": "rate_limited"
                }
              }
            }
          },
          "500": {
            "description": "`server_error`: Something went wrong on our side. The body never contains a stack trace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "server_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "`unavailable`: Delivery is temporarily unavailable. Nothing was charged; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "externalDocs": {
          "url": "https://smsray.com/docs/otp"
        }
      }
    },
    "/sms/otp/verify": {
      "post": {
        "operationId": "otpVerify",
        "summary": "Verify an OTP",
        "description": "Check the code your user typed against the latest active code for the same number and purpose. Codes are single use.\n\n- A wrong code is not an HTTP error. Always read verified; never treat a 200 as success on its own.\n- Each wrong code counts as an attempt. After 5 wrong attempts the code is locked and every further check returns too_many_attempts — send a new code.\n- Codes expire 5 minutes after they are sent and are deleted automatically afterwards.",
        "tags": [
          "OTP"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1–200 printable ASCII characters. Safe retries for 24 h. See Idempotency.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string",
                    "description": "The same number you sent the code to."
                  },
                  "code": {
                    "type": "string",
                    "description": "The 6 digits your user entered."
                  },
                  "purpose": {
                    "type": "string",
                    "description": "Must match the purpose used on send. Defaults to default."
                  }
                },
                "required": [
                  "to",
                  "code"
                ]
              },
              "example": {
                "to": "+9779801234567",
                "purpose": "login",
                "code": "482913"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "description": "true once, for the correct code. The code is then consumed."
                    },
                    "reason": {
                      "type": "string",
                      "description": "Present when verified is false: invalid_code, too_many_attempts (5 wrong tries) or no_active_otp (none sent, expired or already used)."
                    }
                  },
                  "required": [
                    "verified"
                  ]
                },
                "examples": {
                  "correct_code": {
                    "value": {
                      "success": true,
                      "verified": true
                    }
                  },
                  "wrong_code": {
                    "value": {
                      "success": true,
                      "verified": false,
                      "reason": "invalid_code"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: A required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "invalid_request"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The x-api-key header is missing (\"Missing x-api-key\") or the key is unknown (\"Invalid API key\").",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled`: The API client that owns this key has been disabled in the dashboard. `workspace_pending`: Your workspace has not been activated yet, so it cannot send. `workspace_suspended`: Your workspace is suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "client_disabled",
                            "workspace_pending",
                            "workspace_suspended"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_conflict`: The same Idempotency-Key was used with a different body, or the first request is still running (Retry-After: 1).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "idempotency_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: You exceeded your per-client request rate (default 20 requests per second). Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait, when known.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "rate_limited"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "`server_error`: Something went wrong on our side. The body never contains a stack trace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "server_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "externalDocs": {
          "url": "https://smsray.com/docs/otp"
        }
      }
    },
    "/sms/messages/{id}": {
      "get": {
        "operationId": "getMessage",
        "summary": "Retrieve a message",
        "description": "Fetch the current state of a message sent with this API key: status, encoding, segments, cost and timestamps.\n\n- Polling works, but webhooks are cheaper and faster. If you poll, back off (for example 2 s, 5 s, 15 s, 60 s) and stop at a final status: delivered, undelivered, failed or rejected.\n- Internal delivery details are never returned.",
        "tags": [
          "SMS"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The messageId returned by /sms/send or /sms/otp/send.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "description": "queued, submitting, submitted, sent, delivered, undelivered, failed or rejected (test for keys in test mode)."
                    },
                    "to": {
                      "type": "string",
                      "description": "The recipient in E.164."
                    },
                    "text": {
                      "type": "string",
                      "description": "The message body, or (OTP hidden) for messages created by /sms/otp/send."
                    },
                    "from": {
                      "type": "string",
                      "description": "The sender recorded for the message."
                    },
                    "attempts": {
                      "type": "integer",
                      "description": "How many delivery attempts were made."
                    },
                    "error": {
                      "type": "string",
                      "description": "A neutral failure code when one is known (network_unavailable, delivery_timeout, carrier_rejected, invalid_number, delivery_failed), otherwise empty."
                    },
                    "dlr": {
                      "type": "object",
                      "description": "Delivery report { status, code, raw, at }, present when the network returned one."
                    },
                    "submittedAt": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "ISO 8601 UTC timestamps for each transition."
                    },
                    "sentAt": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "ISO 8601 UTC timestamps for each transition."
                    },
                    "deliveredAt": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "ISO 8601 UTC timestamps for each transition."
                    },
                    "failedAt": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "ISO 8601 UTC timestamps for each transition."
                    }
                  },
                  "required": [
                    "status",
                    "to",
                    "text",
                    "from",
                    "attempts",
                    "error",
                    "submittedAt",
                    "sentAt",
                    "deliveredAt",
                    "failedAt"
                  ]
                },
                "examples": {
                  "example_1": {
                    "value": {
                      "_id": "2b1f6c1e-8d4f-4c55-9a51-0d2b0b7f3a11",
                      "to": "+9779801234567",
                      "from": "SMSRay",
                      "text": "Your order #1042 has shipped.",
                      "type": "transactional",
                      "encoding": "GSM7",
                      "segments": 1,
                      "cost": 0.5,
                      "status": "delivered",
                      "error": "",
                      "attempts": 1,
                      "submittedAt": "2026-10-10T04:15:01.000Z",
                      "sentAt": "2026-10-10T04:15:03.000Z",
                      "deliveredAt": "2026-10-10T04:15:07.000Z",
                      "failedAt": null,
                      "createdAt": "2026-10-10T04:15:00.000Z",
                      "updatedAt": "2026-10-10T04:15:07.000Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The x-api-key header is missing (\"Missing x-api-key\") or the key is unknown (\"Invalid API key\").",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled`: The API client that owns this key has been disabled in the dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "client_disabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No message with this id was sent by this API key. Messages from other keys — even in the same workspace — are not visible.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "not_found"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "Message not found",
                  "code": "not_found"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: You exceeded your per-client request rate (default 20 requests per second). Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait, when known.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "rate_limited"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "`server_error`: Something went wrong on our side. The body never contains a stack trace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "server_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "externalDocs": {
          "url": "https://smsray.com/docs/messages"
        }
      }
    },
    "/sms/balance": {
      "get": {
        "operationId": "getBalance",
        "summary": "Retrieve balance",
        "description": "Read your workspace balance, per-type rates, allowed message types and this API client's counters.\n\n- GET requests keep working while your workspace is pending, so you can integrate and check your setup before activation.\n- Your rate is your plan's per-SMS price. See pricing. Remaining balance expires at the end of each monthly period; top-ups are charged at your plan rate.\n- When the balance is lower than a message's cost, the send returns 402 insufficient_balance.\n- A message's cost is refunded only when delivery finally fails (failed). undelivered is not refunded.",
        "tags": [
          "SMS"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "balance": {
                      "type": "number",
                      "description": "Credit remaining for the current monthly period, in your workspace's currency (NPR for Nepal workspaces), shared by every API client in the workspace."
                    },
                    "rate": {
                      "type": "object",
                      "description": "Price per SMS segment for transactional, otp and promotional. Cost = rate × segments. promotional is enabled only on Enterprise; check allowedTypes."
                    },
                    "allowedTypes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Message types this workspace may send. Others return 403 forbidden."
                    },
                    "counters": {
                      "type": "object",
                      "description": "This API client's own sent, delivered and failed totals."
                    },
                    "workspaceStatus": {
                      "type": "string",
                      "description": "active, pending or suspended."
                    }
                  },
                  "required": [
                    "balance",
                    "rate",
                    "allowedTypes",
                    "counters",
                    "workspaceStatus"
                  ]
                },
                "examples": {
                  "example_1": {
                    "value": {
                      "balance": 5000,
                      "rate": {
                        "transactional": 0.5,
                        "otp": 0.5,
                        "promotional": 0.5
                      },
                      "allowedTypes": [
                        "transactional",
                        "otp"
                      ],
                      "counters": {
                        "sent": 120,
                        "delivered": 110,
                        "failed": 4
                      },
                      "workspaceStatus": "active"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The x-api-key header is missing (\"Missing x-api-key\") or the key is unknown (\"Invalid API key\").",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled`: The API client that owns this key has been disabled in the dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "client_disabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: You exceeded your per-client request rate (default 20 requests per second). Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait, when known.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "rate_limited"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "`server_error`: Something went wrong on our side. The body never contains a stack trace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "server_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "externalDocs": {
          "url": "https://smsray.com/docs/balance"
        }
      }
    },
    "/sms/coverage": {
      "get": {
        "operationId": "getCoverage",
        "summary": "Retrieve coverage",
        "description": "Where this API key can send today: your workspace's country, the destinations allowed for it, and the countries SMSRay delivers to now or soon.\n\n- Use allowed to validate phone numbers in your sign-up form before you send, and to pre-select the country picker with country.\n- A public, unauthenticated variant without the workspace fields is available at GET https://api.smsray.com/api/sms/coverage. It returns { live, comingSoon } and is cached for 5 minutes.\n- Today Nepal is live; India and Bangladesh are coming soon. The response is always the current list, so don't hard-code it.",
        "tags": [
          "SMS"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "country": {
                      "type": "string",
                      "description": "Your workspace's country (ISO 3166-1 alpha-2). National-format numbers are read as numbers of this country."
                    },
                    "allowed": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Countries this key can send to right now. A number elsewhere returns 422 destination_not_supported."
                    },
                    "live": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "description": "{ code, name } for every country SMSRay delivers to today."
                    },
                    "comingSoon": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "description": "{ code, name } for countries we are preparing. Not sendable yet."
                    }
                  },
                  "required": [
                    "country",
                    "allowed",
                    "live",
                    "comingSoon"
                  ]
                },
                "examples": {
                  "example_1": {
                    "value": {
                      "country": "NP",
                      "allowed": [
                        "NP"
                      ],
                      "live": [
                        {
                          "code": "NP",
                          "name": "Nepal"
                        }
                      ],
                      "comingSoon": [
                        {
                          "code": "BD",
                          "name": "Bangladesh"
                        },
                        {
                          "code": "IN",
                          "name": "India"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: The x-api-key header is missing (\"Missing x-api-key\") or the key is unknown (\"Invalid API key\").",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled`: The API client that owns this key has been disabled in the dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "client_disabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: You exceeded your per-client request rate (default 20 requests per second). Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait, when known.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "rate_limited"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "`server_error`: Something went wrong on our side. The body never contains a stack trace.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "server_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "externalDocs": {
          "url": "https://smsray.com/docs/coverage"
        }
      }
    }
  },
  "webhooks": {
    "messageStatus": {
      "post": {
        "summary": "message.status",
        "description": "Sent when a message reaches sent, delivered, undelivered or failed. Verify x-lacspace-signature (HMAC-SHA256 of `<t>.<raw body>`).",
        "parameters": [
          {
            "name": "x-lacspace-signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "x-lacspace-event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "message.status",
                "message.inbound"
              ]
            }
          },
          {
            "name": "x-lacspace-delivery",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "id",
                  "status",
                  "to",
                  "at"
                ],
                "properties": {
                  "type": {
                    "const": "message.status"
                  },
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "status": {
                    "enum": [
                      "queued",
                      "sent",
                      "delivered",
                      "undelivered",
                      "failed"
                    ]
                  },
                  "to": {
                    "type": "string"
                  },
                  "at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "messageType": {
                    "type": "string"
                  },
                  "segments": {
                    "type": "integer"
                  },
                  "error": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 s acknowledges the event."
          }
        }
      }
    },
    "messageInbound": {
      "post": {
        "summary": "message.inbound",
        "description": "Sent when a recipient replies to one of your messages.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "id",
                  "from",
                  "text",
                  "at"
                ],
                "properties": {
                  "type": {
                    "const": "message.inbound"
                  },
                  "id": {
                    "type": "string"
                  },
                  "from": {
                    "type": "string"
                  },
                  "text": {
                    "type": "string"
                  },
                  "at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "inReplyTo": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 s acknowledges the event."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "`ls_live_` followed by 48 hex characters."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable; may change."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable code."
          },
          "details": {
            "description": "Validation issues (array) or extra context such as { country, allowed } (object).",
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              {
                "type": "object"
              }
            ]
          }
        }
      }
    }
  },
  "externalDocs": {
    "url": "https://smsray.com/docs"
  }
}