> ## Documentation Index
> Fetch the complete documentation index at: https://docs.robase.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For chatbots and coding agents, start at /guides/ai-agents.
> Hosted MCP: POST https://api.robase.dev/mcp (Streamable HTTP). Auth: Authorization: Bearer robe_… or robe_agent_….
> Phase 1 tools: get_pricing, send_otp, verify_otp, get_otp, send_sms, get_sms. No payment or top-up tools.
> Call get_pricing; treat listed:true as marketed markets (NG, GH, KE, ZA, GB, BJ, CI, US). Do not invent countries or delivery percentages.
> Prefer OTP-shaped tests such as "Your Robase test OTP is 123456". Do not send Hello or Good morning as tests. Match errors on error.type.

# Get an SMS

> GET /v1/sms/{id} — delivery status for a transactional message.



## OpenAPI

````yaml openapi.yaml GET /v1/sms/{id}
openapi: 3.0.3
info:
  title: Robase SMS OTP API
  description: >
    Multi-provider SMS OTP delivery and transactional SMS API with automatic

    failover and per-country routing. Send and verify one-time passwords across

    Africa and globally.


    ## Authentication


    Every endpoint under `/v1` requires an API key issued in the dashboard,

    passed as a bearer token:


    ```

    Authorization: Bearer robe_your_api_key_here

    ```


    ## Idempotency


    `POST` endpoints accept an `Idempotency-Key` header. Replaying the same key

    within 24 hours returns the original response instead of sending a second

    message — so a request that times out mid-flight can be retried safely

    without double-charging credits. The official SDKs generate a key for every

    `POST` automatically.


    ## Rate limits


    Requests are limited per client IP (100/minute) and per destination phone

    number (3 OTP sends / 10 minutes, 10 OTP verifications / 10 minutes,

    10 transactional SMS / minute). Exceeding a limit returns `429` with a

    `Retry-After` header.


    ## Errors


    Failures return a consistent envelope:


    ```json

    {"error": {"type": "insufficient_credits", "message": "insufficient credit
    balance"}}

    ```


    Match on `error.type` — it is stable — rather than on `error.message`, which

    is prose and may change.


    ## Languages


    `error.message` is written in your workspace's default language. Send an

    `Accept-Language` header (`en` or `fr`) to override that for one request —

    it changes only the prose, never `error.type`.


    `Accept-Language` does not affect the text of an SMS. The OTP message body

    follows the workspace's language setting, or the `language` field on

    `POST /v1/otp/send`. A header describing which language *you* read should

    not decide what your end user receives.


    ## Webhooks


    Dashboard-configured HTTPS callbacks. The signed body is compact JSON

    (no pretty-print, no HTML escaping of `<>&`). HMAC-SHA256 is over those

    exact bytes. Envelope and `data` objects are `WebhookEnvelope`,

    `OTPWebhookData`, `SMSWebhookData`, and `CreditWebhookData`.
  version: 1.0.0
  contact:
    name: Robase Support
    url: https://robase.dev
servers:
  - url: https://api.robase.dev
    description: Production
  - url: http://localhost:8080
    description: Local development
security: []
tags:
  - name: OTP
    description: Generate, deliver and verify one-time passwords.
  - name: SMS
    description: Send arbitrary transactional messages.
  - name: System
    description: Unauthenticated operational endpoints.
paths:
  /v1/sms/{id}:
    get:
      tags:
        - SMS
      summary: Get SMS status
      description: |
        Retrieves the current status and details of a transactional SMS. Only
        messages belonging to the authenticated workspace are visible.

        `timeline` lists each hop the message took, oldest first: `queued`,
        `sent`, then `delivered`, `failed` or `blocked`. A terminal `failed` or
        `blocked` hop carries a `reason` token and whether the credit was
        `refunded`. `delivered` appears only when the carrier sends a receipt.
        See [Delivery timeline](/sms/delivery-timeline).
      operationId: getSMS
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: SMS message ID returned from send
      responses:
        '200':
          description: SMS details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SMSDetails'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - BearerAuth: []
