Apply custom discountAnyone who rents OTP Gmail > 2000 Gmails per day for any service (except Facebook, Instagram, and Apple) and wants a private discount, feel free to message me! Discounts can be up to 50% off the original price for those registering accounts in large enough volumes.
New update 29/09I have just added 4 new warehouses for all services + short-live accounts shop (24h) with email + pass for you to log in, feel free to check them out when you have time

Документація API

Публічний API /v1: автентифікація за ключем API та автоматизація оренди скриньок зі свого програмного забезпечення

Ваш ключ API
Ви не ввійшли в акаунт: увійдіть, щоб отримати власний ключ API.
Надсилайте цей ключ у заголовку Authorization: Bearer <key> або в параметрі запиту ?api_key=<key>. Натисніть значок ока, щоб побачити ключ повністю; натисніть Створити новий ключ, коли його потрібно замінити (старий ключ одразу перестане працювати).

Огляд

Базовий URL
https://otpgmail.net
Автентифікація
Додавайте свій ключ API до кожного запиту: або в заголовок Authorization: Bearer og_xxx... (рекомендовано), або в рядок запиту ?api_key=og_xxx....
Idempotency-Key (запобігає дублюванню замовлень)
Кожен запит POST /v1/orders має містити заголовок Idempotency-Key: <новий UUID на замовлення>. Якщо час очікування запиту минув або з'єднання обірвалося, надішліть його ще раз із тим самим ключем: сервер поверне початкову відповідь замість створення ще одного замовлення. Створюйте новий UUID для кожного нового замовлення й використовуйте UUID повторно лише під час повторної спроби.
Багато замовлень одночасно: статус за один виклик
Орендуєте десятки чи сотні скриньок? Запитуйте їхній статус разом: POST /v1/orders/status повертає до 100 замовлень за виклик (рахується як один запит у ліміті), а GET /v1/orders показує список ваших замовлень — після перезапуску ?status=waiting_code поверне всі замовлення, що ще чекають на код. Кожна помилка також повідомляє, чи варто повторювати (error.retryable, error.retryAfterSec). Докладніше: розділ Статус багатьох замовлень.
Тип скриньки: Gmail або iCloud
За замовчуванням ви отримуєте скриньку @gmail.com. Щоб орендувати скриньку @icloud.com, додайте "domain": "icloud.com" до тіла POST /v1/orders. Якщо domain не вказано, це означає gmail.com: наявні інтеграції продовжують працювати без змін. iCloud доступний лише для сервісів, у яких поле icloud у GET /v1/services не дорівнює null; ціна iCloud може відрізнятися від ціни Gmail (див. icloud.price). Кожна відповідь із замовленням містить поле domain, яке показує, який тип скриньки використовує замовлення. Деякі сервіси також пропонують скриньки @gmx.com (наразі лише Pokemon, код pkm, склад 2): надішліть "domain": "gmx.com"; сервіс пропонує його, якщо в його offers[] у GET /v1/services є запис із domain = "gmx.com"; для будь-якого іншого сервісу ⇒ DOMAIN_UNAVAILABLE. gmx.com недоступний для партій.
Склади: один сервіс, кілька джерел скриньок
Сервіс може продаватися з кількох складів, кожен зі своєю ціною, наявністю та правилами кодів (масив offers[] у GET /v1/services). Надішліть "warehouse": 2 під час створення замовлення, щоб обрати склад; "warehouse": 99 ⇒ система обирає найдешевший склад із наявністю й переходить до наступного, якщо він вичерпається (ідеально для сервісів, що часто вичерпуються, як-от Facebook та Instagram); 0 ⇒ найдешевший склад із наявністю. Наявним клієнтам нічого змінювати не потрібно: якщо warehouse не вказано ⇒ склад 1, як раніше, а price, stock і icloud описують склад 1 (сервіси без складу 1 ⇒ найдешевший склад із наявністю). Докладніше: див. Склади.
Масова оренда (партії)
Партії купуються на сайті (сторінка «Масова оренда»); через API можна переглядати список партій і читати коди. Gmail (склад 1): кожна скринька має proxyUrl, посилання на код, якому не потрібен ключ API. iCloud (склад 3): приватні скриньки; читайте коди через GET /v1/batches/{id}/mails/{mailId}/codes або відкрийте readUrl на сайті. Докладніше: розділ «Масова оренда».
Купівля короткочасних акаунтів Gmail (магазин пошти)
Окрім оренди скриньок, ви можете купувати акаунти Gmail повністю з 2 складів: склад 1 — email, пароль, cookies + посилання на скриньку (також у JSON); склад 2 — email, пароль + швидке посилання для входу; склад 99 спершу бере найдешевший (сторінка «Купити короткочасний Gmail» на сайті). API робить усе, що й ця сторінка: показує склади, ціни й залишки, здійснює купівлю (потрібен Idempotency-Key), дозволяє стежити за доставкою, завантажувати акаунти й читати їхні листи. Докладніше: розділ «Магазин пошти».
Дозвольте клієнтам самим читати коди (сторінка /otp)
Перепродаєте орендовані скриньки (на сайті або через API)? Увімкніть код доступу на сторінці «Акаунт», а потім надішліть клієнту посилання https://otpgmail.net/uk/otp#k7m2q9:[email protected] (k7m2q9 = ваш код доступу): він побачить останні коди цього email без входу в акаунт і ніколи не побачить цін чи вашого балансу; сторінка виконує лише безкоштовні дії (з вас нічого не списується).
JSON-посилання для програм і ботів (ваших або клієнта): візьміть посилання у вигляді шляху https://otpgmail.net/uk/otp/k7m2q9:[email protected] (воно відкриває сторінку /otp так само, як посилання з #) і додайте ?json ⇒ сервер поверне JSON рівно з тими даними, які показує сторінка: cards[] — по картці на кожен сервіс, орендований на цьому email (email, serviceName, state, items[] = до 5 останніх кодів / посилань, нові першими), — і serverTime. state: waiting чекає на перший код · listening чекає на новий код · received коди отримано · needs_owner продавець має продовжити · expired термін минув · banned скриньку заблоковано. Кілька email — через кому (не більше 50). Без API-ключа й cookie, CORS відкритий; лише читання («Завантажити новий OTP» / «Орендувати цю адресу знову» лишаються на сторінці /otp). Ті самі обмеження проти підбору, що й на сторінці: невірний код / для email немає замовлень для показу ⇒ 404 NOT_FOUND; повторні промахи або понад 30 запитів на хвилину для одного email ⇒ 429 RATE_LIMITED з retryAfter (секунди). Якщо додати ?json до посилання /otp#…, JSON не повернеться: браузер ніколи не надсилає на сервер частину після #.
curl "https://otpgmail.net/uk/otp/k7m2q9:[email protected]?json"
{
  "success": true,
  "data": {
    "cards": [
      {
        "ref": "Q3vX9kL2mP0aB7cD",
        "serviceCode": "fb",
        "serviceName": "Facebook",
        "logo": "/logos/fb.png?v=20261007",
        "domain": "gmail.com",
        "email": "[email protected]",
        "state": "received",
        "until": null,
        "items": [
          {
            "kind": "code",
            "value": "529170",
            "label": null,
            "at": 1760000405
          },
          {
            "kind": "code",
            "value": "381904",
            "label": null,
            "at": 1759999030
          }
        ],
        "total": 2,
        "canLoad": true
      }
    ],
    "serverTime": 1760000530
  }
}
Повні приклади коду на GitHub
Готові до запуску клієнти для Python, Node.js, PHP, C# (.NET) і curl: орендуйте скриньку, чекайте на код, автоматично скасовуйте для повернення коштів і перемикайтеся між iCloud та Gmail, коли наявність вичерпується: github.com/letoan0902/otpgmail-email-otp-api (англійською) · thue-otp-gmail (в'єтнамською). Примітка щодо Python: використовуйте requests замість «голого» urllib (його User-Agent за замовчуванням отримує 403 від CDN).
Вебхуки
Замість постійного опитування кожного замовлення зареєструйте один URL і отримуйте підписаний POST, щойно замовлення отримає новий код або його буде скасовано чи відшкодовано — див. Вебхуки.

Ендпоінти

GET/v1/balance
Повертає баланс акаунта (у VND) для ключа API, використаного в запиті.
Приклад
curl "https://otpgmail.net/v1/balance" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "balance": 25000
  }
}
Можливі помилки: UNAUTHORIZED
GET/v1/services
Показує список увімкнених сервісів: код, назва, ціна Gmail (у VND) і поточна наявність Gmail, блок icloud із ціною та наявністю для скриньок @icloud.com, а також масив offers[]: кожен склад, що продає сервіс, по одному запису на склад × тип скриньки (ціна, наявність, кількість кодів, правила). Дані кешуються: наявність оновлюється приблизно кожні 30–60 секунд, ціни рідше.
icloud = null означає, що сервіс ще не підтримує iCloud (постачальник його не пропонує або його тимчасово вимкнено): орендувати можна лише Gmail. Для сервісів із блоком icloud надішліть "domain": "icloud.com" під час створення замовлення, щоб отримати скриньку @icloud.com за ціною icloud.price. price / stock / icloud.price описують склад 1, куди потрапляють замовлення без warehouse (сервіси без складу 1 ⇒ найдешевший склад із наявністю); ціни й наявність по кожному складу є в offers[] (див. Склади). stock — число, яке публікує джерело (склад 1: оновлюється приблизно раз на 30 секунд), і це не обіцянка, що один виклик орендує стільки ж: скриньки видаються по одній, і джерело може вичерпатися посередині — тоді POST /v1/orders поверне менше замовлень і поле partial. Склади 2 і 4 так само показують числа своїх джерел.
Приклад
curl "https://otpgmail.net/v1/services" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": [
    {
      "code": "dr",
      "name": "OpenAI (ChatGPT)",
      "price": 350,
      "stock": 616525,
      "icloud": {
        "price": 630,
        "stock": 12464
      },
      "offers": [
        {
          "warehouse": 2,
          "domain": "gmail.com",
          "price": 350,
          "stock": 616525,
          "maxCodes": null,
          "rules": null,
          "fee": {
            "freeHours": 6,
            "paidPct": 50,
            "maxHours": 24
          },
          "isDefault": true
        },
        {
          "warehouse": 1,
          "domain": "gmail.com",
          "price": 630,
          "stock": 12464,
          "maxCodes": 3,
          "rules": null,
          "fee": null,
          "isDefault": false
        },
        {
          "warehouse": 1,
          "domain": "icloud.com",
          "price": 630,
          "stock": 12464,
          "maxCodes": 5,
          "rules": null,
          "fee": null,
          "isDefault": true
        }
      ],
      "note": null
    },
    {
      "code": "fb",
      "name": "Facebook",
      "price": 1590,
      "stock": 88,
      "icloud": null,
      "offers": [
        {
          "warehouse": 1,
          "domain": "gmail.com",
          "price": 1590,
          "stock": 88,
          "maxCodes": 10,
          "rules": null,
          "fee": null,
          "isDefault": false
        },
        {
          "warehouse": 4,
          "domain": "gmail.com",
          "price": 1580,
          "stock": 2310,
          "maxCodes": null,
          "rules": null,
          "fee": null,
          "isDefault": true
        }
      ],
      "note": null
    }
  ]
}
Можливі помилки: UNAUTHORIZED
POST/v1/orders
Створює замовлення на скриньку, що отримує коди підтвердження: за замовчуванням Gmail або iCloud, якщо надіслати domain: "icloud.com". Кошти з балансу списуються, щойно постачальник призначає скриньку. Повертає список замовлень (одне або кілька, якщо quantity > 1).
Часткова видача: quantity — це максимум
  • Скриньки видаються по одній. Якщо в постачальника вони закінчаться посередині (наприклад, на складі 1 у цю мить немає скриньок для цього сервісу), виклик зупиняється й повертає HTTP 200 з уже створеними замовленнями: data може бути коротшим за quantity, а списання відбувається лише за замовлення з data. Значення stock у GET /v1/services не гарантує, скільки отримає один виклик.
  • Тоді у відповіді поруч із data є ще поле верхнього рівня partial (якщо видано все, його немає — боти, що читають лише data, працюють без змін): requested, delivered, missing і reason = {code, message, retryable, retryAfterSec}, де code — помилка, що зупинила виклик (NO_MAILS_AVAILABLE, OUT_OF_STOCK, ALL_WAREHOUSES_EXHAUSTED, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED…).
  • Нестача зазвичай тимчасова: на складі 1 за кілька секунд у постачальника часто знову є скриньки. Якщо reason.retryable = true, зачекайте reason.retryAfterSec секунд (3–5 с) і знову запросіть скриньки, яких бракує (missing), з НОВИМ Idempotency-Key (старий ключ лише повторить цю саму відповідь); повторюйте, доки не отримаєте всі.
  • Одразу після часткової видачі наступний виклик може повернути 409 WAREHOUSE_UNAVAILABLE / OUT_OF_STOCK або 503 NO_MAILS_AVAILABLE — це нормально: і далі робіть паузи по 3–5 с між викликами та здавайтеся, лише коли багато викликів поспіль невдалі (наприклад, 10 спроб — близько хвилини); тоді спробуйте пізніше або надішліть "warehouse": 99, щоб система перейшла на інші склади. retryable = false (INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED…) ⇒ спершу усуньте причину (поповніть баланс або дочекайтеся завершення відкритих замовлень).
  • Відповіді з помилкою містять ці самі два поля всередині error: error.retryable і error.retryAfterSec (наприклад, 409 OUT_OF_STOCK ⇒ true, 5; 402 INSUFFICIENT_BALANCE ⇒ false, null) — див. «Коди помилок».
