API documentation

Public API /v1 — authenticate with an API key and automate inbox rentals from your own software

Your API key
You are not logged in — log in to get your own API key.
Send this key in the Authorization: Bearer <key> header or the ?api_key=<key> query parameter. Click the eye icon to reveal the full key; click Generate new key when you need to replace it (the old key stops working immediately).

Overview

Base URL
https://otpgmail.net
Authentication
Add your API key to every request, either in the Authorization: Bearer og_xxx... header (recommended) or in the ?api_key=og_xxx... query string.
Idempotency-Key (prevents duplicate orders)
Every POST /v1/orders request should include the Idempotency-Key: <new UUID per order> header. If the request times out or the connection drops, send it again with the same key — the server returns the original response instead of creating another order. Generate a new UUID for each new order and reuse a UUID only when retrying.
Inbox type: Gmail or iCloud
By default you get an @gmail.com inbox. To rent an @icloud.com inbox, add "domain": "icloud.com" to the POST /v1/orders body. Omitting domain means gmail.comexisting integrations keep working without changes. iCloud is available only for services whose icloud field is not null in GET /v1/services; the iCloud price may differ from the Gmail price (see icloud.price). Every order response includes a domain field that tells you which inbox type the order uses.
Complete sample code on GitHub
Ready-to-run clients for Python, Node.js, PHP, C# (.NET) and curl — rent an inbox, wait for the code, cancel automatically for a refund, and switch between iCloud and Gmail when stock runs out: github.com/letoan0902/otpgmail-email-otp-api (English) · thue-otp-gmail (Vietnamese). Python note: use requests rather than bare urllib (its default User-Agent gets a 403 from the CDN).

Endpoints

GET/v1/balance
Returns the account balance (in VND) for the API key used in the request.
Example
curl "https://otpgmail.net/v1/balance" \
  -H "Authorization: Bearer og_xxx..."
Sample response
{
  "success": true,
  "data": {
    "balance": 25000
  }
}
Possible errors: UNAUTHORIZED
GET/v1/services
Lists the enabled services — code, name, Gmail price (in VND) and current Gmail stock — plus an icloud block with the price and stock for @icloud.com inboxes. The data is cached and refreshed from the provider every 30 minutes.
icloud = null means the service does not support iCloud yet (the provider does not offer it, or it is temporarily disabled) — only Gmail can be rented. For services with an icloud block, send "domain": "icloud.com" when creating the order to get an @icloud.com inbox at icloud.price.
Example
curl "https://otpgmail.net/v1/services" \
  -H "Authorization: Bearer og_xxx..."
Sample response
{
  "success": true,
  "data": [
    {
      "code": "ig",
      "name": "Instagram",
      "price": 1060,
      "stock": 342,
      "icloud": {
        "price": 950,
        "stock": 120
      }
    },
    {
      "code": "tg",
      "name": "Telegram",
      "price": 530,
      "stock": 1200,
      "icloud": {
        "price": 480,
        "stock": 860
      }
    },
    {
      "code": "fb",
      "name": "Facebook",
      "price": 1590,
      "stock": 88,
      "icloud": null
    }
  ]
}
Possible errors: UNAUTHORIZED
POST/v1/orders
Creates an order for an inbox that receives verification codes — Gmail by default, or iCloud when you send domain: "icloud.com". Your balance is charged as soon as the provider allocates the inbox. Returns a list of orders (one, or several when quantity > 1).
The Idempotency-Key header (a UUID) is required. Generate a new UUID for each new rental; when retrying after a timeout, reuse the SAME key to avoid creating a duplicate order. This call waits for the provider to allocate an inbox, so it usually takes about 0.3 seconds but can take up to 10 seconds at peak times (rare) — set your client timeout to about 15 seconds rather than giving up after 5 seconds and retrying early.
Parameters
NameTypeRequiredDescription
servicestringYesService code (from GET /v1/services, e.g. "ig", "tg", "fb")
quantitynumberNoNumber of inboxes to rent (default 1, up to 50 per request)
domainstringNoInbox type: "gmail.com" (default) or "icloud.com". Omitted = gmail.com — existing integrations need no changes. iCloud can be rented only for services whose icloud field is not null in GET /v1/services; otherwise the call fails with DOMAIN_UNAVAILABLE. Any other value ⇒ VALIDATION_ERROR.
Example
# Generate a random Idempotency-Key for every NEW order
# REUSE the same key when retrying after a timeout (prevents duplicate orders)
# domain: "gmail.com" (default, optional) | "icloud.com" (rent an @icloud.com inbox)
curl -X POST "https://otpgmail.net/v1/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service":"ig","quantity":1,"domain":"gmail.com"}'
Sample response
{
  "success": true,
  "data": [
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200
    }
  ]
}
Possible errors: UNAUTHORIZED, VALIDATION_ERROR, INSUFFICIENT_BALANCE, OUT_OF_STOCK, DOMAIN_UNAVAILABLE, NO_MAILS_AVAILABLE, QUANTITY_EXCEEDED, WAITING_LIMIT_REACHED, DUPLICATE_REQUEST, MAINTENANCE
GET/v1/orders/{id}
Returns an order by ID, including the codes received so far (otp[]). Poll this endpoint every 3–5 seconds to pick up the code while the order is in the waiting_code status.
The otp[] array is ordered by arrival: oldest first, NEWEST LAST — read the latest code with otp[otp.length - 1]. After the first code arrives, the system AUTOMATICALLY keeps listening for the next one — no extra call is needed. Just keep polling this endpoint; each new code is appended to the end of otp[] (free of charge, for 24 hours after the rental). If the service allows only one code per inbox, otp[] simply stops at that code — responses stay normal, with no error.
Parameters
NameTypeRequiredDescription
idstringYesOrder ID (the orderId returned when the order was created)
Example
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Sample response
{
  "success": true,
  "data": {
    "orderId": "abc123xyz",
    "service": "ig",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "completed",
    "price": 1060,
    "otp": [
      {
        "code": "123456",
        "receivedAt": 1753268500
      },
      {
        "code": "789012",
        "receivedAt": 1753269000
      }
    ],
    "codeDeadlineAt": 1753270000,
    "rentExpiresAt": 1753356400,
    "createdAt": 1753268200
  }
}
Possible errors: UNAUTHORIZED, NOT_FOUND
POST/v1/orders/{id}/cancel
Cancels the order and refunds 100% of the price. Allowed only while the order is in the waiting_code status AND no code has been received.
An order that has received a code (even a single one) cannot be cancelled or refunded — this applies to both the website and the API.
Parameters
NameTypeRequiredDescription
idstringYesID of the order to cancel
Example
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json"
Sample response
{
  "success": true,
  "data": {
    "orderId": "abc123xyz",
    "service": "ig",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "cancelled",
    "price": 1060,
    "otp": [],
    "codeDeadlineAt": 1753270000,
    "rentExpiresAt": 1753356400,
    "createdAt": 1753268200
  }
}
Possible errors: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE

