---
title: "Check message_id Status"
method: POST
path: "/check_otp_status"
---

# Check message_id Status

`POST /check_otp_status`

## Request

- Base URL: `https://api.myotp.app`
- URL: `https://api.myotp.app/check_otp_status`
- Auth: none declared. Required headers: `X-API-Key`

Delivery report for a sent OTP, plus whether the code is still active.

Delivery receipts arrive a few seconds after the send, so the first call after /generate_otp normally reports a pending state rather than a carrier status. Poll every few seconds, and treat any DLR value beginning "Pending:" as "ask again shortly". If you would rather not poll at all, register a delivery webhook in the dashboard and we will POST each status change to you instead.

Data is retained for 24 hours after an OTP expires.

## Headers

- `X-API-Key` string, required

## Request body

- object
  - `message_id` string, required: The message ID returned by /generate_otp.

## Response `200`

The delivery report, or an explanation of why one is not available. Note that an unknown message_id is also a 200, carrying "message" instead of "DLR".

- object
  - `DLR` string: The delivery state. Values fall into four groups. Carrier status, passed through verbatim, for SMS: ATES (accepted by the carrier), DELIVRD (delivered), UNDELIV, EXPIRED, REJECTD. For WhatsApp and Telegram this is our own transaction status instead: sent, delivered, read, pending, or a failed.<reason> code. Pending, meaning try again in a few seconds: "Pending: not yet accepted by the carrier" (the send has not been acknowledged yet) and "Pending: the carrier has not reported yet" (accepted, no receipt yet). Failures are reported as failed.<reason>. On WhatsApp the reason is one of two values: failed.Undeliverable, meaning the number cannot receive WhatsApp, which is permanent and is your signal to fall back to SMS, and failed.Provider, meaning the send failed on our side or the carrier's, which is worth retrying. SMS reasons name the stage that failed, for example failed.NoBalance or failed.SMPP. A bare failed predates the WhatsApp reasons and should be read as failed.Provider. Unavailable, which are statements about your plan or the route rather than this message: "Not available with your plan" (your plan has no DLR_ACCESS entitlement), "Not available for this market" (the route reports nothing back) and "Not available from the carrier" (the carrier declined to answer).
  - `is_active` boolean: Whether the OTP can still be verified, i.e. it has not expired.
  - `expires_at` string, date-time: When the OTP expires. Absent when the message_id is unknown.
  - `message` string: Present instead of DLR when the message_id is not found. Data is available for 24 hours after an OTP expires.
  - `DLR:` string: Deprecated alias of DLR, kept so existing integrations keep working. Read DLR instead; this key will be removed.
  - `Message:` string: Deprecated alias of message. Read message instead.

## Other responses

- `400`: message_id is required
- `401`: Unauthorized
- `403`: The calling IP is not on your account's allowlist
- `429`: Too many requests from this IP address. The edge allows 100 requests a minute per client IP with a burst of 100, counted across every endpoint together, so sends and status checks share one budget. Accounts that ask for more are raised to 500 a minute; X-RateLimit-Limit on every response tells you which ceiling applies to you. Wait the number of seconds in Retry-After and repeat the request. Write to support@myotp.app with the addresses you call from and the peak you are designing for if you need a higher limit.

---

[API](https://skmtc.dev/myotp/apis/myotpapp-api.md) · [All operations](https://skmtc.dev/myotp/apis/myotpapp-api/llms.txt) · [OpenAPI document](https://skmtc.dev/myotp/apis/myotpapp-api/revisions/95ed911fdece?raw)