Заголовок Idempotency-Key (UUID) обов'язковий. Створюйте новий UUID для кожної нової оренди; під час повторної спроби після тайм-ауту використовуйте ТОЙ САМИЙ ключ, щоб не створити дубльоване замовлення. Цей виклик чекає, поки постачальник призначить скриньку, тому зазвичай триває близько 0,3 секунди, але в години пік (рідко) може тривати до 10 секунд: встановіть тайм-аут клієнта приблизно 15 секунд, а не здавайтеся через 5 секунд і не повторюйте запит завчасно.
Параметри
НазваТипОбов'язковийОпис
servicestringТакКод сервісу (з GET /v1/services, напр. "ig", "tg", "fb")
quantitynumberНіМАКСИМАЛЬНА кількість скриньок для оренди (за замовчуванням 1; не більше 200 за запит) — якщо склад вичерпається посередині, ви отримаєте менше: рахуйте data[] і читайте partial
domainstringНіТип скриньки: "gmail.com" (за замовчуванням), "icloud.com" або "gmx.com". Якщо не вказано = gmail.com: наявним інтеграціям нічого змінювати не потрібно. iCloud можна орендувати лише для сервісів, у яких поле icloud у GET /v1/services не дорівнює null; "gmx.com" лише для сервісів з offers[].domain = "gmx.com" (наразі лише Pokemon, код pkm); інакше виклик завершується помилкою DOMAIN_UNAVAILABLE. Будь-яке інше значення ⇒ VALIDATION_ERROR.
warehousenumberНіСклад, з якого орендувати. Якщо не вказано = СКЛАД 1, точно так, як до появи кількох складів (сервіси без складу 1 переходять на найдешевший): наявним клієнтам нічого змінювати не потрібно. 0 = найдешевший склад із наявністю (пробується лише один склад). 1–4 = саме цей склад (з offers[].warehouse у GET /v1/services); якщо він не продає цей сервіс/тип скриньки, призупинений або в нього немає наявності ⇒ WAREHOUSE_UNAVAILABLE. 99 = АВТОМАТИЧНО: склади з наявністю пробуються від найдешевшого до найдорожчого, з переходом далі, коли склад вичерпано або він дає збій; якщо не лишилося жодного ⇒ ALL_WAREHOUSES_EXHAUSTED; рекомендовано для сервісів, що часто вичерпуються (Facebook, Instagram…). Див. Склади.
Приклад
# Створюйте випадковий Idempotency-Key для кожного НОВОГО замовлення
# ВИКОРИСТОВУЙТЕ той самий ключ повторно під час повторної спроби після тайм-ауту (запобігає дублюванню замовлень)
# domain: "gmail.com" (за замовчуванням, необов'язково) | "icloud.com" (оренда скриньки @icloud.com) | "gmx.com" (лише сервіси, які його пропонують, напр. Pokemon)
# warehouse: 0 | 1–4 | 99 (необов'язково: якщо не вказувати, залишається склад 1, як раніше; 0 = найдешевший склад із наявністю; 99 = автоматично, переходить далі, коли склад вичерпується; див. offers[] у GET /v1/services)
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"}'
Приклад відповіді
{
  "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,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ]
}
Приклад відповіді — часткова видача (quantity 5, видано 2 скриньки)
{
  "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,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    {
      "orderId": "jkl012mno",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270001,
      "rentExpiresAt": 1753356401,
      "createdAt": 1753268201,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ],
  "partial": {
    "requested": 5,
    "delivered": 2,
    "missing": 3,
    "reason": {
      "code": "NO_MAILS_AVAILABLE",
      "message": "No inboxes available for this service right now, please try again later",
      "retryable": true,
      "retryAfterSec": 5
    }
  }
}
Можливі помилки: UNAUTHORIZED, VALIDATION_ERROR, SERVICE_DISABLED, INSUFFICIENT_BALANCE, OUT_OF_STOCK, DOMAIN_UNAVAILABLE, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED, NO_MAILS_AVAILABLE, QUANTITY_EXCEEDED, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCE
GET/v1/orders/{id}
Повертає замовлення за ID, зокрема коди, отримані на цей момент (otp[]). Опитуйте цей ендпоінт кожні 3–5 секунд, щоб підхопити код, поки замовлення має статус waiting_code.
Масив otp[] упорядковано за надходженням: найстаріший перший, НАЙНОВІШИЙ ОСТАННІЙ: читайте останній код через otp[otp.length - 1]. Під час кожного виклику система перевіряє в постачальника замовлення, що ще відкриті, у власному темпі кожного складу (кожні кілька секунд); частіші виклики просто повертають збережені дані. Після першого коду продовжуйте опитування: подальші коди (якщо сервіс надсилає більше одного) додаються в кінець otp[] без додаткової плати протягом строку замовлення; ліміт і часове вікно показано до оренди (maxCodes і fee в offers[]); щоб переконатися, що система очікує на новий код, викличте POST /v1/orders/{id}/next-code (для кожного складу; плату стягує лише склад 2 поза безкоштовним вікном). Якщо сервіс дозволяє лише один код на скриньку, otp[] зупиняється на цьому коді: відповіді залишаються звичайними, без помилки. Поле warehouse показує, який склад постачив скриньку (1–4); nextCodeFee — плата за додаткові коди (замовлення складів 2 і 3; null для складів 1 і 4), див. Склади. Починаючи з 2026-10-02 замовлення також містять nextCodeBlocked (true = скринька більше не може отримувати коди) і nextCodeBlockReason, причину: "email_banned" (email заблоковано сервісом: орендуйте новий), "activation_gone" (постачальник закрив скриньку), "hard_expired" (вікно кодів закінчилося), "max_codes" (отримано максимальну кількість кодів), null (не заблоковано / старіше замовлення без записаної причини). Це додані поля: наявним клієнтам нічого змінювати не потрібно. Опитуєте багато замовлень? Надсилайте до 100 ID за виклик у POST /v1/orders/status замість окремого GET на кожне замовлення — ті самі об'єкти замовлень, один запит у ліміті.
Параметри
НазваТипОбов'язковийОпис
idstringТакID замовлення (orderId, повернений під час створення замовлення)
Приклад
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "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,
    "warehouse": 1,
    "nextCodeFee": null,
    "listening": true,
    "listenUntil": 1753270500,
    "nextCodeBlocked": false,
    "nextCodeBlockReason": null
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND
POST/v1/orders/{id}/cancel
Скасовує замовлення й повертає 100% ціни. Дозволено лише поки замовлення має статус waiting_code І жодного коду не отримано.
Замовлення, що отримало код (навіть один), не можна скасувати чи повернути за нього кошти: це стосується і сайту, і API. Замовлення з rules.cancellable = false (сервіси власного складу, як-от Cointiply) скасувати не можна: щойно скриньку призначено, скасування й повернення коштів немає, а виклик скасування повертає ORDER_NOT_CANCELLABLE. Замовлення складу 3 для звичайних сервісів (rules.cancellable = true) можна скасувати, як і будь-які інші, до надходження коду.
Параметри
НазваТипОбов'язковийОпис
idstringТакID замовлення для скасування
Приклад
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json"
Приклад відповіді
{
  "success": true,
  "data": {
    "orderId": "abc123xyz",
    "service": "ig",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "cancelled",
    "price": 1060,
    "otp": [],
    "codeDeadlineAt": 1753270000,
    "rentExpiresAt": 1753356400,
    "createdAt": 1753268200,
    "warehouse": 1,
    "nextCodeFee": null,
    "listening": false,
    "listenUntil": null,
    "nextCodeBlocked": false,
    "nextCodeBlockReason": null
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR
POST/v1/orders/{id}/rerent
Орендує ТУ САМУ скриньку знову для замовлення сервісу власного складу (provider = "inventory", напр. Cointiply): створює НОВЕ замовлення на цій скриньці з новим вікном отримання за ціною rerentPriceVnd старого замовлення.
Працює, лише коли старе замовлення має canRerent = true (останнє замовлення цієї скриньки, попереднє вікно якого закінчилося або яке отримало всі свої листи). Гроші списуються, щойно виклик успішний: надішліть Idempotency-Key і використовуйте його повторно під час повторної спроби, щоб вас ніколи не списали двічі. Відповідь має вигляд { order, message }; order має ту саму форму, що й GET /v1/orders/{id}. Склад 2 (з 2026-10-09): замовлення, яке було скасовано, закінчилося за часом або прострочене (скриньку закрито): викликайте, коли canRerent = true (останнє замовлення цього email, і в email немає іншого активного замовлення). Система знову відкриває САМЕ ЦЕЙ email як НОВЕ замовлення (status = waiting_code) за ціною rerentPriceVnd (наразі 100% вашої ціни) і чекає 20 хв на лист, як для будь-якого замовлення складу 2: якщо листа немає, замовлення скасовується, а кошти повертаються на 100%. Email заблоковано сервісом ⇒ RERENT_EMAIL_BANNED; постачальник більше не може його відкрити ⇒ RERENT_MAIL_GONE; склад 2 тимчасово не працює ⇒ PROVIDER_ERROR; у жодному з трьох випадків кошти не списуються.
Параметри
НазваТипОбов'язковийОпис
idstringТакID попереднього замовлення на скриньку (того, де canRerent = true)
Приклад
# ВИКОРИСТОВУЙТЕ той самий ключ повторно під час повторної спроби після тайм-ауту (запобігає дублюванню замовлень)
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/rerent" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Idempotency-Key: $(uuidgen)"
Приклад відповіді
{
  "success": true,
  "data": {
    "order": {
      "orderId": "def456uvw",
      "service": "ctp",
      "domain": "icloud.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 3500,
      "otp": [],
      "codeDeadlineAt": 1753273500,
      "rentExpiresAt": 1753273500,
      "createdAt": 1753269000,
      "provider": "inventory",
      "rules": {
        "windowMinutes": 75,
        "maxCodes": 4,
        "cancellable": false,
        "refundOnTimeout": false,
        "rerentRatio": 0.5
      },
      "windowEndsAt": 1753273500,
      "canRerent": false,
      "rerentPriceVnd": 3500,
      "warehouse": 3,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    "message": "Inbox rented again — a new 75-minute message window has started"
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, RERENT_NOT_AVAILABLE, RERENT_EMAIL_BANNED, RERENT_MAIL_GONE, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCE
POST/v1/orders/{id}/next-code
Запитує наступний код на ТІЙ САМІЙ скриньці: працює для замовлень З УСІХ складів (склади 1–4), які вже отримали перший код (status = completed). Безкоштовно, окрім замовлень складу 2, чиє безкоштовне вікно закінчилося (nextCodeFee.feePriceVnd > 0): тоді надішліть confirmFee: true, щоб сплатити плату й відкрити ще одне вікно кодів (до жорсткого ліміту nextCodeFee.hardUntil). Вашому боту не потрібно знати склад: просто далі опитуйте GET /v1/orders/{id} — нові коди додаються до otp[] — і викликайте цей ендпоінт, коли хочете, щоб система очікувала на новий код.
Що робить цей виклик на кожному складі
  • Склад 1: система сама очікує на наступний код після кожного коду протягом строку замовлення (rentExpiresAt); виклик повертає 200 і нічого не змінює; коли скринька більше не може приймати коди (ліміт кодів вичерпано, скриньку закрито, термін минув) ⇒ 409 з nextCodeBlockReason.
  • Склад 2: знову відкриває скриньку в постачальника (reordered = true) і чекає 20 хв на лист; безкоштовно в межах безкоштовного вікна (nextCodeFee.feePriceVnd = 0), поза ним потрібно надіслати confirmFee = true (якщо немає ⇒ 409 NEXT_CODE_FEE_CONFIRM, нічого не списано). Плата залишається, лише якщо надійшов новий код: якщо коду немає протягом 20 хв ⇒ автоматичне повернення коштів. Після кожного коду система продовжує очікувати наступний ще 25 хв (listening = true, listenUntil).
  • Склад 3 (звичайні сервіси): відкриває коротке вікно прослуховування, безкоштовно в межах безкоштовного вікна (nextCodeFee.freeUntil); після нього ⇒ 409: скористайтеся POST /v1/orders/{id}/rerent. Сервіси власного складу, як-от Cointiply (rules.cancellable = false), не мають наступного коду (409): кожен лист з'являється сам протягом вікна.
  • Склад 4: повторно читає замовлення в постачальника й чекає на новий код, безкоштовно до rentExpiresAt.
200 = система очікує на новий код для цього замовлення (listening = true, listenUntil = коли очікування припиниться; reordered = true, якщо скриньку щойно відкрито знову; chargedVnd, якщо стягнуто плату), або новий код надійшов саме в момент вашого виклику (уже в order.otp). 409 = нове вікно очікування відкрити не вдалося: дивіться error.code; чому скринька не приймає більше кодів, зазначено в order.nextCodeBlockReason (GET /v1/orders/{id}). Замовлення, яке ще очікує на перший код (waiting_code) ⇒ 409, викликати поки не потрібно. Надсилайте Idempotency-Key з платними викликами (використовуйте його повторно під час повторної спроби, щоб вас ніколи не списали двічі). Постачальник недоступний ⇒ 502 PROVIDER_ERROR, спробуйте пізніше. Коди помилок 409 цього виклику: NEXT_CODE_FEE_CONFIRM (склад 2 потребує плати: викличте знову з confirmFee: true), EMAIL_BANNED (email заблоковано сервісом: орендуйте новий; error.details.blockReason = "email_banned"), ORDER_EXPIRED (термін минув, скриньку закрито, ліміт кодів вичерпано або в сервісу немає «наступного коду»: причина в order.nextCodeBlockReason), ORDER_NOT_CANCELLABLE (замовлення ще не отримало свого першого коду). Боти, які розглядають будь-який 409 від цього виклику як «більше кодів не буде», продовжують працювати без змін. refresh: true (склад 2): спочатку перевіряється новий лист (щойно отриманий видається, скринька не відкривається знову), потім скринька знову відкривається в постачальника — у безкоштовному вікні очікування починається заново на 20 хв; поза вікном строк очікування (listenUntil) не змінюється і нічого не списується. У відповіді додаються refreshed (true = щойно відкрито знову) і refreshAvailableAt (unix-секунди; до цього моменту повторне оновлення лише перевіряє листи; null = можна одразу). Інші склади: без ефекту (refreshed = false). Без refresh виклик працює точно як раніше.
Параметри
НазваТипОбов'язковийОпис
idstringТакID замовлення (orderId) — будь-який склад
confirmFeebooleanНіtrue = ви погоджуєтеся сплатити nextCodeFee.feePriceVnd, щоб відкрити ще одне вікно кодів. Не потрібно, поки feePriceVnd = 0.
refreshbooleanНіtrue = склад 2, замовлення чекає новий код (listening = true), але код не надходить: одразу знову відкрити скриньку в постачальника (оновити очікування) — попереднє очікування могло перестати отримувати листи на боці постачальника. Безкоштовно; не частіше одного разу на 5 с на замовлення. Замовлення не чекає ⇒ як звичайний виклик.
Приклад
# Безкоштовно (nextCodeFee дорівнює null або feePriceVnd = 0 — кожен склад, окрім складу 2 після його безкоштовного вікна): confirmFee не потрібен
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
  -H "Authorization: Bearer og_xxx..."

# Склад 2 після безкоштовного вікна: надішліть confirmFee = true, щоб сплатити nextCodeFee.feePriceVnd
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"confirmFee":true}'
Приклад відповіді
{
  "success": true,
  "data": {
    "order": {
      "orderId": "ghi789rst",
      "service": "dr",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "completed",
      "price": 350,
      "otp": [
        {
          "code": "482913",
          "receivedAt": 1753268300
        }
      ],
      "codeDeadlineAt": 1753269700,
      "rentExpiresAt": 1753354200,
      "createdAt": 1753267800,
      "warehouse": 2,
      "nextCodeFee": {
        "feePriceVnd": 0,
        "freeUntil": 1753311000,
        "hardUntil": 1753354200,
        "paidPct": 50,
        "freeHours": 6
      },
      "listening": true,
      "listenUntil": 1753269500,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    "message": "Another code window is open — waiting for a new code",
    "listening": true,
    "listenUntil": 1753269500,
    "reordered": true
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, NEXT_CODE_FEE_CONFIRM, INSUFFICIENT_BALANCE, EMAIL_BANNED, ORDER_EXPIRED, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR, DUPLICATE_REQUEST, MAINTENANCE

Статус багатьох замовлень за один виклик

Реселерам і ботам, які орендують багато скриньок, не варто надсилати окремий GET /v1/orders/{id} на кожне замовлення: POST /v1/orders/status читає до 100 замовлень за виклик з тими самими об'єктами замовлень, а GET /v1/orders показує список ваших замовлень зі збережених даних.
  • Опитуйте кожні 3–5 секунд через POST /v1/orders/status, до 100 ID замовлень за виклик — кожен виклик рахується як один запит у ліміті.
  • За кожен виклик система перевіряє в постачальника не більше 20 відкритих замовлень зі списку (спершу ті, що чекають перший код, потім ті, що найдовше не перевірялися), а решту повертає зі збережених даних; їх перевірять під час наступних викликів. Виклик відповідає приблизно за 3 секунди.
  • Прибирайте замовлення зі списку, щойно воно завершене: cancelled (кошти повернено) або completed, якщо більше кодів не потрібно.
  • ID, яких немає або які належать іншому акаунту, повертаються в missing (без 404) — приберіть і їх.
  • Після перезапуску поверніть усі відкриті замовлення через GET /v1/orders?status=waiting_code (ідіть за nextCursor) і продовжуйте опитування через POST /v1/orders/status.
POST/v1/orders/status
Повертає до 100 ваших замовлень за один виклик. Кожен елемент — саме те, що GET /v1/orders/{id} повертає для цього замовлення (status, otp[] з усіма отриманими кодами, warehouse…). Використовуйте, щоб опитувати одразу багато відкритих замовлень.
data зберігає порядок ids; повторювані ID ігноруються. ID, яких немає або які належать іншому акаунту, наведено в missing (без 404). Для відкритих замовлень система перевіряє постачальника так само, як GET /v1/orders/{id}, але не більше ніж для 20 замовлень за виклик — спершу ті, що чекають перший код, потім ті, що найдовше не перевірялися, — а решту повертає зі збережених даних (їх перевірять під час наступних викликів); виклик відповідає приблизно за 3 секунди. Один виклик — це один запит у ліміті, скільки б ID він не містив. Понад 100 ID, порожній список або ID не рядком ⇒ 400 VALIDATION_ERROR.
Параметри
НазваТипОбов'язковийОпис
idsstring[]ТакID замовлень (orderId) — від 1 до 100 за виклик
Приклад
# ID замовлень, на які ви чекаєте (orderId з POST /v1/orders), — до 100 за виклик
curl -X POST "https://otpgmail.net/v1/orders/status" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -d '{"ids":["ORDER_ID_1","ORDER_ID_2"]}'
Приклад відповіді
{
  "success": true,
  "data": [
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "completed",
      "price": 1060,
      "otp": [
        {
          "code": "123456",
          "receivedAt": 1753268500
        }
      ],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    {
      "orderId": "jkl012mno",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270001,
      "rentExpiresAt": 1753356401,
      "createdAt": 1753268201,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ],
  "missing": [
    "zzz999qqq"
  ]
}
Можливі помилки: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED
GET/v1/orders
Список ваших замовлень, спершу найновіші, лише зі збережених даних — нових кодів у постачальника не запитує (це робить POST /v1/orders/status). Фільтр за статусом і часом створення; сторінки — через nextCursor.
Передайте nextCursor у параметр cursor, щоб отримати наступну сторінку; nextCursor = null ⇒ остання сторінка. Сторінки ніколи не повторюють і не пропускають замовлення, навіть якщо багато замовлень створено в одну секунду. Внутрішні статуси ніколи не виводяться (лише waiting_code, completed, cancelled і queued). Після перезапуску бота: GET /v1/orders?status=waiting_code&limit=200, ідіть за nextCursor, потім опитуйте ці замовлення через POST /v1/orders/status.
Параметри
НазваТипОбов'язковийОпис
statusstringНіЛише цей статус: waiting_code | completed | cancelled | queued (не вказано ⇒ усі)
sincenumberНіЛише замовлення, створені в цей unix-час (секунди) або пізніше
limitnumberНіЗамовлень на сторінці: 1–200 (за замовчуванням 50)
cursorstringНіnextCursor попередньої сторінки, без змін
Приклад
# Після перезапуску: зберіть усі замовлення, що ще чекають на код, сторінку за сторінкою
curl "https://otpgmail.net/v1/orders?status=waiting_code&limit=200" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": [
    {
      "orderId": "jkl012mno",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270001,
      "rentExpiresAt": 1753356401,
      "createdAt": 1753268201,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ],
  "nextCursor": "1753268200:abc123xyz"
}
Можливі помилки: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED

Склади

Кожен запис offers[] — це одна пропозиція = (сервіс, склад, тип скриньки). Перелічено лише склади, що зараз продають; склад, у якому тимчасово немає наявності, залишається в списку з stock = 0. Номери складів постійні: 1, 2, 3, 4: передавайте той самий номер у параметрі warehouse під час створення замовлення.
Наявним клієнтам нічого змінювати не потрібно. Якщо warehouse не вказано ⇒ поведінка як і раніше (склад 1: очікування 25 хв, додаткові коди надходять автоматично протягом 24-годинного строку замовлення, якщо сервіс надсилає більше одного); надішліть 0 ⇒ найдешевший склад із наявністю; надішліть 99 ⇒ автоматичний склад із переходом далі, коли склад вичерпується (див. одразу нижче); усі наявні поля залишаються як є, додаються лише нові поля (offers[], warehouse, nextCodeFee, listening, nextCodeBlocked…).
Автоматичний склад: warehouse = 99 (рекомендовано для сервісів, що часто вичерпуються)
Надішліть "warehouse": 99, і система орендує з найдешевшого складу з наявністю; якщо цей склад вичерпано або він дає збій, вона переходить до наступного в межах того самого виклику. Ідеально для сервісів, що часто вичерпуються, як-от Facebook та Instagram: вашому боту більше не потрібно читати offers[] і повторювати спроби склад за складом.
  • Порядок спроб: ціна від нижчої до вищої (напр. 1 500 → 1 600 → 1 700 VND); однакові ціни ⇒ спочатку склад із меншим номером.
  • Склади, що повідомляють про нульову наявність (stock = 0 в offers[]), ніколи не викликаються: час не витрачається.
  • Переходить далі, коли поточний склад вичерпано або він дає збій (OUT_OF_STOCK, NO_MAILS_AVAILABLE, WAREHOUSE_UNAVAILABLE, PROVIDER_ERROR).
  • Інші помилки повертаються одразу, без спроби наступного складу: INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, QUANTITY_EXCEEDED, MAINTENANCE, SERVICE_DISABLED, DOMAIN_UNAVAILABLE…
  • quantity > 1: якщо першому складу не вистачає, решта береться з наступного — замовлення можуть охоплювати кілька складів; кожне замовлення містить warehouse і price складу, що його видав (і діють правила кодів цього складу).
  • Якщо перебрано всі склади й жодної скриньки орендувати не вдалося ⇒ ALL_WAREHOUSES_EXHAUSTED (HTTP 409, нічого не списано): спробуйте пізніше. Якщо орендовано лише частину скриньок, ви все одно отримаєте 200 з цими замовленнями.
Приклад: оренда Facebook Gmail із warehouse = 99
# warehouse: 99 — найдешевший склад із наявністю; немає наявності або збій ⇒ автоматично наступний склад
curl -X POST "https://otpgmail.net/v1/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service":"fb","quantity":1,"domain":"gmail.com","warehouse":99}'
Поля offers[]
ПолеТипОпис
warehouse1 | 2 | 3 | 4Номер складу: 1, 2, 3 або 4.
domainstringТип скриньки пропозиції: gmail.com, icloud.com або gmx.com (додатковий домен: лише деякі сервіси, наразі Pokemon).
pricenumberЦіна однієї скриньки з цього складу (VND).
stocknumberЗалишок скриньок. 0 = немає в наявності: оренда зі складу 1 або 2 повертає WAREHOUSE_UNAVAILABLE; склад 4 усе одно пробується (його джерела часто видають пошту, хоча повідомляють 0) і повертає OUT_OF_STOCK, якщо нічого немає.
maxCodesnumber | nullМаксимум кодів, які може отримати одна скринька; null = без обмежень / невідомо (див. fee).
rulesobject | nullПравила вікна для складу 3 (windowMinutes, maxCodes, cancellable, refundOnTimeout, rerentRatio); null для складів 1, 2 і 4.
feeobject | nullВікно додаткових кодів: freeHours безкоштовних годин від оренди, далі кожне продовження коштує paidPct% ціни оренди, до maxHours годин від оренди (null = без обмежень). fee = null: плати немає — додаткові коди нічого не коштують протягом строку замовлення, якщо сервіс надсилає більше одного (до maxCodes), як на складі 1.
isDefaultbooleantrue для найдешевшого складу з наявністю (за однакових цін ⇒ менший номер складу): саме він використовується з warehouse = 0 і пробується першим з 99.
Правила за складами
ПравилоСклад 1Склад 2Склад 3Склад 4
Очікування першого коду25 хв20 хв25 хв ¹До 25 хв (у деяких джерел коротше: реальний крайній термін — codeDeadlineAt замовлення)
Немає кодуАвтоскасування, повернення 100%Автоскасування, повернення 100%Автоскасування, повернення 100% ¹Автоскасування, повернення 100%
Скасування до кодуТакТакТак ¹Так
Кодів на скринькуЗгідно з maxCodesБез обмеженьБез обмежень ¹Згідно з maxCodes (зазвичай null = без обмежень; 1 для деяких пропозицій)
Додаткові кодиБез додаткової плати протягом 24-годинного строку замовлення, якщо сервіс надсилає більше кодів; автоматично (додатковий виклик не потрібен). Скринька зазвичай залишається відкритою близько 25 хвилин після останнього коду.Безкоштовно fee.freeHours год (наразі Gmail 6 год, iCloud 12 год); далі кожне продовження коштує fee.paidPct% ціни через next-code, до fee.maxHours (Gmail 24 год, iCloud 10 днів)Без додаткової плати 24 години; далі орендуйте ту саму скриньку знову (rerent) за 50% ціниБез додаткової плати протягом 24-годинного строку замовлення, як на складі 1: нові коди з'являються під час читання замовлення; next-code перечитує його одразу.
¹ Сервіси, що продаються лише з власного складу, як-от Cointiply (provider = "inventory", rules.cancellable = false), підпорядковуються rules: до rules.maxCodes листів протягом rules.windowMinutes хв, без скасування й без повернення коштів, якщо час минув без листа.
nextCodeFee — плата за додаткові коди (замовлення складів 2 і 3)
Входить у GET /v1/orders/{id} та кожну відповідь із замовленням; null для замовлень складів 1 і 4 (додаткові коди безкоштовні). Склад 2: викликайте POST /v1/orders/{id}/next-code, коли потрібен ще один код: якщо feePriceVnd більше 0, потрібно надіслати confirmFee = true. Склад 3 (звичайні сервіси): коли безкоштовне вікно закінчилося, feePriceVnd — це ціна повторної оренди тієї самої скриньки (rerent).
ПолеТипОпис
feePriceVndnumberСума, що списується, якщо ви викличете next-code зараз; 0 = ще в межах безкоштовного вікна.
freeUntilnumber | nullКінець поточного безкоштовного вікна (unix-секунди).
hardUntilnumber | nullЖорсткий ліміт (unix-секунди): після цього часу кодів більше не буде.
paidPctnumber% ціни оренди, що стягується за кожне додаткове вікно.
freeHoursnumberБезкоштовні години одного вікна кодів (склад 2: Gmail 6, iCloud 12; склад 3: 24).
Приклад: отримати список складів, а потім орендувати з одного з них
# warehouse: 0 | 1–4 | 99 (необов'язково: якщо не вказувати, залишається склад 1, як раніше; 0 = найдешевший склад із наявністю; 99 = автоматично, переходить далі, коли склад вичерпується; див. offers[] у GET /v1/services)
curl -X POST "https://otpgmail.net/v1/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service":"dr","quantity":1,"domain":"gmail.com","warehouse":2}'
Приклад замовлення складу 2 (GET /v1/orders/{id})
{
  "success": true,
  "data": {
    "orderId": "ghi789rst",
    "service": "dr",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "completed",
    "price": 350,
    "otp": [
      {
        "code": "482913",
        "receivedAt": 1753268300
      }
    ],
    "codeDeadlineAt": 1753269700,
    "rentExpiresAt": 1753354200,
    "createdAt": 1753267800,
    "warehouse": 2,
    "nextCodeFee": {
      "feePriceVnd": 175,
      "freeUntil": 1753289400,
      "hardUntil": 1753354200,
      "paidPct": 50,
      "freeHours": 6
    },
    "listening": false,
    "listenUntil": null,
    "nextCodeBlocked": false,
    "nextCodeBlockReason": null
  }
}

Масова оренда (партії)

Партії купуються на сайті (сторінка «Масова оренда»), а через API можна переглядати список партій і читати коди. Домен email визначає склад: Gmail = склад 1, iCloud = склад 3 (приватні скриньки).
  • Партія зберігається 24 год від купівлі: протягом цього часу читайте коди безкоштовно, без обмеження кількості кодів.
  • За скриньки, що не отримали код, кошти не повертаються; повертається (автоматично) лише нестача за кількістю.
  • Склад 1 (Gmail): кожна скринька має proxyUrl — GET повертає {"status":1,"code":"482913","all_codes":[...]} без ключа API (секретом є саме посилання: зберігайте його в таємниці). Ендпоінт кодів нижче також працює.
  • Склад 3 (iCloud): proxyUrl = null — читайте коди через ендпоінт нижче або відкрийте readUrl (вебсторінка). Кожна скринька закріплена за вашим акаунтом для цього сервісу; після закінчення терміну її все одно можна орендувати знову на сайті.
  • Листи без коду, який можна витягнути (вітальні листи, розсилки…), повертаються дослівно в code з isRaw: true (HTML або текст, до 20 000 символів).
Поля скриньки в партії (mails[])
ПолеТипОпис
idnumberID скриньки — параметр {mailId} ендпоінта кодів
emailstringАдреса email
urlstringСклад 1: = proxyUrl · склад 3: = readUrl (збережено для старіших скриптів / файлів експорту)
proxyUrlstring | nullСклад 1: посилання на код, що не потребує входу · склад 3: null
readUrlstringВебсторінка для читання кодів партії
codeCountnumberКількість отриманих кодів
lastCodestring | nullНайновіший код (необроблений лист: перші 300 символів; повний текст — в ендпоінті кодів)
lastIsRawbooleantrue = найновіший код є необробленим листом
checkedAtnumber | nullОстанній раз, коли система перевіряла постачальника щодо цієї скриньки (unix-секунди; null = ніколи)
GET/v1/batches
Ваші партії, найновіші першими (посторінково через page / pageSize, до 100). Кожна партія має warehouse (1 = Gmail, 3 = iCloud), status (active | expired | failed), expiresAt, codeCount і readUrl.
Параметри
НазваТипОбов'язковийОпис
pagenumberНіСторінка (за замовчуванням 1)
pageSizenumberНіПартій на сторінці (за замовчуванням 20, макс. 100)
Приклад
curl "https://otpgmail.net/v1/batches?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "k3v9m2x7q1w8e5r4",
        "serviceCode": "lf",
        "serviceName": "Tiktok",
        "domain": "icloud.com",
        "warehouse": 3,
        "status": "active",
        "countRequested": 3,
        "countReceived": 3,
        "countDuplicate": 0,
        "countCharged": 3,
        "unitPriceVnd": 600,
        "quotedVnd": 1800,
        "chargedVnd": 1800,
        "refundVnd": 0,
        "holdHours": 24,
        "expiresAt": 1753354200,
        "createdAt": 1753267800,
        "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
        "codeCount": 1,
        "rerentPriceVnd": null
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  }
}
Можливі помилки: UNAUTHORIZED
GET/v1/batches/{id}
Одна партія з її mails[]: візьміть id кожної скриньки, щоб прочитати її коди.
Партії Gmail (склад 1): proxyUrl кожної скриньки працює у ваших скриптах так само, як раніше. Партії iCloud (склад 3): proxyUrl = null, читайте коди через ендпоінт нижче. Прострочені партії iCloud містять rerentPriceVnd — ціну повторної оренди однієї скриньки окремо (здійснюється на сайті).
Параметри
НазваТипОбов'язковийОпис
idstringТакID партії (показано на сторінці «Масова оренда» та в readUrl)
Приклад
curl "https://otpgmail.net/v1/batches/BATCH_ID" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "id": "k3v9m2x7q1w8e5r4",
    "serviceCode": "lf",
    "serviceName": "Tiktok",
    "domain": "icloud.com",
    "warehouse": 3,
    "status": "active",
    "countRequested": 3,
    "countReceived": 3,
    "countDuplicate": 0,
    "countCharged": 3,
    "unitPriceVnd": 600,
    "quotedVnd": 1800,
    "chargedVnd": 1800,
    "refundVnd": 0,
    "holdHours": 24,
    "expiresAt": 1753354200,
    "createdAt": 1753267800,
    "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
    "codeCount": 1,
    "rerentPriceVnd": null,
    "mails": [
      {
        "id": 812,
        "email": "[email protected]",
        "url": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
        "proxyUrl": null,
        "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
        "fresh": true,
        "codeCount": 1,
        "lastCode": "482913",
        "lastCodeAt": 1753268500,
        "lastIsRaw": false,
        "checkedAt": 1753268507
      }
    ]
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND
GET/v1/batches/{id}/mails/{mailId}/codes
Читає коди однієї скриньки партії: якщо настав час, система перевіряє постачальника, а потім повертає УСІ отримані коди, найстаріший першим — найновіший код останній у codes[].
Опитуйте кожні 5–10 секунд під час очікування (частіші виклики просто повертають збережені дані — polledAt не змінюється). Листи без коду, який можна витягнути, повертаються дослівно в code з isRaw: true. Прострочені партії й далі повертають збережені коди, але більше не перевіряються.
Параметри
НазваТипОбов'язковийОпис
idstringТакID партії
mailIdnumberТакID скриньки (mails[].id)
poll0 | 1Ні0 = лише читати збережені коди, не перевіряти постачальника (за замовчуванням 1)
Приклад
# Викликайте знову кожні 5–10 секунд під час очікування; найновіший код останній у codes[]
curl "https://otpgmail.net/v1/batches/BATCH_ID/mails/MAIL_ID/codes" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "mailId": 812,
    "codes": [
      {
        "code": "482913",
        "receivedAt": 1753268500,
        "isRaw": false
      }
    ],
    "polledAt": 1753268507,
    "mail": {
      "id": 812,
      "email": "[email protected]",
      "url": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
      "proxyUrl": null,
      "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
      "fresh": true,
      "codeCount": 1,
      "lastCode": "482913",
      "lastCodeAt": 1753268500,
      "lastIsRaw": false,
      "checkedAt": 1753268507
    }
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED

Buy live Gmail accounts (2 warehouses)

Buy short-lived Gmail accounts outright: you are charged when you order and the accounts arrive within seconds. There are two mail-shop warehouses (numbered separately from the OTP-rental warehouses 1–4), each with ONE price for any quantity. Prices returned by the API are YOUR account's prices (custom price if the admin set one) — exactly what you are charged.
Warehouses
WarehouseEach account deliversDefault priceStock
1gm_cookieemail · password · cookies · inboxUrl (inbox link) · inboxJsonUrl (emails as JSON) · twoFaUrl (2FA link — just the 6 digits)3,300 VNDA number — published by the source, refreshed every minute. The source checks that accounts are live at purchase time, so you may get fewer — the shortfall is refunded (a partial order; warehouse 99: the rest goes to the next warehouse)
2gm_loginemail · password · loginUrl (quick login link — open it in a desktop browser) · otpUrl (Vinted OTP link — when delivers lists it)6,000 VNDA REFERENCE number from a source sharing the same stock (refreshed every minute; null when it cannot be read) — may be off by a few, never blocks a purchase; a shortfall becomes a partial order with the rest refunded
99As the warehouse that supplied each account (items[].warehouse)Price of the cheapest warehouse on sale (auto.priceVnd)Buys the cheapest warehouse first (auto.order); if it runs out midway the rest goes to the next warehouse at that warehouse's price (the difference is charged — refunded instead if your balance is short); every warehouse out ⇒ MAILSHOP_OUT_OF_STOCK, nothing charged
Вхід в акаунти складу 1 (без резервної пошти)
  • Акаунти складу 1 (gm_cookie) надходять без резервної пошти — ви отримуєте email, пароль, cookies і посилання на скриньку (inboxUrl / inboxJsonUrl), і більше нічого.
  • Рекомендований спосіб входу — імпорт cookies (cookies, JSON) у браузер: розширенням для імпорту cookies або через імпорт cookies у вашому антидетект-браузері, — а потім відкрийте Gmail: сесія вже авторизована, зазвичай без запиту пароля й без перевірки.
  • Потрібні лише коди чи листи? Читайте їх за inboxUrl (вебсторінка) або inboxJsonUrl (JSON) — без API-ключа й без входу в Google.
  • Вхід за паролем з нового пристрою чи IP може спричинити перевірку Google (резервна пошта, телефон або код) — цим акаунтам нічим її пройти, тож користуйтеся cookies або посиланням на скриньку.
  • Перепродаєте? Віддавайте своїм клієнтам cookies і посилання на скриньку — у .txt-вивантаженні (GET /v1/mail-shop/orders/{id}/download) усе це є одним рядком: email|password|cookies|inboxUrl — і попереджайте, що входити треба через cookies, а не за паролем.
  • Акаунти складу 2 (gm_login) натомість надходять із посиланням для швидкого входу (loginUrl) — відкрийте його в браузері на комп'ютері, щоб увійти.
3 steps
  1. See warehouses & prices: GET /v1/mail-shop/products ⇒ products[] (per warehouse: priceVnd, stock, available, maxQty) + auto (warehouse 99: order, priceVnd). To see the total first, call GET /v1/mail-shop/quote.
  2. Buy: POST /v1/mail-shop/orders with {"warehouse": 1 | 2 | 99, "quantity": N} + header Idempotency-Key (a new UUID per purchase). The response is the order, usually already completed with items[].
  3. Get the accounts: read items[] (table below) or download the TXT GET /v1/mail-shop/orders/{id}/download. status = pending ⇒ poll GET /v1/mail-shop/orders/{id} every 5 seconds until completed / partial / failed.
# 1) warehouses, your prices, stock
curl "https://otpgmail.net/v1/mail-shop/products" -H "Authorization: Bearer og_xxx..."

# 2) buy (warehouse 1 | 2 | 99) — a NEW Idempotency-Key per purchase
curl -X POST "https://otpgmail.net/v1/mail-shop/orders" \
  -H "Authorization: Bearer og_xxx..." -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" --max-time 60 \
  -d '{"warehouse":99,"quantity":2}'

# 3) status "pending"? read the order again (or download the .txt)
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" -H "Authorization: Bearer og_xxx..."
Fields of an account (items[])
ПолеТипОпис
lineNonumberPosition in the order (1, 2, …) — the {lineNo} of the inbox endpoints
warehouse1 | 2Warehouse that supplied this account
emailstringGmail address
passwordstringPassword
recoverystring?Recovery email — only when the source provides one; warehouse 1 (gm_cookie) accounts have NONE (sign in with the cookies, see the sign-in notes above)
cookiesstring | nullWarehouse 1: login cookies (JSON)
inboxUrlstring | nullWarehouse 1: inbox page without an API key (https://otpgmail.net/mail/{code}); null = link turned off
inboxJsonUrlstring | nullWarehouse 1: the same link + ?json ⇒ emails as JSON (section below)
twoFaUrlstring | nullWarehouse 1: the 2FA LINK (https://otpgmail.net/2fa/{code} — same code + expiry as inboxUrl): GET / open it in a browser ⇒ JUST the 6-digit 2FA code (section below). null = link turned off. The .txt file has this column when downloaded with ?twofa=1
inboxLinkExpiresAtnumber | nullWarehouse 1: when the link expires (unix seconds); null = no limit
loginUrlstring | nullWarehouse 2: quick login link
otpUrlstring | nullWarehouse 2: the account's Vinted OTP link — open it in a browser to see the code, ?json ⇒ JSON (section below). null = this account has no link
otpService"vinted" | nullWarehouse 2: the service of otpUrl
rawLinestringThe whole account on one line (cookies compacted) — copy it as is
Read the inbox as JSON (inboxJsonUrl)
GET {inboxJsonUrl} (= inboxUrl + ?json) returns the same JSON as GET /api/mailbox/{code}: email, letters[] (≤ max latest emails: from, subject, receivedAt, code — extracted code / link, content — sanitized HTML), total, etag, nextAt. No API key needed (the code in the link is the key — keep it private), CORS is open. Add &max=20|50|100|200 for more emails (default 10), &etag={previous etag} ⇒ unchanged emails come back as empty letters + unchanged: true. Reading one link faster than every 10 seconds ⇒ 429 (retryAfter); link off / wrong / expired ⇒ 404; account locked ⇒ 410.
curl "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json&max=20"
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "letters": [
      {
        "key": "h:9f2c4e1a7b3d5c80",
        "from": "Instagram [email protected]",
        "subject": "350038 is your Instagram code",
        "receivedAt": 1760000520,
        "code": "350038",
        "content": "<html><body><p>Enter this confirmation code in the app: 350038</p></body></html>"
      }
    ],
    "max": 10,
    "total": 8,
    "fetchedAt": 1760000530,
    "cached": false,
    "etag": "f1c2a9d4e0b37a65",
    "unchanged": false,
    "nextAt": 1760000540,
    "expiresAt": 1762592400,
    "serverTime": 1760000530
  }
}
Read the Vinted OTP code (otpUrl)
A warehouse-2 account with otpUrl ⇒ open it in a browser to see the latest Vinted code (the page refreshes itself); GET {otpUrl}?json returns {status, code, service, email, checkedAt, retryAfter}: status = waiting (no code yet — ask again after retryAfter seconds) · ok (code = the latest code) · dead (codes can no longer be read for this account) · error (temporarily unreadable — ask again). No API key needed (the code in the link is the key — keep it private), CORS is open; the server reads the source at most every few seconds per account; more than 120 calls / minute / IP ⇒ 429.
curl "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c?json"
{
  "success": true,
  "data": {
    "status": "ok",
    "code": "4829",
    "service": "vinted",
    "email": "[email protected]",
    "checkedAt": 1760000530,
    "retryAfter": 5
  }
}
The 2FA code (twoFaUrl)
Some warehouse-1 accounts have 2-step verification (2FA) on — a password sign-in makes Google ask for a 6-digit code. GET {twoFaUrl} (or open it in a browser) returns text/plain with JUST the current 6 digits (no newline), a Refresh header = seconds until the next code + 1 (browsers reload by themselves), Cache-Control: no-store, open CORS — you can hand the link to your customers. Account without 2FA ⇒ 404 no 2fa; wrong / off / expired link ⇒ 404 not found; locked account ⇒ 410 gone; too fast / source hiccup ⇒ 429 / 502 retry (Retry-After header). With an API key: GET /v1/mail-shop/orders/{id}/items/{lineNo}/2fa ⇒ {none, code, expiresIn}. The server caches the code until it expires. The .txt file (GET /v1/mail-shop/orders/{id}/download): add ?twofa=1 ⇒ a last twoFaUrl column (off by default — the old layout is unchanged).
curl "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d"
# 424819
Order statuses
СтатусОпис
pendingBeing delivered — accounts that arrived are in items[]; poll again in 5 seconds
completedFully delivered. Final.
partialDelivered short — the shortfall was refunded (refundVnd; message = the reason + the source's own words when it gave any). Final.
failedNothing delivered — fully refunded (message as above). Final.
refundedRefunded by an admin (refundVnd). Final.
Rules
  • Money: chargedVnd = Σ route[].quantity × unitPriceVnd (a 99 order split across warehouses ⇒ two legs in route). Shortfalls are refunded automatically (refundVnd). Delivered accounts are not refunded — the only warranty is the warehouse's terms (read them first).
  • Idempotency-Key is required (400 without it): retrying with the SAME key after a timeout returns the same order and charges nothing more; a new key buys again. Use a client timeout of at least 60 seconds.
  • Limits: at most 500 accounts per order (and the warehouse's maxQty), 10 purchases per minute for your account (web + API share it), 180 inbox reads per hour. Above ⇒ 429 RATE_LIMITED.
  • Common errors: MAILSHOP_OUT_OF_STOCK 409 (details.stock = how many you can buy now) · MAILSHOP_QTY_LIMIT 400 (details.max) · INSUFFICIENT_BALANCE 402 · MAILSHOP_DISABLED 503 (warehouse / section paused) · MAILSHOP_SUPPLIER_ERROR 502 (the source refused at once — a failed order was recorded and refunded, details.orderId) · VALIDATION_ERROR 400 (warehouse not 1 | 2 | 99, warehouse and product disagree, missing Idempotency-Key…).
  • Terms (warranty only for the first sign-in within 2 hours, accounts last ~6–24 hours) are in each warehouse's terms; the API does not ask you to accept them (the web asks once). API orders show up in your web history (channel = api) and vice versa.
GET/v1/mail-shop/products
The two mail-shop warehouses + automatic warehouse 99. products[]: warehouse (1 | 2), code (gm_cookie | gm_login), delivers (fields of every account), priceVnd (YOUR price), listPriceVnd (list price), stock, available, maxQty (most one order can take now). auto: order (the order warehouse 99 buys in), priceVnd (cheapest warehouse), available.
Warehouse 1 stock = what the source publishes (refreshed every minute; the source checks accounts are live at purchase time, so you may get fewer — the shortfall is refunded). Warehouse 2 = a reference number from a source sharing the same stock, may be off by a few (null = could not be read — just buy); a shortfall becomes a partial order with the rest refunded. available = false ⇒ do not buy from that warehouse (out of stock / paused). Prices can change — read the catalog again before a large purchase.
Приклад
curl "https://otpgmail.net/v1/mail-shop/products" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "products": [
      {
        "warehouse": 1,
        "code": "gm_cookie",
        "name": "Short-lived live Gmail — with cookies + inbox link",
        "description": "Short-lived personal Gmail account. You get the email · password · login cookies · an inbox link (add ?json to read the emails as JSON).",
        "terms": "• Warranty covers ONLY the first successful sign-in, within 2 hours of purchase.\n• These Gmail accounts typically last 6–24 hours: use them right after purchase.",
        "delivers": [
          "email",
          "password",
          "cookies",
          "inboxUrl",
          "inboxJsonUrl"
        ],
        "priceVnd": 3300,
        "listPriceVnd": 3300,
        "stock": 1840,
        "available": true,
        "maxQty": 500
      },
      {
        "warehouse": 2,
        "code": "gm_login",
        "name": "Short-lived live Gmail — with a quick login link",
        "description": "Short-lived personal Gmail account that has been used on a few services. You get the email · password · a quick login link.",
        "terms": "• Warranty covers ONLY the first successful sign-in, within 2 hours of purchase.\n• Sign in with the link in a DESKTOP BROWSER.",
        "delivers": [
          "email",
          "password",
          "loginUrl"
        ],
        "priceVnd": 6000,
        "listPriceVnd": 6000,
        "stock": 141,
        "available": true,
        "maxQty": 100
      }
    ],
    "auto": {
      "warehouse": 99,
      "priceVnd": 3300,
      "available": true,
      "order": [
        1,
        2
      ]
    }
  }
}
Можливі помилки: UNAUTHORIZED
GET/v1/mail-shop/quote
Call quote to see the total first (nothing is reserved). Warehouse 99 ⇒ route[] = planned split by known stock; available = false ⇒ that quantity cannot be bought right now.
Параметри
НазваТипОбов'язковийОпис
warehouse1 | 2 | 99НіMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringНіInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberНіNumber of accounts (default 1)
Приклад
curl "https://otpgmail.net/v1/mail-shop/quote?warehouse=99&quantity=10" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "warehouse": 99,
    "quantity": 10,
    "unitPriceVnd": 3300,
    "totalVnd": 38400,
    "available": true,
    "stock": null,
    "maxQty": 500,
    "route": [
      {
        "warehouse": 1,
        "quantity": 8,
        "unitPriceVnd": 3300
      },
      {
        "warehouse": 2,
        "quantity": 2,
        "unitPriceVnd": 6000
      }
    ]
  }
}
Можливі помилки: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_FOUND
POST/v1/mail-shop/orders
Buy: you are charged at once, then the system buys from the source and delivers. The response is the order — usually already completed with items[]; status = pending ⇒ poll GET /v1/mail-shop/orders/{id} every 5 seconds. The call waits up to about 16 seconds for the delivery.
Idempotency-Key is required (400 without it). Not enough stock ⇒ MAILSHOP_OUT_OF_STOCK (error.details.stock = how many you can buy now, nothing charged); over the per-order limit ⇒ MAILSHOP_QTY_LIMIT (error.details.max). 502 MAILSHOP_SUPPLIER_ERROR = the source refused at once: a failed order was recorded and fully refunded (error.details.orderId) — retry with a new key.
Параметри
НазваТипОбов'язковийОпис
warehouse1 | 2 | 99НіMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringНіInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberНіNumber of accounts (default 1)
Idempotency-KeystringТакHeader: a new UUID for every new purchase — reuse it only to retry that same purchase
Приклад
# Idempotency-Key обов'язковий: новий UUID на кожну купівлю; використовуйте той самий ключ повторно лише для повторної спроби після тайм-ауту
# warehouse: 1 = cookies + inbox link · 2 = login link · 99 = cheapest first
curl -X POST "https://otpgmail.net/v1/mail-shop/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --max-time 60 \
  -d '{"warehouse":99,"quantity":5}'
Приклад відповіді
{
  "success": true,
  "data": {
    "id": "p3x9k2m7q1w8e5r4",
    "requestedWarehouse": 99,
    "warehouse": 1,
    "productCode": "gm_cookie",
    "quantity": 2,
    "deliveredCount": 2,
    "unitPriceVnd": 3300,
    "chargedVnd": 9300,
    "refundVnd": 0,
    "status": "completed",
    "channel": "api",
    "route": [
      {
        "warehouse": 1,
        "quantity": 1,
        "unitPriceVnd": 3300
      },
      {
        "warehouse": 2,
        "quantity": 1,
        "unitPriceVnd": 6000
      }
    ],
    "message": null,
    "createdAt": 1760000400,
    "completedAt": 1760000409,
    "items": [
      {
        "lineNo": 1,
        "warehouse": 1,
        "email": "[email protected]",
        "password": "Pw7Xq!k3d",
        "rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "inboxUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxJsonUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json",
        "twoFaUrl": "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxLinkExpiresAt": 1762592400
      },
      {
        "lineNo": 2,
        "warehouse": 2,
        "email": "[email protected]",
        "password": "Qp9!rT2",
        "rawLine": "[email protected]!rT2----no----https://login.example/google/login?uid=s2c38a3b",
        "loginUrl": "https://login.example/google/login?uid=s2c38a3b",
        "otpUrl": "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c",
        "otpService": "vinted"
      }
    ]
  }
}
Можливі помилки: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_FOUND, MAILSHOP_DISABLED, MAILSHOP_QTY_LIMIT, MAILSHOP_OUT_OF_STOCK, INSUFFICIENT_BALANCE, MAILSHOP_SUPPLIER_ERROR, RATE_LIMITED, DUPLICATE_REQUEST, MAINTENANCE
GET/v1/mail-shop/orders
Your mail-shop orders, newest first (page / pageSize, up to 100 per page) — without items[]: fetch an order to get its accounts. Orders bought on the web are listed too (channel = web).
Параметри
НазваТипОбов'язковийОпис
pagenumberНіPage (default 1)
pageSizenumberНіOrders per page (default 20, max 100)
Приклад
curl "https://otpgmail.net/v1/mail-shop/orders?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "p3x9k2m7q1w8e5r4",
        "requestedWarehouse": 99,
        "warehouse": 1,
        "productCode": "gm_cookie",
        "quantity": 2,
        "deliveredCount": 2,
        "unitPriceVnd": 3300,
        "chargedVnd": 9300,
        "refundVnd": 0,
        "status": "completed",
        "channel": "api",
        "route": [
          {
            "warehouse": 1,
            "quantity": 1,
            "unitPriceVnd": 3300
          },
          {
            "warehouse": 2,
            "quantity": 1,
            "unitPriceVnd": 6000
          }
        ],
        "message": null,
        "createdAt": 1760000400,
        "completedAt": 1760000409
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  }
}
Можливі помилки: UNAUTHORIZED
GET/v1/mail-shop/orders/{id}
One order with every account delivered so far (an order still delivering already shows what has arrived). route[] = which warehouse, how many, at what price.
Poll every 5 seconds while status = pending; completed / partial / failed / refunded are final. An order of many warehouse-1 accounts (cookies) can weigh several MB — stop polling once it is final.
Параметри
НазваТипОбов'язковийОпис
idstringТакOrder ID (data.id returned by the purchase)
Приклад
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "id": "p3x9k2m7q1w8e5r4",
    "requestedWarehouse": 99,
    "warehouse": 1,
    "productCode": "gm_cookie",
    "quantity": 2,
    "deliveredCount": 2,
    "unitPriceVnd": 3300,
    "chargedVnd": 9300,
    "refundVnd": 0,
    "status": "completed",
    "channel": "api",
    "route": [
      {
        "warehouse": 1,
        "quantity": 1,
        "unitPriceVnd": 3300
      },
      {
        "warehouse": 2,
        "quantity": 1,
        "unitPriceVnd": 6000
      }
    ],
    "message": null,
    "createdAt": 1760000400,
    "completedAt": 1760000409,
    "items": [
      {
        "lineNo": 1,
        "warehouse": 1,
        "email": "[email protected]",
        "password": "Pw7Xq!k3d",
        "rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "inboxUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxJsonUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json",
        "twoFaUrl": "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxLinkExpiresAt": 1762592400
      },
      {
        "lineNo": 2,
        "warehouse": 2,
        "email": "[email protected]",
        "password": "Qp9!rT2",
        "rawLine": "[email protected]!rT2----no----https://login.example/google/login?uid=s2c38a3b",
        "loginUrl": "https://login.example/google/login?uid=s2c38a3b",
        "otpUrl": "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c",
        "otpService": "vinted"
      }
    ]
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND
GET/v1/mail-shop/orders/{id}/download
The delivered accounts as .txt (text/plain), separated by |. The FIRST LINE is the column header: warehouse 1 email|password|cookies|inboxUrl · warehouse 2 email|password|loginUrl · a 99 order with both warehouses ⇒ email|password|cookies|inboxUrl|loginUrl (missing cells empty).
cookies is one-line JSON and may contain | — split email and password at the first two |, the link at the last | (with extra columns ⇒ the links after cookies at the last | separators — links never contain |; read the header line for their order). Nothing delivered yet ⇒ 409 VALIDATION_ERROR. Add <c>?otp=1</c> ⇒ warehouse 2 gets a LAST column <c>otpUrl</c> (the Vinted OTP link — empty when the account has none): email|password|loginUrl|otpUrl. Without it ⇒ exactly the columns above. Add <c>?twofa=1</c> ⇒ warehouse 1 gets a LAST column <c>twoFaUrl</c> (the 2FA link — same code as inboxUrl; GET ⇒ just the 6 digits; empty when the link is off / expired): email|password|cookies|inboxUrl|twoFaUrl. Combines with <c>otp=1</c> — fixed column order: …|loginUrl|otpUrl|twoFaUrl (a 99 order with both warehouses). Without it ⇒ the default layout above.
Параметри
НазваТипОбов'язковийОпис
idstringТакOrder ID (data.id returned by the purchase)
Приклад
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
  -H "Authorization: Bearer og_xxx..." \
  -o mail-ORDER_ID.txt