components:
  schemas:
    SMSDetails:
      type: object
      required:
        - id
        - phone_number
        - country_code
        - message
        - credit_cost
        - status
        - created_at
      properties:
        id:
          type: string
          format: uuid
        phone_number:
          type: string
        country_code:
          type: string
        message:
          type: string
        credit_cost:
          type: integer
        status:
          type: string
          enum:
            - pending
            - sent
            - delivered
            - failed
            - blocked
          description: |
            `blocked` means anti-spam screening stopped the message before any
            provider saw it. It is terminal, and the credit was refunded in
            full; `metadata.antispam` carries the classifier's verdict.
        metadata:
          type: object
          additionalProperties: true
          nullable: true
          description: |
            Whatever was sent with the message. Screening adds an `antispam`
            key when the classifier flagged or blocked it, carrying `is_spam`,
            `score`, `category` and `reason`.
        created_at:
          type: string
          format: date-time
        timeline:
          type: array
          description: Hops the message took, oldest first.
          items:
            $ref: '#/components/schemas/TimelineItem'
          example:
            - at: '2026-09-13T09:00:00Z'
              status: queued
            - at: '2026-09-13T09:00:02Z'
              status: sent
            - at: '2026-09-13T09:00:41Z'
              status: failed
              reason: delivery_failed
              refunded: true
    TimelineItem:
      type: object
      required:
        - at
        - status
      properties:
        at:
          type: string
          format: date-time
          description: When the hop happened (RFC 3339).
        status:
          type: string
          description: |
            `queued` (charged and waiting for the worker), then the message's
            own status values: `sent`, `delivered`, `failed`, `blocked`, and for
            OTPs `verified` and `expired`. A hop is listed only when its time
            is known; `status` on the parent object is always authoritative.
        reason:
          type: string
          description: |
            Stable machine token. Present on a terminal hop when known, and on
            `sent` when the anti-spam screen flagged but still sent the message.
            Tokens: `send_failed`, `delivery_failed`, `antispam_blocked`,
            `antispam_flagged`, `max_attempts_exceeded`. Omitted when unknown.
        refunded:
          type: boolean
          description: |
            On a terminal `failed` or `blocked` hop only. Reflects the credit
            ledger: `true` means the charge was returned.
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - message
          properties:
            type:
              type: string
              description: |
                Stable machine-readable identifier. Match on this rather than on
                `message`.
              enum:
                - validation_error
                - invalid_phone
                - country_not_supported
                - unauthorized
                - insufficient_credits
                - kyc_required
                - account_temporarily_restricted
                - otp_not_found
                - sms_not_found
                - insufficient_scope
                - country_not_enabled
                - prefix_blocked
                - content_blocked
                - screening_unavailable
                - idempotency_key_reused
                - request_in_progress
                - otp_expired
                - otp_already_verified
                - max_attempts_exceeded
                - rate_limited
                - internal_error
                - whatsapp_not_available
            message:
              type: string
              description: Human-readable description. Not stable — do not parse.
            required_scope:
              type: string
              description: With `insufficient_scope`, the scope the API key is missing.
      example:
        error:
          type: insufficient_credits
          message: insufficient credit balance
  responses:
    Unauthorized:
      description: The API key is missing, malformed, or unknown.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: unauthorized
              message: invalid API key
    InsufficientScope:
      description: |
        The API key does not have the scope this endpoint needs. Agent keys
        carry scopes (`otp:send`, `otp:verify`, `sms:send`, `read`,
        `approvals:request`); keys made before scopes existed keep full access.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: insufficient_scope
              message: this API key does not have the read scope
              required_scope: read
    NotFound:
      description: No record with that ID exists in this workspace.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: otp_not_found
              message: OTP not found
    RateLimited:
      description: |
        A rate limit was exceeded: per API key (600 sends or 1200 reads a
        minute by default), per workspace (3000 a minute by default), per
        destination prefix (300 OTPs an hour by default), per destination
        phone, or per client IP. Limits are counted per workspace, so another
        workspace's traffic never uses up yours. Retry after `Retry-After`.
      headers:
        Retry-After:
          description: >-
            Whole seconds until the counter that refused the request resets (at
            least 1).
          schema:
            type: integer
            minimum: 1
          example: 42
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: rate_limited
              message: too many send attempts, try again later
    InternalError:
      description: >-
        An unexpected server-side failure. Safe to retry with the same
        `Idempotency-Key`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: internal_error
              message: an unexpected error occurred
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Your Robase API key. Starts with robe_. Include as Authorization: Bearer
        robe_...

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.