> ## 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 OTP

> GET /v1/otp/{id} — status and metadata. The code is never returned.



## OpenAPI

````yaml openapi.yaml GET /v1/otp/{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/otp/{id}:
    get:
      tags:
        - OTP
      summary: Get OTP status
      description: |
        Retrieves the current status and details of an OTP. The code itself is
        never returned — only its length and hash-verified state.

        `timeline` lists each hop the OTP took, oldest first: `queued`, `sent`,
        `delivered`, then `verified`, `expired` or `failed`. A `failed` hop
        carries a `reason` token and whether the credit was `refunded`. See
        [Delivery timeline](/sms/delivery-timeline).
      operationId: getOTP
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: OTP ID returned from send
      responses:
        '200':
          description: OTP details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OTPDetails'
        '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:
    OTPDetails:
      type: object
      required:
        - id
        - workspace_id
        - phone_number
        - country_code
        - credit_cost
        - status
        - code_length
        - ttl_seconds
        - attempt_count
        - max_attempts
        - expired_at
        - created_at
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        phone_number:
          type: string
        country_code:
          type: string
          description: ISO 3166-1 alpha-2 country code
        credit_cost:
          type: integer
        status:
          type: string
          enum:
            - pending
            - sent
            - verified
            - expired
            - failed
        code_length:
          type: integer
        ttl_seconds:
          type: integer
        attempt_count:
          type: integer
          description: Verification attempts made so far
        max_attempts:
          type: integer
          description: Attempt budget before the OTP is burned
          example: 5
        sent_at:
          type: string
          format: date-time
        delivered_at:
          type: string
          format: date-time
        verified_at:
          type: string
          format: date-time
        failed_at:
          type: string
          format: date-time
          description: When the OTP was marked failed. Absent otherwise.
        failure_reason:
          type: string
          description: Reason token behind a failed status. See TimelineItem.
        expired_at:
          type: string
          format: date-time
          description: When the code stops being verifiable
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        timeline:
          type: array
          description: Hops the OTP took, oldest first.
          items:
            $ref: '#/components/schemas/TimelineItem'
    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.