Приклад відповіді
email|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d
Можливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR
GET/v1/mail-shop/orders/{id}/items/{lineNo}/inbox
Latest emails of a WAREHOUSE-1 account (sender, subject, time, extracted code / link, sanitized full email). Without an API key: GET the account's inboxJsonUrl (same emails, JSON).
max = 10 (default) | 20 | 50 | 100 | 200 — more emails take longer (up to about a minute). Cached for 15 seconds; every real read counts toward the hourly limit. 409 VALIDATION_ERROR = a warehouse-2 account (no inbox reading); 409 PROVIDER_ERROR with details.dead = the account was locked or removed.
Параметри
НазваТипОбов'язковийОпис
idstringТакOrder ID (data.id returned by the purchase)
lineNonumberТакAccount number within the order (items[].lineNo)
maxnumberНіNumber of latest emails: 10 (default), 20, 50, 100 or 200
Приклад
# Warehouse 1 accounts only; max = 10 | 20 | 50 | 100 | 200
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/inbox?max=20" \
  -H "Authorization: Bearer og_xxx..." \
  --max-time 70

# Same emails without an API key: GET the account's inboxJsonUrl
curl "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json&max=20"
Приклад відповіді
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "letters": [
      {
        "key": "h:9f2c4e1a7b3d5c80",
        "from": "Instagram [email protected]",
        "subject": "350038 is your Instagram code",
        "receivedAt": 1760000520,
        "code": "350038",
        "content": "<html><body><p>Enter this confirmation code in the app: 350038</p></body></html>"
      }
    ],
    "max": 10,
    "total": 8,
    "fetchedAt": 1760000530,
    "cached": false
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR
GET/v1/mail-shop/orders/{id}/items/{lineNo}/status
Is a bought warehouse-1 account still alive: live = true / false, or null when it cannot be determined. Counts toward the hourly inbox-read limit.
Параметри
НазваТипОбов'язковийОпис
idstringТакOrder ID (data.id returned by the purchase)
lineNonumberТакAccount number within the order (items[].lineNo)
Приклад
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/status" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "live": true,
    "reason": null,
    "checkedAt": 1760000600
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR
GET/v1/mail-shop/orders/{id}/items/{lineNo}/2fa
The current 2FA code (6-digit TOTP, changes every 30 seconds) of a warehouse-1 account — some warehouse-1 accounts have 2-step verification on (Google asks for a code at a password sign-in). none = true ⇒ the account has no 2FA (code / expiresIn null). expiresIn = seconds the code stays valid — ask again after that. Without an API key: GET the account's twoFaUrl (the body is just the 6 digits).
The server caches the code until it expires (every call within those 30 seconds gets the same code) — it does NOT count toward the hourly inbox-read limit. Calling too fast ⇒ 429 (retryAfterSec); a warehouse-2 account ⇒ 409; a locked account ⇒ 409 (details.dead).
Параметри
НазваТипОбов'язковийОпис
idstringТакOrder ID (data.id returned by the purchase)
lineNonumberТакAccount number within the order (items[].lineNo)
Приклад
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/2fa" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "none": false,
    "code": "424819",
    "expiresIn": 24
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR

