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

# OTP.ID Quickstart: Send and Verify Your First OTP Code

> Send and verify your first OTP in under 5 minutes using the OTP.ID REST API. Covers API keys, POST /v3/request, POST /v3/verify, and status polling.

This guide walks you through the complete OTP flow — from obtaining your API key to sending a code and verifying it — using nothing but `curl`. By the end, you'll have made real API calls and understand the shape of every response you'll encounter in production.

<Note>
  The OTP code itself is never returned in API responses — it's delivered directly to the user through the chosen channel.
</Note>

<Steps>
  <Step title="Get your API key">
    Every request to the OTP.ID API must include a Bearer API key. To obtain yours:

    1. Log in to your dashboard at [https://otp.id](https://otp.id).
    2. Navigate to **Settings → API Keys**.
    3. Copy your key — you'll pass it in the `Authorization` header of every request.

    Your API key looks like this: `otpid_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`

    Keep it secret. Never commit it to source control or expose it in client-side code.
  </Step>

  <Step title="Send an OTP via WhatsApp">
    Call `POST /v3/request` to have OTP.ID generate a code and deliver it to your user over WhatsApp. Pass the destination number in E.164 format (digits only, no `+` prefix) and a `brand` name that will appear in the message.

    ```bash theme={null}
    curl -X POST https://api.otp.id/v3/request \
      -H "Authorization: Bearer <api_key>" \
      -H "Content-Type: application/json" \
      -d '{
        "channel": "whatsapp",
        "number": "6281234567890",
        "brand": "MyApp"
      }'
    ```

    A successful response looks like this:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "otp_id": "OTP20260807ABCD000001",
        "status": "sent",
        "channel": "whatsapp",
        "number": "6281234567890",
        "expires_at": "2026-08-07T10:05:00Z",
        "price": 500,
        "last_balance": 49500
      },
      "error": null
    }
    ```

    Save the `otp_id` — you'll need it in the next two steps. The `expires_at` field tells you when the code stops being valid. `price` is the amount deducted from your balance (in the smallest currency unit), and `last_balance` is your remaining prepaid balance after this send.

    <Tip>
      Pass an `external_id` field in your request body to make sends safely retriable. If you call `POST /v3/request` again with the same `external_id`, OTP.ID returns the original OTP record instead of creating a duplicate. See the [idempotency guide](/guides/idempotency) for details.
    </Tip>
  </Step>

  <Step title="Verify the OTP">
    Once your user submits the code they received, call `POST /v3/verify` with the `otp_id` from the previous step and the code the user entered.

    ```bash theme={null}
    curl -X POST https://api.otp.id/v3/verify \
      -H "Authorization: Bearer <api_key>" \
      -H "Content-Type: application/json" \
      -d '{
        "otp_id": "OTP20260807ABCD000001",
        "otp": "482913"
      }'
    ```

    **Successful verification** — the code matches:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "otp_id": "OTP20260807ABCD000001",
        "verified": true,
        "verified_at": "2026-08-07T10:03:21Z"
      },
      "error": null
    }
    ```

    **Code mismatch** — the user entered the wrong code:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "otp_id": "OTP20260807ABCD000001",
        "verified": false,
        "reason": "mismatch"
      },
      "error": null
    }
    ```

    <Note>
      A mismatch returns HTTP `200` with `verified: false` — it is a valid, expected outcome, not an API error. Only check `data.verified` to decide whether to grant access.
    </Note>

    Other possible values for `reason` include `"expired"` (the OTP window has passed) and `"already_verified"` (the code was already used).
  </Step>

  <Step title="Check transaction status">
    For polling-based flows — or when you want to inspect a past OTP record — call `GET /v3/otp/{otp_id}` with the OTP's ID.

    ```bash theme={null}
    curl -X GET https://api.otp.id/v3/otp/OTP20260807ABCD000001 \
      -H "Authorization: Bearer <api_key>"
    ```

    The response includes the full OTP record with its current `status`:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "otp_id": "OTP20260807ABCD000001",
        "channel": "whatsapp",
        "number": "6281234567890",
        "status": "verified",
        "created_at": "2026-08-07T10:00:00Z",
        "expires_at": "2026-08-07T10:05:00Z",
        "verified_at": "2026-08-07T10:03:21Z"
      },
      "error": null
    }
    ```

    Common `status` values are `sent`, `delivered`, `verified`, `expired`, and `failed`. For real-time notifications without polling, configure an `otp.verified` webhook in your dashboard instead.
  </Step>
</Steps>

## Next Steps

You've completed the full OTP flow. Here's where to go deeper:

* **[Authentication](/authentication)** — understand API key scoping and how to handle auth errors.
* **[Channels](/concepts/channels)** — learn when to use SMS, Email, Missed Call, or WhatsApp Inbound instead of WhatsApp outbound.
* **[Webhooks](/guides/webhooks)** — set up the `otp.verified` event to receive async verification results.
* **[API Reference](/api-reference)** — see the full schema for every request and response field.
