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

# Start a verification

> Generates a code and sends it to `to` on the channel set in your project's Verify settings. When Telegram is the channel and SMS fallback is on, OTPBay switches to SMS if Telegram can't deliver or doesn't deliver within the fallback wait. Store the returned `sid` and pass it to [Check a code](/api-reference/verify/check) when the user enters the code.



## OpenAPI

````yaml api-reference/openapi.yaml POST /verifications
openapi: 3.1.0
info:
  title: OTPBay API
  version: 1.0.0
  description: >-
    Send one-time passwords over Telegram and SMS, verify the codes your users
    enter, and send transactional SMS from your own sender ID.
  contact:
    name: OTPBay support
    email: hello@otpbay.com
servers:
  - url: https://api.otpbay.com/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Project
    description: The project that owns the API key.
  - name: Verify
    description: >-
      Send a code and check it. OTPBay generates the code and falls back from
      Telegram to SMS.
  - name: SMS
    description: Send any text message from one of your approved sender IDs.
  - name: Telegram
    description: Send a one-time code to a Telegram account through the Telegram Gateway.
paths:
  /verifications:
    post:
      tags:
        - Verify
      summary: Start a verification
      description: >-
        Generates a code and sends it to `to` on the channel set in your
        project's Verify settings. When Telegram is the channel and SMS fallback
        is on, OTPBay switches to SMS if Telegram can't deliver or doesn't
        deliver within the fallback wait. Store the returned `sid` and pass it
        to [Check a code](/api-reference/verify/check) when the user enters the
        code.
      operationId: startVerification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StartVerificationRequest'
            example:
              to: '+14155552671'
              metadata:
                user_id: usr_1042
      responses:
        '201':
          description: The verification was created and the first code was sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Verification'
              example:
                sid: VE6650c3a1b2c3d4e5f6a7b8c9
                status: pending
                to: '+14155552671'
                channel: telegram
                fallback_reason: null
                attempts:
                  - channel: telegram
                    sid: TG6650c3a1b2c3d4e5f6a7b8d0
                    status: sent
                    cost: '0.02'
                    date_created: '2026-09-26T10:15:02.114Z'
                checks_left: 5
                fee: '0.02'
                cost: '0.04'
                date_created: '2026-09-26T10:15:01.873Z'
                expires_at: '2026-09-26T10:25:01.873Z'
                approved_at: null
                metadata:
                  user_id: usr_1042
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientBalance'
        '403':
          description: >-
            `VERIFY_DISABLED` — Verify is turned off for this project, or
            `IP_NOT_ALLOWED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: VERIFY_DISABLED
                  message: Verify is turned off for this project.
        '422':
          description: >-
            `INVALID_REQUEST_BODY`, `SENDER_ID_REQUIRED` (SMS is used but no
            sender ID is set in Verify settings), `SENDER_ID_NOT_APPROVED`,
            `DESTINATION_NOT_PRICED` or `TELEGRAM_REJECTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: INVALID_REQUEST_BODY
                  message: 'to: Must be a valid international phone number (E.164)'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  schemas:
    StartVerificationRequest:
      type: object
      required:
        - to
      additionalProperties: false
      properties:
        to:
          $ref: '#/components/schemas/PhoneNumber'
        metadata:
          $ref: '#/components/schemas/Metadata'
        callback:
          $ref: '#/components/schemas/CallbackUrl'
    Verification:
      type: object
      required:
        - sid
        - status
        - to
        - channel
        - fallback_reason
        - attempts
        - checks_left
        - fee
        - cost
        - date_created
        - expires_at
        - approved_at
        - metadata
      properties:
        sid:
          type: string
          pattern: ^VE[a-f0-9]{24}$
          description: Unique verification ID.
          example: VE6650c3a1b2c3d4e5f6a7b8c9
        status:
          type: string
          enum:
            - pending
            - approved
            - expired
            - failed
          description: >-
            `pending` until the user enters the right code (`approved`), the
            code expires (`expired`), or the code can't be sent or too many
            wrong codes are entered (`failed`).
        to:
          $ref: '#/components/schemas/PhoneNumber'
        channel:
          type: string
          enum:
            - telegram
            - sms
          description: Channel the code was last sent on.
        fallback_reason:
          type:
            - string
            - 'null'
          enum:
            - telegram_failed
            - timeout
            - null
          description: Why the code was re-sent by SMS, or `null` if it wasn't.
        attempts:
          type: array
          description: Every send of the code, oldest first.
          items:
            $ref: '#/components/schemas/VerificationAttempt'
        checks_left:
          type: integer
          description: Wrong codes the user can still enter before the verification fails.
          example: 5
        fee:
          $ref: '#/components/schemas/Money'
        cost:
          allOf:
            - $ref: '#/components/schemas/Money'
          description: >-
            Verify fee plus what the channel sends currently cost. Refunded
            sends count as `0.00`.
          example: '0.04'
        date_created:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: When the code stops being accepted.
        approved_at:
          type:
            - string
            - 'null'
          format: date-time
          description: When the right code was entered.
        metadata:
          $ref: '#/components/schemas/Metadata'
        last_error:
          type: string
          description: Why the verification failed. Only present when `status` is `failed`.
          example: Too many wrong codes.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                Stable, machine-readable error code. Branch on this, not on
                `message`.
              example: INSUFFICIENT_BALANCE
            message:
              type: string
              description: Human-readable explanation. The wording can change.
              example: Insufficient balance for this verification.
    PhoneNumber:
      type: string
      description: >-
        Destination phone number in international format. OTPBay normalizes it
        to E.164, so `+1 (415) 555-2671` becomes `+14155552671`.
      example: '+14155552671'
    Metadata:
      type: object
      description: >-
        Your own key-value data, returned in responses and webhooks. At most 32
        keys of 1–64 characters. Values are strings (up to 512 characters),
        numbers or booleans.
      maxProperties: 32
      additionalProperties:
        oneOf:
          - type: string
            maxLength: 512
          - type: number
          - type: boolean
      example:
        user_id: usr_1042
    CallbackUrl:
      type: string
      format: uri
      maxLength: 2048
      description: >-
        Public `http` or `https` URL that receives [status webhooks](/webhooks).
        Localhost, private and link-local addresses are rejected. Defaults to
        the channel's default callback from project settings.
      example: https://example.com/webhooks/otpbay
    VerificationAttempt:
      type: object
      description: One send of the code.
      required:
        - channel
        - sid
        - status
        - cost
        - date_created
      properties:
        channel:
          type: string
          enum:
            - telegram
            - sms
          description: Channel this attempt was sent on.
        sid:
          type: string
          description: '`sid` of the underlying message: `TG…` for Telegram, `SM…` for SMS.'
          example: TG6650c3a1b2c3d4e5f6a7b8d0
        status:
          type: string
          description: >-
            Delivery status of the message. Telegram: `sending`, `sent`,
            `delivered`, `read`, `expired`, `revoked`, `failed`. SMS: `queued`,
            `processing`, `sent`, `failed`, `enqueue_failed`.
          example: sent
        cost:
          $ref: '#/components/schemas/Money'
        date_created:
          type: string
          format: date-time
    Money:
      type: string
      pattern: ^\d+\.\d{2}$
      description: Amount in US dollars as a decimal string.
      example: '0.02'
  responses:
    Unauthorized:
      description: '`MISSING_API_KEY` or `INVALID_API_KEY`.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INVALID_API_KEY
              message: Invalid API key.
    InsufficientBalance:
      description: '`INSUFFICIENT_BALANCE` — top up your project balance.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INSUFFICIENT_BALANCE
              message: Insufficient balance for this SMS destination.
    ServiceUnavailable:
      description: >-
        `QUEUE_UNAVAILABLE`, `PRICING_NOT_CONFIGURED`, `TELEGRAM_UNAVAILABLE` or
        `TELEGRAM_NOT_CONFIGURED` — a temporary problem on our side. Retry with
        backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: QUEUE_UNAVAILABLE
              message: >-
                SMS saved but queue is unavailable — try again later or check
                Redis.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Your project API key, sent as `Authorization: Bearer otp_live_...`.
        Create keys on the **API Keys** page of the dashboard.

````