Життєвий цикл замовлення

СтатусОпис
Очікує кодНове замовлення, що очікує на перший код. Опитуйте GET /v1/orders/{id} кожні 3–5 секунд.
ЗавершеноПерший код надійшов: статус змінюється одразу. Ще коди можуть надходити, якщо сервіс надсилає більше одного (без додаткової плати протягом строку замовлення; ліміт і вікно залежать від складу); продовжуйте опитування, і кожен новий код з'являтиметься в кінці otp[].
СкасованоКошти повернено на 100%. Відбувається, коли ви викликаєте /cancel (до надходження будь-якого коду) або коли код не надійшов до codeDeadlineAt (щонайбільше 25 хв).
Важливі примітки:
  • Опитуйте GET /v1/orders/{id} кожні 3–5 секунд, поки замовлення має статус waiting_code, щоб вчасно підхопити код.
  • Щойно надходить перший код, статус одразу змінюється на completed.
  • Немає коду до codeDeadlineAt (щонайбільше 25 хв; склад 2: 20 хв; деякі джерела складу 4 коротші) ⇒ система скасовує замовлення й повертає 100% (status = cancelled).
  • Масив otp[] упорядковано так: найстаріший перший, найновіший останній. Читайте останній код через otp[otp.length - 1] (Python: otp[-1]).
  • Замовлення completed й далі отримує коди автоматично: продовжуйте опитувати GET /v1/orders/{id}, і кожен новий код з'являтиметься в кінці otp[]. Додаткові коди нічого не коштують протягом строку замовлення, якщо сервіс надсилає більше одного; ліміт і часове вікно показано до оренди (maxCodes і fee в offers[]; на складі 1 скринька зазвичай залишається відкритою близько 25 хвилин після останнього коду). Щоб явно запросити ще один код (напр. щоб увійти після реєстрації): POST /v1/orders/{id}/next-code — працює на кожному складі, а плату стягує лише склад 2 поза безкоштовним вікном.
  • Якщо сервіс дозволяє лише один код на скриньку, otp[] зупиняється на цьому коді. Відповіді залишаються звичайними, без помилки: ви самі вирішуєте, коли припинити опитування.
  • otp[].code — це зазвичай короткий код (цифри / літери) або посилання. Лист без коду, який можна витягнути (вітальні листи, розсилки, незвичні формати кодів…), доставляється дослівно (HTML або текст) у otp[].code: розглядайте його як повний лист, якщо він довший за 80 символів, містить розрив рядка або є HTML, і шукайте в ньому код чи посилання самостійно.
  • POST /v1/orders чекає, поки постачальник призначить скриньку, тому зазвичай триває ~0,3 секунди, але в години пік (рідко) може тривати до 10 секунд. Встановіть тайм-аут клієнта ≈ 15 секунд, а не здавайтеся через 5 секунд і не повторюйте запит завчасно, а коли повторюєте, використовуйте той самий Idempotency-Key.
  • Замовлення, що отримало код, не можна скасувати чи повернути за нього кошти: виклик скасування повертає ORDER_NOT_CANCELLABLE.
  • Щоб орендувати скриньку @icloud.com, надішліть "domain": "icloud.com" під час створення замовлення (для Gmail поле не вказуйте; наявним ботам нічого змінювати не потрібно). Якщо сервіс не підтримує iCloud або iCloud тимчасово вимкнено, ви отримаєте DOMAIN_UNAVAILABLE: перейдіть на Gmail, і оренда одразу пройде. Життєвий цикл, правила скасування й повернення коштів та спосіб читання кодів цілком такі самі, як для Gmail.
  • Сервіси власного складу (provider = "inventory", напр. Cointiply, див. GET /v1/services) працюють лише з iCloud; кожен лист надходить як посилання в otp[].code разом із kind, label і повним текстом листа в text. Замовлення отримує до rules.maxCodes листів протягом rules.windowMinutes хв (до windowEndsAt), його не можна скасувати (ORDER_NOT_CANCELLABLE), а якщо час минув без листа, воно стає cancelled без повернення коштів. Щоб і далі отримувати на тій самій скриньці, викличте POST /v1/orders/{id}/rerent, поки canRerent дорівнює true (ціна: rerentPriceVnd). Усі інші сервіси / замовлення мають provider = "standard" — з 2026-10-24 provider має лише два значення: "standard" | "inventory" (фактичний склад — у warehouse); порівнюйте лише з "inventory".
  • Замовлення складу 2 (warehouse = 2): коди безкоштовні fee.freeHours год; коли безкоштовне вікно закривається, нові коди перестають надходити — викличте POST /v1/orders/{id}/next-code з confirmFee = true (сплативши nextCodeFee.feePriceVnd), щоб відкрити ще одне вікно, до nextCodeFee.hardUntil. Замовлення складу 3 для звичайних сервісів: безкоштовні коди 24 години, скасування можливе до надходження коду, потім використовуйте rerent. Замовлення складу 4: нові коди потрапляють в otp[] під час читання замовлення, безкоштовно до rentExpiresAt; для джерела з одним кодом nextCodeBlockReason після першого коду стає "max_codes".
  • Багато замовлень одночасно? Опитуйте до 100 за виклик через POST /v1/orders/status — див. розділ Статус багатьох замовлень за один виклик.

