Gmail OTP API in Python: Rent and Wait

Reading a verification code inside a Python script comes down to three HTTP calls: rent an inbox, poll until a code shows up, cancel if you give up. This walkthrough builds exactly that, using nothing but requests and the standard library, against the real OTPGmail API.

What the script needs to do

Every account has its own API key on the Account page; send it as Authorization: Bearer <api_key>. The whole flow is:

  1. POST /v1/orders to rent an address for a specific service.
  2. Poll GET /v1/orders/{orderId} every few seconds until otp has an entry.
  3. POST /v1/orders/{orderId}/cancel if you no longer want to wait — refunded in full, as long as no code has arrived.

If nothing happens on its own, the order cancels itself and refunds automatically after 25 minutes, so step 3 is a courtesy, not a requirement.

Renting an inbox

import uuid
import requests

HOST = "otpgmail.net"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}


def rent(service, domain="gmail.com"):
    resp = requests.post(
        f"https://{HOST}/v1/orders",
        json={"service": service, "domain": domain},
        headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
        timeout=15,
    )
    body = resp.json()
    if not body.get("success"):
        raise RuntimeError(body["error"]["code"])
    return body["data"][0]  # data is a list — one entry per rented inbox

domain is optional and defaults to gmail.com; pass "icloud.com" where a service offers it — iCloud inboxes are usually cheaper than Gmail, depending on the service. The Idempotency-Key header matters more than it looks: generate a new UUID for every new order, and if the request times out, retry with that same key so the API hands back the order it already created instead of billing you for a second one.

Use requests, not the standard-library urllib — its default Python-urllib user agent gets rejected with an HTTP 403 by the CDN in front of the API.

Waiting for the code

import time

def wait_for_code(order_id, max_wait=600, interval=4):
    deadline = time.time() + max_wait
    while time.time() < deadline:
        resp = requests.get(f"https://{HOST}/v1/orders/{order_id}", headers=HEADERS, timeout=15)
        order = resp.json()["data"]
        if order["otp"]:
            return order["otp"][-1]["code"]  # newest code is last
        if order["status"] == "cancelled":
            return None
        time.sleep(interval)
    # Gave up before the API's own 25-minute timeout — cancel now for an instant refund
    requests.post(f"https://{HOST}/v1/orders/{order_id}/cancel", headers=HEADERS, timeout=15)
    return None

A 3–5 second interval is the sweet spot: fast enough that you are not the bottleneck, slow enough to stay well inside the rate limit. otp is ordered oldest first, so otp[-1] is always the most recent code — useful later if the same order picks up a second code (a login, a device confirmation) while it can still receive codes (the service's code limit and the source's window apply).

Putting it together

try:
    order = rent("git", domain="icloud.com")
except RuntimeError as e:
    if str(e) in ("DOMAIN_UNAVAILABLE", "NO_MAILS_AVAILABLE"):
        order = rent("git", domain="gmail.com")  # fall back to Gmail
    else:
        raise

print("Address:", order["email"])
code = wait_for_code(order["orderId"])
print("Code:", code or "none received")

git is GitHub's service code from GET /v1/services; swap it for any other code (dr for ChatGPT, ds for Discord) to target a different sign-up. Fill in the target service's own sign-up form with order["email"], submit it, and only then start polling — the code will not exist before the form is submitted.

Handling errors

rent() raises whenever success is false; catch specific codes rather than a bare exception so a script can react sensibly:

For the full error table, the field-by-field response shape, and how the equivalent script looks in JavaScript, see the API docs and the Node.js example. A ready-to-run version of this script, plus Node.js, PHP, C# and curl clients, is in the public otpgmail-email-otp-api repository. For patterns specific to test suites — retries, teardown, running many orders in parallel — see renting Gmail for testing sign-up flows, and the Gmail OTP API overview for the rest of the endpoints.

Frequently asked questions

Why does the request need an Idempotency-Key header?

So a retried request after a dropped connection does not rent a second inbox. Reuse the same UUID only when retrying the exact same order; generate a new one for every new order.

How long should max_wait be?

It depends on the service: a GitHub code usually arrives after about 1 minute and an AWS code after about 1 minute, while ChatGPT usually takes about 1 minute and sometimes longer. Setting max_wait above 25 minutes has no effect — the API cancels and refunds an order with no code after that point regardless.

Can I rent several inboxes in one call?

Yes, with a quantity field in the request body (default 1, up to 200). data then holds one entry per inbox instead of just data[0].

What does order["otp"][-1]["code"] return before any code has arrived?

An IndexError, since otp is an empty list until the first code lands — that is exactly why wait_for_code checks if order["otp"]: first.

Create an account and grab your key on the rent page, then keep the API docs open while you adapt this script to your own service.

Related guides

Get your verification code now

Sign up free. You are only charged when an inbox is issued. No code within 25 minutes — 100% refunded automatically.

Can't find the service you need? Use Add service on the rental page and we will add it if our infrastructure supports it.

Need a Gmail account you can sign in to, not just a code? See buy short-lived Gmail accounts · Refund and warranty