Order lifecycle

StatusDescription
Waiting for codeNewly created order, waiting for the first code. Poll GET /v1/orders/{id} every 3–5 seconds.
CompletedThe first code has arrived — the status changes immediately. More codes are still collected for free until the 24 hours are up; keep polling and each new code appears at the end of otp[].
CancelledRefunded 100%. Happens when you call /cancel (before any code arrives), or when the order is cancelled automatically after 30 minutes without a code.
Important notes:
  • Poll GET /v1/orders/{id} every 3–5 seconds while the order is waiting_code so you pick up the code promptly.
  • As soon as the first code arrives, the status changes to completed immediately.
  • After 30 minutes without a code, the system cancels the order and refunds 100% (status = cancelled).
  • The otp[] array is ordered oldest first, newest last. Read the latest code with otp[otp.length - 1] (Python: otp[-1]).
  • A completed order keeps receiving codes for free for 24 hours after it was created, and it does so automatically — no extra call is needed. Right after each code, the system is already listening for the next one. Just keep polling GET /v1/orders/{id}; new codes show up at the end of otp[].
  • If the service allows only one code per inbox, otp[] stops at that code. Responses stay normal, with no error — you decide when to stop polling.
  • POST /v1/orders waits for the provider to allocate an inbox, so it usually takes ~0.3 seconds but can take up to 10 seconds at peak times (rare). Set a client timeout of ≈ 15 seconds rather than giving up after 5 seconds and retrying early — and when you do retry, reuse the same Idempotency-Key.
  • An order that has received a code cannot be cancelled or refunded — the cancel call returns ORDER_NOT_CANCELLABLE.
  • To rent an @icloud.com inbox, send "domain": "icloud.com" when creating the order (omit it for Gmail; existing bots need no changes). If the service does not support iCloud, or iCloud is temporarily disabled, you get DOMAIN_UNAVAILABLE — switch to Gmail and the rental goes through right away. The lifecycle, the cancellation and refund rules, and the way you read codes are exactly the same as for Gmail.

Rate limits

Limits apply per API key:
  • 10 requests/second — short bursts over a few seconds
  • 300 requests/minute — total traffic per minute
Going over a limit returns HTTP 429 with a Retry-After: <seconds> header and the body {"success":false,"error":{"code":"RATE_LIMITED",...}}. Wait the number of seconds given in the header before retrying.

Error codes

Every error has the same shape: {"success":false,"error":{"code":"...","message":"..."}}
Error codeHTTPMeaning
UNAUTHORIZED401The API key is invalid or missing, or the account is suspended.
VALIDATION_ERROR400The request parameters are invalid (wrong type, missing required field, etc.).
INSUFFICIENT_BALANCE402The account balance is too low to pay for the order.
OUT_OF_STOCK409The service has no inboxes available (for the selected inbox type) at the time of the request.
DOMAIN_UNAVAILABLE409The service does not offer the selected inbox type — iCloud is not supported yet, or it is temporarily disabled. Send domain "gmail.com" (or omit the field) to rent Gmail.
NO_MAILS_AVAILABLE503The provider has no inboxes left for this service (temporarily out).
QUANTITY_EXCEEDED400quantity exceeds the maximum (50 per request).
WAITING_LIMIT_REACHED429Too many orders are waiting for a code at the same time (up to 100 waiting_code orders per account).
ORDER_NOT_CANCELLABLE409The order cannot be cancelled — it may already have a code, or it is no longer in the waiting_code status.
RATE_LIMITED429More than 10 req/second or 300 req/minute. The Retry-After header tells you how many seconds to wait.
DUPLICATE_REQUEST409The Idempotency-Key was already used with a different payload. Generate a new key to create another order.
PROVIDER_ERROR502Error from the inbox provider (temporary — retry after a few seconds).
MAINTENANCE503The system is under maintenance — creating and cancelling orders is paused.