Ліміти запитів

Ліміти діють на кожен ключ API (акаунт може мати власні ліміти; інакше діють типові ліміти сайту):
  • 100 запитів/секунду — короткі сплески протягом кількох секунд
  • 3000 запитів/хвилину — загальний трафік за хвилину
Перевищення ліміту повертає HTTP 429 із заголовком Retry-After: <seconds> і тілом {"success":false,"error":{"code":"RATE_LIMITED",...}}. Зачекайте кількість секунд, указану в заголовку, перш ніж повторювати. Тіло помилки містить те саме число секунд у error.retryAfterSec (разом з error.retryable = true).

Коди помилок

Кожна помилка має однакову форму: {"success":false,"error":{"code":"...","message":"...","retryable":false,"retryAfterSec":null}}
З 2026-10-17 кожна помилка /v1 має ще error.retryable і error.retryAfterSec (додані поля — наявним клієнтам нічого змінювати не треба; details не змінюється). retryable = true ⇒ той самий виклик може вдатися пізніше: зачекайте retryAfterSec секунд (ціле число ≥ 1) і надішліть його знову — немає в наявності / немає скриньок / склад призупинено / помилка постачальника ⇒ 5 с; 429 RATE_LIMITED ⇒ заголовок Retry-After; 409 DUPLICATE_REQUEST, поки перший запит із тим самим Idempotency-Key ще виконується ⇒ 2 с. retryable = false (retryAfterSec = null) ⇒ повторний виклик не допоможе, доки ви не усунете причину (баланс, ліміт очікувальних замовлень, параметри, ключ API…). partial.reason у POST /v1/orders дотримується тих самих правил. У разі відповіді 409 «немає в наявності» (OUT_OF_STOCK, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED) зачекайте повні retryAfterSec секунд перед повтором — раніший повтор зазвичай повертає ту саму відповідь — і перед новою орендою перевірте stock / offers[].stock у GET /v1/services; за вже орендованими замовленнями стежте через вебхуки, а не частим опитуванням.
Приклади відповідей з помилкою
HTTP/1.1 409 Conflict
{
  "success": false,
  "error": {
    "code": "OUT_OF_STOCK",
    "message": "This service is currently out of stock",
    "retryable": true,
    "retryAfterSec": 5
  }
}

