API Reference · Endpoints

OTP Hub API

Send and verify WhatsApp verification codes: POST /api/v1/otp/send, POST /api/v1/otp/verify, GET /api/v1/otp/requests/:id.

The OTP Hub endpoints belong to the OTP Hub app. Install the app first, then create a key on its API keys tab. That key carries only the otp permission. A general workspace key with full access (*) also works. These endpoints don't need the API access plan feature: OTP Hub is billed per message.

EndpointWhat it does
POST /api/v1/otp/sendSend a code to a phone number
POST /api/v1/otp/verifyCheck a code the user entered
GET /api/v1/otp/requests/:idRead one request: status, delivery, price
Authentication
Send your key in the X-API-Key header on every request. Every response is JSON with success. Errors also include a machine-readable code and a readable error.

Send a code

POST/api/v1/otp/sendGenerate a code and send it on WhatsApp
ParameterTypeRequiredDescription
phonestringrequiredRecipient in international format with country code, e.g. 201012345678 or +966 50 123 4567. Spaces, dashes, a leading + or 00 are removed.
languagestringoptionalMessage language: ar or en. Defaults to Arabic.
testbooleanoptionalTest mode: nothing is sent or charged, and the code is returned in data.code.
metadataobjectoptionalYour own reference data (for example your user id). It is stored with the request and returned as is.
Idempotency-Key (header)stringoptionalUp to 80 characters. A repeat within 24 hours returns the first request instead of sending again. See Safe retries.
curl -X POST "https://inboxi.app/api/v1/otp/send" \
  -H "X-API-Key: inx_live_your_otp_hub_key" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "201012345678",
    "language": "ar",
    "metadata": {
      "userId": "u_1842"
    }
  }'

Response: 201 Created

json
{
  "success": true,
  "data": {
    "id": "otp_5f0c2a9e41b7d3c8a6e2f019",
    "to": "+2010•••••678",
    "status": "pending",
    "delivery": "sent",
    "attempts": 0,
    "attemptsLeft": 5,
    "expiresAt": "2026-10-16T12:10:00.000Z",
    "verifiedAt": null,
    "test": false,
    "billing": "wallet",
    "from": "otp_hub",
    "price": { "amount": 0.0104, "currency": "USD" },
    "metadata": { "userId": "u_1842" },
    "createdAt": "2026-10-16T12:00:00.000Z"
  }
}

Save data.id and pass it to /verify. In test mode the response also has "test": true,"delivery": "test", "billing": "test" and "code": "482913".

Verify a code

POST/api/v1/otp/verifyCheck the code the user typed
ParameterTypeRequiredDescription
idstringoptionalThe id returned by /send. Recommended.
phonestringoptionalInstead of id: checks the latest code sent to this number.
codestringrequiredThe code the user entered. Spaces are ignored.
curl -X POST "https://inboxi.app/api/v1/otp/verify" \
  -H "X-API-Key: inx_live_your_otp_hub_key" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "otp_5f0c2a9e41b7d3c8a6e2f019",
    "code": "482913"
  }'

An answer is always 200 with data.valid, including for a wrong code. Allow the action only whenvalid is true. A correct code works once.

reasonMeaningWhat to do
nullThe code is correct. The request is now verified.Continue
invalid_codeWrong code. attemptsLeft says how many tries remain.Ask the user to try again
max_attemptsToo many wrong tries. This code no longer works.Send a new code
expiredMore than 10 minutes passed.Send a new code
canceledA newer code was sent to the same number.Use the latest code
already_verifiedThis code was already used.Treat as a new attempt
failedThe message was not delivered.Send a new code or offer another channel

Get a request

GET/api/v1/otp/requests/:idStatus, delivery and price of one code
curl "https://inboxi.app/api/v1/otp/requests/otp_5f0c2a9e41b7d3c8a6e2f019" \
  -H "X-API-Key: inx_live_your_otp_hub_key"

Returns the same object as /send, without the code.

The request object

FieldTypeDescription
idstringRequest id, starts with otp_
tostringThe recipient, masked
statusstringpending · verified · expired · failed · canceled
deliverystringsent · delivered · read · failed · test. Updates as WhatsApp reports progress
attempts / attemptsLeftnumberVerify tries used, and remaining
expiresAt / verifiedAtISO dateWhen the code expires, and when it was verified
billingstringwallet (paid) · free_quota (monthly allowance) · admin_free (free from Inboxi) · test · none (not sent) · own_number (sent from your number, billed by Meta)
fromstringotp_hub (the OTP Hub number) or own_number (your number). See Which number sends the codes
priceobject{ amount, currency }. What this message costs you. 0 when free or refunded
testbooleanSent in test mode
metadataobject | nullYour reference data from /send

Errors

json
{
  "success": false,
  "code": "INSUFFICIENT_BALANCE",
  "error": "Your wallet balance is too low to send this code. Top up from Settings › Billing.",
  "price": 0.0104,
  "balance": 0.003
}
StatuscodeWhen
400PHONE_REQUIREDNo phone in the body.
400INVALID_PHONENot an international number (8-15 digits with country code).
401-Missing, invalid, inactive or expired API key.
402INSUFFICIENT_BALANCEThe wallet cannot cover this message. The body includes price and balance.
403-The key does not have the otp permission (Missing permission: otp).
403APP_NOT_INSTALLEDOTP Hub is not installed on the workspace.
403APP_BLOCKEDOTP Hub was disabled for this workspace by INBOXI. Contact support.
403COUNTRY_NOT_ALLOWEDThe number is in a country you did not allow (OTP Hub › Settings).
409OWN_TEMPLATE_NOT_APPROVEDYou send from your own number and its verification template is not approved by Meta yet. Wait for approval, or switch to the OTP Hub number.
409NO_WHATSAPPYou send from your own number and it is no longer connected. Reconnect it, or switch to the OTP Hub number.
422NOT_ON_WHATSAPPThe number cannot receive WhatsApp messages. Offer another channel.
429RESEND_TOO_SOONAnother code went to this number less than 30 seconds ago. Retry-After says when.
429TOO_MANY_CODESThis number already received 5 codes in the last hour.
429DAILY_LIMIT_REACHEDYour workspace reached its daily code limit (OTP Hub › Settings). Retry-After says when the day resets (UTC).
429-The API key went over its per-minute request limit.
502SEND_FAILEDWhatsApp refused the message. You are not charged.
503APP_DISABLEDOTP Hub is temporarily unavailable.
503SENDER_UNAVAILABLEThe sending number is temporarily unavailable. Retry later.
Retries
It is safe to retry 502 and 503 after a short wait. For 429, wait the number of seconds in theRetry-After header. Don't retry 4xx errors without changing the request.