HTTP/1.1 429 Too Many Requests
Retry-After: 1
{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded, retry after 1s",
    "retryable": true,
    "retryAfterSec": 1
  }
}

HTTP/1.1 402 Payment Required
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Insufficient balance to rent an inbox",
    "retryable": false,
    "retryAfterSec": null
  }
}
Код помилкиHTTPretryable · retryAfterSecЗначення
UNAUTHORIZED401false · nullКлюч API недійсний або відсутній.
ACCOUNT_LOCKED403false · nullАкаунт заблоковано: усі запити з його ключем відхиляються. Зверніться до підтримки.
FORBIDDEN403false · nullАкаунт перестав бути активним, поки обробляли оренду (його щойно заблокували).
NOT_FOUND404false · nullУ вашому акаунті немає замовлення / партії / скриньки з таким ID (замовлення іншого акаунта також повертають 404).
VALIDATION_ERROR400false · nullПараметри запиту недійсні (неправильний тип, відсутнє обов'язкове поле тощо).
INSUFFICIENT_BALANCE402false · nullБаланс акаунта замалий, щоб оплатити замовлення.
SERVICE_DISABLED409false · nullКоду сервісу не існує або сервіс тимчасово вимкнено. Візьміть точний код із GET /v1/services.
OUT_OF_STOCK409true · 5У сервісу немає вільних скриньок (для обраного типу скриньки) на момент запиту.
DOMAIN_UNAVAILABLE409false · nullСервіс не пропонує обраний тип скриньки: iCloud ще не підтримується / тимчасово вимкнений, або в сервісу немає скриньок gmx.com. Надішліть domain "gmail.com" (або не вказуйте поле), щоб орендувати Gmail.
WAREHOUSE_UNAVAILABLE409true · 5 / falseОбраний склад не продає цей сервіс/тип скриньки, призупинений або щойно вичерпався. Оберіть інший склад з offers[] або надішліть warehouse = 99, щоб система обрала склад із наявністю. Якщо цей склад (або всі склади, що продають цей сервіс) недоступний для вашого акаунта, retryable = false — повторний виклик не допоможе, використайте інший склад.
ALL_WAREHOUSES_EXHAUSTED409true · 5Лише з warehouse = 99: перебрано всі склади з наявністю (від найдешевшого), і на всіх немає наявності або вони дають збій — або всі склади повідомляють про нульову наявність. Нічого не списано; спробуйте пізніше.
NO_MAILS_AVAILABLE503true · 5У постачальника не лишилося скриньок для цього сервісу (тимчасово немає).
QUANTITY_EXCEEDED400false · nullquantity перевищує максимум на запит (наразі 200).
WAITING_LIMIT_REACHED429false · nullЗабагато замовлень одночасно очікують на код (понад ліміт одночасних замовлень waiting_code вашого акаунта): зачекайте, поки частина завершиться, і орендуйте знову.
ORDER_NOT_CANCELLABLE409false · nullЗамовлення не можна скасувати: можливо, воно вже має код або більше не має статусу waiting_code. next-code також повертає цей код, коли замовлення ще не отримало свого першого коду.
RERENT_NOT_AVAILABLE409false · nullЗамовлення не можна орендувати знову. Склад 3: це не останнє замовлення скриньки, скриньку більше не закріплено за вашим акаунтом або попереднє вікно отримання ще відкрите. Склад 2: замовлення ще не закрите (воно ще може отримувати коди: скористайтеся next-code), воно надто старе для повторної оренди, у email є інше активне замовлення або сервіс призупинено. Склади 1 і 4 не мають повторної оренди.
RERENT_EMAIL_BANNED409false · nullПовторна оренда складу 2: постачальник повідомляє, що email заблоковано сервісом (email banned): його не можна орендувати знову; кошти не списуються.
RERENT_MAIL_GONE409false · nullПовторна оренда складу 2: постачальник більше не може відкрити цей email — орендуйте новий; кошти не списуються.
NEXT_CODE_FEE_CONFIRM409false · nullБезкоштовне вікно замовлення складу 2 закінчилося: викличте next-code знову з confirmFee: true, щоб сплатити плату (nextCodeFee.feePriceVnd). Нічого не списано.
EMAIL_BANNED409false · nullEmail заблоковано сервісом (постачальник повідомляє email banned): він не може отримувати більше кодів; орендуйте новий. З 2026-10-02 (раніше було ORDER_EXPIRED, той самий HTTP 409).
ORDER_EXPIRED409false · nullЗамовлення не може отримувати більше кодів: його термін минув (rentExpiresAt / nextCodeFee.hardUntil), постачальник закрив скриньку, ліміт кодів (maxCodes) вичерпано або в сервісу немає «наступного коду». Причина вказана в order.nextCodeBlockReason.
RATE_LIMITED429true · = Retry-AfterПонад 100 запитів/секунду або 3000 запитів/хвилину на ваш ключ API (акаунт може мати власні ліміти). Заголовок Retry-After повідомляє, скільки секунд чекати. На складі 2 POST /v1/orders і /rerent також повертають цей код, коли акаунт за останні 60 хвилин скасував багато замовлень складу 2 без коду, а код отримали лише небагато: оренда зі складу 2 призупиняється, доки частка скасувань не знизиться ("warehouse": 99 теж зупиняється там із цією помилкою — тим часом оберіть інший склад). Facebook / Instagram (fb, fbb, ig) — виняток.
DUPLICATE_REQUEST409true · 2 / falseIdempotency-Key уже використовувався з іншими даними або іншим ендпоінтом, або перший запит із цим ключем ще виконується (повторіть за мить). Створіть новий ключ, щоб зробити ще одне замовлення.
PROVIDER_ERROR502true · 5Помилка від постачальника скриньок (тимчасова: повторіть через кілька секунд).
MAINTENANCE503false · nullСистема на технічному обслуговуванні: створення й скасування замовлень призупинено.
MAILSHOP_DISABLED503false · nullКупівлю короткочасних Gmail призупинено або цей продукт вимкнено: спробуйте пізніше.
MAILSHOP_PRODUCT_NOT_FOUND404false · nullПродукту з таким кодом немає: візьміть точний код із GET /v1/mail-shop/products.
MAILSHOP_OUT_OF_STOCK409false · nullНедостатньо в наявності для цієї кількості за поточною ціною: error.details.stock = скільки акаунтів можна купити зараз.
MAILSHOP_QTY_LIMIT400false · nullquantity перевищує максимум на замовлення (наразі 500): error.details.max.
MAILSHOP_SUPPLIER_ERROR502false · nullДжерело одразу відмовило в купівлі: невдале замовлення записано, а гроші повернено (error.details.orderId). Спробуйте пізніше з новим Idempotency-Key.
INTERNAL500false · nullНеочікувана помилка сервера: спробуйте пізніше; якщо вона повторюється, зверніться до підтримки.

Вебхуки (push-сповіщення)

Зареєструйте один URL на акаунт, і ми надсилатимемо на нього підписану JSON-подію методом POST, щойно замовлення отримає новий код (order.code) або його буде скасовано / відшкодовано (order.cancelled). Вебхуки необов'язкові й охоплюють усі замовлення акаунта (веб і API). Доставка — за принципом «найкращої спроби»: залиште рідке опитування як запасний варіант (див. нижче).
Налаштування за 3 кроки
  1. PUT /v1/webhook із вашим URL (і за бажанням events) — збережіть data.secret (64 шістнадцяткові символи): він потрібен для перевірки кожної доставки. Повторний виклик PUT зберігає той самий secret.
  2. POST /v1/webhook/test — ми одразу надсилаємо подію ping і повертаємо, що відповів ваш сервер (ok, httpStatus, error).
  3. Ваш ендпоінт має відповісти будь-яким 2xx протягом 5 секунд — спершу відповідайте, потім обробляйте подію. Усе інше (3xx, 4xx, 5xx, тайм-аут, помилка з'єднання) вважається невдачею та повторюється.
URL має починатися з http:// або https:// (з дійсним сертифікатом) і бути доступним з інтернету: localhost, приватні, link-local, multicast і зарезервовані адреси (10.x, 172.16–31.x, 192.168.x, 169.254.x, fc00::/7, fe80::/10…) та власні сервери OTPGmail відхиляються з VALIDATION_ERROR. Ім'я хоста повторно розв'язується перед кожною доставкою. Перенаправлення не виконуються — реєструйте кінцевий URL.
Формат запиту
Кожна доставка — це HTTP-запит POST на ваш URL з тілом JSON (UTF-8) і такими заголовками:
ЗаголовокЗначення
Content-Typeapplication/json
User-AgentOTPGmail-Webhook/1
X-OTPGmail-EventНазва події: order.code, order.cancelled або ping
X-OTPGmail-DeliveryУнікальний ID події (UUID) — збігається з deliveryId у тілі й не змінюється під час повторів
X-OTPGmail-TimestampUnix-час (секунди) підпису цієї спроби — збігається з sentAt у тілі
X-OTPGmail-Signaturesha256= + hex у нижньому регістрі від HMAC-SHA256 над timestamp + "." + сире тіло з ключем — вашим secret
Перевірка підпису
Обчисліть HMAC-SHA256 з вашим secret як ключем над X-OTPGmail-Timestamp + . + сирим тілом запиту у вигляді hex у нижньому регістрі та порівняйте зі значенням після sha256= за сталий час. Ключ — сам рядок secret із 64 символів (його байти UTF-8, не декодуйте hex). Використовуйте сирі байти: розбір і повторна серіалізація JSON змінюють їх. Відхиляйте мітки часу, що відрізняються від вашого годинника більш ніж на 5 хвилин (захист від повторів).
Приклади приймача (Python і Node використовують лише стандартну бібліотеку; команда curl надсилає на ваш приймач правильно підписану тестову доставку)
# Локальний тест: надішліть своєму приймачу доставку, підписану точно так, як наша
SECRET="9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e"
TS=$(date +%s)
BODY='{"event":"ping","deliveryId":"6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8","sentAt":'$TS'}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -X POST "http://localhost:8080/" \
  -H "Content-Type: application/json" \
  -H "X-OTPGmail-Event: ping" \
  -H "X-OTPGmail-Delivery: 6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8" \
  -H "X-OTPGmail-Timestamp: $TS" \
  -H "X-OTPGmail-Signature: sha256=$SIG" \
  -d "$BODY"
Події
ПодіяКоли надсилається
order.codeЗамовлення отримало новий код — одна подія на кожен код, зокрема наступні коди на тій самій скриньці. code — новий код; otp перелічує всі коди замовлення на цей момент, від старих до нових (як у GET /v1/orders/{id}; лише новий код, якщо тіло перевищило б 64 КБ).
order.cancelledЗамовлення скасовано або відшкодовано — один раз на замовлення: код не надійшов до терміну (timeout), скасовано вами (user) чи адміністратором (admin), або відшкодовано через збій складу (provider). refundVnd = сума у VND, повернена на баланс (0, якщо правила сервісу не передбачають повернення).
pingЛише від POST /v1/webhook/test.
Поля події
ПолеТипОпис
eventstringНазва події (також у заголовку X-OTPGmail-Event)
deliveryIdstringУнікальний ID події (UUID, = X-OTPGmail-Delivery) — збережіть, щоб пропускати дублікати
sentAtnumberUnix-час цієї спроби (= X-OTPGmail-Timestamp)
orderIdstringID замовлення — той самий, що в GET /v1/orders/{id}
servicestringКод сервісу
warehouse1 | 2 | 3 | 4 | nullСклад 1–4, що видав скриньку; null, якщо замовлення так і не отримало скриньку
domainstringДомен скриньки: gmail.com, icloud.com або gmx.com
emailstring | nullАдреса орендованої скриньки
codestringЛише order.code: новий код — може бути й посиланням або повним листом, як otp[].code
receivedAtnumberЛише order.code: Unix-час надходження коду
statusstringСтатус замовлення на цей момент: completed для order.code, cancelled для order.cancelled (якщо адміністратор відшкодовує замовлення, що вже має коди, статус лишається completed)
otparrayЛише order.code: усі коди на цей момент, [{code, receivedAt}], від старих до нових
reasonstringЛише order.cancelled: timeout | user | admin | provider
refundVndnumberЛише order.cancelled: VND, повернені на баланс (0 = без повернення)
Приклад тіла — order.code
{
  "event": "order.code",
  "deliveryId": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8",
  "sentAt": 1760700125,
  "orderId": "k3m9x2p7q1w8e5r4",
  "service": "fb",
  "warehouse": 1,
  "domain": "gmail.com",
  "email": "[email protected]",
  "code": "482913",
  "receivedAt": 1760700124,
  "status": "completed",
  "otp": [
    {
      "code": "482913",
      "receivedAt": 1760700124
    }
  ]
}
Приклад тіла — order.cancelled
{
  "event": "order.cancelled",
  "deliveryId": "0b7e4c2a-5d1f-4e8b-9a3c-2f6d8e1b4a7c",
  "sentAt": 1760701630,
  "orderId": "p8v2n5c1x7z3m9k4",
  "service": "ig",
  "warehouse": 2,
  "domain": "gmail.com",
  "email": "[email protected]",
  "reason": "timeout",
  "refundVnd": 1500,
  "status": "cancelled"
}
Приклад тіла — ping
{
  "event": "ping",
  "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f",
  "sentAt": 1760700000
}
Повтори, пауза та історія
  • Невдала доставка повторюється через 10 с, 1 хв, 5 хв, 30 хв і 2 год (5 повторів); якщо й останній повтор невдалий, подію відкидають (failed).
  • 50 невдалих спроб поспіль (за всіма подіями) ставлять вебхук на паузу: GET /v1/webhook показує pausedAt, нічого не надсилається, нові події не стають у чергу.
  • Щоб відновити: знову викличте PUT /v1/webhook (можна з тим самим URL) або виконайте успішний POST /v1/webhook/test — після цього події з черги буде надіслано.
  • GET /v1/webhook/deliveries показує останні 100 доставок зі статусом, кількістю спроб і останньою помилкою; історія зберігається 7 днів.
Дублікати й порядок
Та сама подія може прийти кілька разів (наприклад, якщо ваша відповідь 2xx загубилася), а події можуть приходити не за порядком (кілька надсилаються паралельно). Зберігайте оброблені deliveryId і пропускайте повтори; для замовлення спирайтеся на його найсвіжіші дані (otp, status), а не на порядок надходження.
Опитування як запасний варіант
Вебхуки можуть затримуватися (повтори) або губитися (вебхук на паузі, ваш сервер недоступний). Залиште рідке опитування — наприклад, GET /v1/orders/{id} кожні 30–60 с для замовлень, які ще в waiting_code, — і вважайте об'єкт замовлення з API джерелом істини.
PUT/v1/webhook
Реєструє або оновлює вебхук акаунта. Перший виклик створює його й повертає новий secret; наступні виклики зберігають secret, знову вмикають вебхук і відновлюють його, якщо він був на паузі. Без events: обидві події для нового вебхука, без змін для наявного. URL має бути публічною адресою — інакше 400 VALIDATION_ERROR (причина в error.details.problem).
Параметри
НазваТипОбов'язковийОпис
urlstringТакВаш ендпоінт (http:// або https://, публічна адреса, не більше 2048 символів)
eventsstring[]НіЯкі події надсилати: ["order.code", "order.cancelled"] (за замовчуванням обидві)
Приклад
# Збережіть data.secret — він потрібен для перевірки кожної доставки
curl -X PUT "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-server.example/otpgmail-webhook","events":["order.code","order.cancelled"]}'
Приклад відповіді
{
  "success": true,
  "data": {
    "url": "https://your-server.example/otpgmail-webhook",
    "events": [
      "order.code",
      "order.cancelled"
    ],
    "enabled": true,
    "secret": "9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e",
    "pausedAt": null,
    "consecutiveFailures": 0,
    "createdAt": 1760699000,
    "updatedAt": 1760699000
  }
}
Можливі помилки: UNAUTHORIZED, VALIDATION_ERROR
GET/v1/webhook
Вебхук вашого акаунта, зокрема secret, pausedAt і consecutiveFailures. data дорівнює null, якщо вебхук не зареєстровано.
Приклад
curl "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "url": "https://your-server.example/otpgmail-webhook",
    "events": [
      "order.code",
      "order.cancelled"
    ],
    "enabled": true,
    "secret": "9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e",
    "pausedAt": 1760705400,
    "consecutiveFailures": 50,
    "createdAt": 1760699000,
    "updatedAt": 1760705400
  }
}
Можливі помилки: UNAUTHORIZED
DELETE/v1/webhook
Видаляє вебхук; події, що чекають у черзі, відкидаються. Завжди 200 — data.deleted дорівнює false, якщо видаляти не було чого.
Приклад
curl -X DELETE "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "deleted": true
  }
}
Можливі помилки: UNAUTHORIZED
POST/v1/webhook/test
Одразу надсилає подію ping на ваш URL і повідомляє результат: ok (ваш сервер відповів 2xx), httpStatus, durationMs, error. Успішний тест відновлює вебхук на паузі. Не більше 10 тестів на хвилину; 503 MAINTENANCE, поки вебхуки вимкнено в усій системі.
Приклад
curl -X POST "https://otpgmail.net/v1/webhook/test" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": {
    "ok": true,
    "httpStatus": 200,
    "durationMs": 143,
    "error": null,
    "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f"
  }
}
Відповідь 200, коли ваш сервер не відповідає 2xx
{
  "success": true,
  "data": {
    "ok": false,
    "httpStatus": 500,
    "durationMs": 88,
    "error": "HTTP 500",
    "deliveryId": "7d2b9f4e-1c6a-4e3b-8d5f-0a9c2e7b1f4d"
  }
}
Можливі помилки: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED, MAINTENANCE
GET/v1/webhook/deliveries
Останні доставки, спершу нові (зберігаються 7 днів): status = queued (наступна спроба в nextAt) | delivered | failed | paused, з attempts, lastHttpStatus і lastError. Тіла подій не повертаються.
Параметри
НазваТипОбов'язковийОпис
limitnumberНіКількість доставок, 1–100 (за замовчуванням 100)
Приклад
curl "https://otpgmail.net/v1/webhook/deliveries?limit=20" \
  -H "Authorization: Bearer og_xxx..."
Приклад відповіді
{
  "success": true,
  "data": [
    {
      "id": "0b7e4c2a-5d1f-4e8b-9a3c-2f6d8e1b4a7c",
      "event": "order.cancelled",
      "orderId": "p8v2n5c1x7z3m9k4",
      "status": "queued",
      "attempts": 1,
      "lastHttpStatus": 503,
      "lastError": "HTTP 503",
      "nextAt": 1760701640,
      "createdAt": 1760701630,
      "deliveredAt": null
    },
    {
      "id": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8",
      "event": "order.code",
      "orderId": "k3m9x2p7q1w8e5r4",
      "status": "delivered",
      "attempts": 1,
      "lastHttpStatus": 200,
      "lastError": null,
      "nextAt": null,
      "createdAt": 1760700124,
      "deliveredAt": 1760700125
    }
  ]
}
Можливі помилки: UNAUTHORIZED