Документація 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
Python
Node.js
curl "https://otpgmail.net/v1/balance" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"balance": 25000
}
}Можливі помилки: UNAUTHORIZEDGET/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
Python
Node.js
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
}
]
}Можливі помилки: UNAUTHORIZEDPOST/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або 503NO_MAILS_AVAILABLE— це нормально: і далі робіть паузи по 3–5 с між викликами та здавайтеся, лише коли багато викликів поспіль невдалі (наприклад, 10 спроб — близько хвилини); тоді спробуйте пізніше або надішліть"warehouse": 99, щоб система перейшла на інші склади.retryable= false (INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED…) ⇒ спершу усуньте причину (поповніть баланс або дочекайтеся завершення відкритих замовлень). - Відповіді з помилкою містять ці самі два поля всередині
error:error.retryableіerror.retryAfterSec(наприклад, 409OUT_OF_STOCK⇒true, 5; 402INSUFFICIENT_BALANCE⇒false,null) — див. «Коди помилок».
Заголовок Idempotency-Key (UUID) обов'язковий. Створюйте новий UUID для кожної нової оренди; під час повторної спроби після тайм-ауту використовуйте ТОЙ САМИЙ ключ, щоб не створити дубльоване замовлення. Цей виклик чекає, поки постачальник призначить скриньку, тому зазвичай триває близько 0,3 секунди, але в години пік (рідко) може тривати до 10 секунд: встановіть тайм-аут клієнта приблизно 15 секунд, а не здавайтеся через 5 секунд і не повторюйте запит завчасно.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
service | string | Так | Код сервісу (з GET /v1/services, напр. "ig", "tg", "fb") |
quantity | number | Ні | МАКСИМАЛЬНА кількість скриньок для оренди (за замовчуванням 1; не більше 200 за запит) — якщо склад вичерпається посередині, ви отримаєте менше: рахуйте data[] і читайте partial |
domain | string | Ні | Тип скриньки: "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. |
warehouse | number | Ні | Склад, з якого орендувати. Якщо не вказано = СКЛАД 1, точно так, як до появи кількох складів (сервіси без складу 1 переходять на найдешевший): наявним клієнтам нічого змінювати не потрібно. 0 = найдешевший склад із наявністю (пробується лише один склад). 1–4 = саме цей склад (з offers[].warehouse у GET /v1/services); якщо він не продає цей сервіс/тип скриньки, призупинений або в нього немає наявності ⇒ WAREHOUSE_UNAVAILABLE. 99 = АВТОМАТИЧНО: склади з наявністю пробуються від найдешевшого до найдорожчого, з переходом далі, коли склад вичерпано або він дає збій; якщо не лишилося жодного ⇒ ALL_WAREHOUSES_EXHAUSTED; рекомендовано для сервісів, що часто вичерпуються (Facebook, Instagram…). Див. Склади. |
curl
Python
Node.js
# Створюйте випадковий 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, MAINTENANCEGET/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 на кожне замовлення — ті самі об'єкти замовлень, один запит у ліміті.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | ID замовлення (orderId, повернений під час створення замовлення) |
curl
Python
Node.js
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_FOUNDPOST/v1/orders/{id}/cancel
Скасовує замовлення й повертає 100% ціни. Дозволено лише поки замовлення має статус waiting_code І жодного коду не отримано.
Замовлення, що отримало код (навіть один), не можна скасувати чи повернути за нього кошти: це стосується і сайту, і API. Замовлення з rules.cancellable = false (сервіси власного складу, як-от Cointiply) скасувати не можна: щойно скриньку призначено, скасування й повернення коштів немає, а виклик скасування повертає ORDER_NOT_CANCELLABLE. Замовлення складу 3 для звичайних сервісів (rules.cancellable = true) можна скасувати, як і будь-які інші, до надходження коду.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | ID замовлення для скасування |
curl
Python
Node.js
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_ERRORPOST/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; у жодному з трьох випадків кошти не списуються.| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | ID попереднього замовлення на скриньку (того, де canRerent = true) |
curl
Python
Node.js
# ВИКОРИСТОВУЙТЕ той самий ключ повторно під час повторної спроби після тайм-ауту (запобігає дублюванню замовлень)
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, MAINTENANCEPOST/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 (якщо немає ⇒ 409NEXT_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 виклик працює точно як раніше.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | ID замовлення (orderId) — будь-який склад |
confirmFee | boolean | Ні | true = ви погоджуєтеся сплатити nextCodeFee.feePriceVnd, щоб відкрити ще одне вікно кодів. Не потрібно, поки feePriceVnd = 0. |
refresh | boolean | Ні | true = склад 2, замовлення чекає новий код (listening = true), але код не надходить: одразу знову відкрити скриньку в постачальника (оновити очікування) — попереднє очікування могло перестати отримувати листи на боці постачальника. Безкоштовно; не частіше одного разу на 5 с на замовлення. Замовлення не чекає ⇒ як звичайний виклик. |
curl
Python
Node.js
# Безкоштовно (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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
ids | string[] | Так | ID замовлень (orderId) — від 1 до 100 за виклик |
curl
Python
Node.js
# 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_LIMITEDGET/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
status | string | Ні | Лише цей статус: waiting_code | completed | cancelled | queued (не вказано ⇒ усі) |
since | number | Ні | Лише замовлення, створені в цей unix-час (секунди) або пізніше |
limit | number | Ні | Замовлень на сторінці: 1–200 (за замовчуванням 50) |
cursor | string | Ні | nextCursor попередньої сторінки, без змін |
curl
Python
Node.js
# Після перезапуску: зберіть усі замовлення, що ще чекають на код, сторінку за сторінкою
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 (рекомендовано для сервісів, що часто вичерпуються)
Поля offers[]Надішліть
"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 з цими замовленнями.
curl
Python
Node.js
# 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}'¹ Сервіси, що продаються лише з власного складу, як-от Cointiply (
nextCodeFee — плата за додаткові коди (замовлення складів 2 і 3)provider = "inventory", rules.cancellable = false), підпорядковуються rules: до rules.maxCodes листів протягом rules.windowMinutes хв, без скасування й без повернення коштів, якщо час минув без листа.Входить у
GET /v1/orders/{id} та кожну відповідь із замовленням; null для замовлень складів 1 і 4 (додаткові коди безкоштовні). Склад 2: викликайте POST /v1/orders/{id}/next-code, коли потрібен ще один код: якщо feePriceVnd більше 0, потрібно надіслати confirmFee = true. Склад 3 (звичайні сервіси): коли безкоштовне вікно закінчилося, feePriceVnd — це ціна повторної оренди тієї самої скриньки (rerent).curl
Python
Node.js
# 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}'{
"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 символів).
GET/v1/batches
Ваші партії, найновіші першими (посторінково через page / pageSize, до 100). Кожна партія має warehouse (1 = Gmail, 3 = iCloud), status (active | expired | failed), expiresAt, codeCount і readUrl.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
page | number | Ні | Сторінка (за замовчуванням 1) |
pageSize | number | Ні | Партій на сторінці (за замовчуванням 20, макс. 100) |
curl
Python
Node.js
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
}
}Можливі помилки: UNAUTHORIZEDGET/v1/batches/{id}
Одна партія з її mails[]: візьміть id кожної скриньки, щоб прочитати її коди.
Партії Gmail (склад 1): proxyUrl кожної скриньки працює у ваших скриптах так само, як раніше. Партії iCloud (склад 3): proxyUrl = null, читайте коди через ендпоінт нижче. Прострочені партії iCloud містять rerentPriceVnd — ціну повторної оренди однієї скриньки окремо (здійснюється на сайті).
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | ID партії (показано на сторінці «Масова оренда» та в readUrl) |
curl
Python
Node.js
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_FOUNDGET/v1/batches/{id}/mails/{mailId}/codes
Читає коди однієї скриньки партії: якщо настав час, система перевіряє постачальника, а потім повертає УСІ отримані коди, найстаріший першим — найновіший код останній у codes[].
Опитуйте кожні 5–10 секунд під час очікування (частіші виклики просто повертають збережені дані — polledAt не змінюється). Листи без коду, який можна витягнути, повертаються дослівно в code з isRaw: true. Прострочені партії й далі повертають збережені коди, але більше не перевіряються.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | ID партії |
mailId | number | Так | ID скриньки (mails[].id) |
poll | 0 | 1 | Ні | 0 = лише читати збережені коди, не перевіряти постачальника (за замовчуванням 1) |
curl
Python
Node.js
# Викликайте знову кожні 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_LIMITEDBuy 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- Акаунти складу 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) — відкрийте його в браузері на комп'ютері, щоб увійти.
- 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, callGET /v1/mail-shop/quote. - Buy:
POST /v1/mail-shop/orderswith{"warehouse": 1 | 2 | 99, "quantity": N}+ headerIdempotency-Key(a new UUID per purchase). The response is the order, usually alreadycompletedwithitems[]. - Get the accounts: read
items[](table below) or download the TXTGET /v1/mail-shop/orders/{id}/download.status=pending⇒ pollGET /v1/mail-shop/orders/{id}every 5 seconds untilcompleted/partial/failed.
curl
Python
Node.js
# 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..."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"
# 424819Order statuses- Money:
chargedVnd= Σroute[].quantity×unitPriceVnd(a 99 order split across warehouses ⇒ two legs inroute). Shortfalls are refunded automatically (refundVnd). Delivered accounts are not refunded — the only warranty is the warehouse'sterms(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 ⇒ 429RATE_LIMITED. - Common errors:
MAILSHOP_OUT_OF_STOCK409 (details.stock= how many you can buy now) ·MAILSHOP_QTY_LIMIT400 (details.max) ·INSUFFICIENT_BALANCE402 ·MAILSHOP_DISABLED503 (warehouse / section paused) ·MAILSHOP_SUPPLIER_ERROR502 (the source refused at once — a failed order was recorded and refunded,details.orderId) ·VALIDATION_ERROR400 (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
Python
Node.js
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
]
}
}
}Можливі помилки: UNAUTHORIZEDGET/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
warehouse | 1 | 2 | 99 | Ні | Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product |
product | string | Ні | Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2) |
quantity | number | Ні | Number of accounts (default 1) |
curl
Python
Node.js
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_FOUNDPOST/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
warehouse | 1 | 2 | 99 | Ні | Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product |
product | string | Ні | Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2) |
quantity | number | Ні | Number of accounts (default 1) |
Idempotency-Key | string | Так | Header: a new UUID for every new purchase — reuse it only to retry that same purchase |
curl
Python
Node.js
# 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, MAINTENANCEGET/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).
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
page | number | Ні | Page (default 1) |
pageSize | number | Ні | Orders per page (default 20, max 100) |
curl
Python
Node.js
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
}
}Можливі помилки: UNAUTHORIZEDGET/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Order ID (data.id returned by the purchase) |
curl
Python
Node.js
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_FOUNDGET/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Order ID (data.id returned by the purchase) |
curl
Python
Node.js
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
-H "Authorization: Bearer og_xxx..." \
-o mail-ORDER_ID.txtemail|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1dМожливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERRORGET/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Order ID (data.id returned by the purchase) |
lineNo | number | Так | Account number within the order (items[].lineNo) |
max | number | Ні | Number of latest emails: 10 (default), 20, 50, 100 or 200 |
curl
Python
Node.js
# 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_ERRORPOST/v1/mail-shop/orders/{id}/items/{lineNo}/inbox-link
Manage the inbox link (inboxUrl + inboxJsonUrl + twoFaUrl — one shared code) of a warehouse-1 account: action "regenerate" = NEW link (the old one stops working immediately; also re-enables a link you turned off), "off" = turn it off (inboxUrl / inboxJsonUrl / twoFaUrl become null). Returns the account (an element of items[]).
At most 60 changes per 10 minutes for your account.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Order ID (data.id returned by the purchase) |
lineNo | number | Так | Account number within the order (items[].lineNo) |
action | string | Так | "regenerate" = new link (the old one stops working immediately) · "off" = turn the link off |
curl
Python
Node.js
# Нове посилання (старе одразу перестає працювати); надішліть {"action":"off"}, щоб вимкнути його
curl -X POST "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/inbox-link" \
-H "Authorization: Bearer og_xxx..." \
-H "Content-Type: application/json" \
-d '{"action":"regenerate"}'{
"success": true,
"data": {
"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/Hn4Vw8Zr2Lk6Tq9Pm3Xs7Bd1",
"inboxJsonUrl": "https://otpgmail.net/mail/Hn4Vw8Zr2Lk6Tq9Pm3Xs7Bd1?json",
"twoFaUrl": "https://otpgmail.net/2fa/Hn4Vw8Zr2Lk6Tq9Pm3Xs7Bd1",
"inboxLinkExpiresAt": 1762592400
}
}Можливі помилки: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITEDGET/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.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Order ID (data.id returned by the purchase) |
lineNo | number | Так | Account number within the order (items[].lineNo) |
curl
Python
Node.js
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_ERRORGET/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).
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Order ID (data.id returned by the purchase) |
lineNo | number | Так | Account number within the order (items[].lineNo) |
curl
Python
Node.js
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 секунд, поки замовлення має статус 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-24providerмає лише два значення:"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
}
}Вебхуки (push-сповіщення)
Зареєструйте один URL на акаунт, і ми надсилатимемо на нього підписану JSON-подію методом POST, щойно замовлення отримає новий код (
Налаштування за 3 крокиorder.code) або його буде скасовано / відшкодовано (order.cancelled). Вебхуки необов'язкові й охоплюють усі замовлення акаунта (веб і API). Доставка — за принципом «найкращої спроби»: залиште рідке опитування як запасний варіант (див. нижче).PUT /v1/webhookіз вашим URL (і за бажаннямevents) — збережітьdata.secret(64 шістнадцяткові символи): він потрібен для перевірки кожної доставки. Повторний виклик PUT зберігає той самий secret.POST /v1/webhook/test— ми одразу надсилаємо подіюpingі повертаємо, що відповів ваш сервер (ok,httpStatus,error).- Ваш ендпоінт має відповісти будь-яким 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) і такими заголовками:
Обчисліть HMAC-SHA256 з вашим secret як ключем над
Приклади приймача (Python і Node використовують лише стандартну бібліотеку; команда curl надсилає на ваш приймач правильно підписану тестову доставку)X-OTPGmail-Timestamp + . + сирим тілом запиту у вигляді hex у нижньому регістрі та порівняйте зі значенням після sha256= за сталий час. Ключ — сам рядок secret із 64 символів (його байти UTF-8, не декодуйте hex). Використовуйте сирі байти: розбір і повторна серіалізація JSON змінюють їх. Відхиляйте мітки часу, що відрізняються від вашого годинника більш ніж на 5 хвилин (захист від повторів).curl
Python
Node.js
# Локальний тест: надішліть своєму приймачу доставку, підписану точно так, як наша
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"{
"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).
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
url | string | Так | Ваш ендпоінт (http:// або https://, публічна адреса, не більше 2048 символів) |
events | string[] | Ні | Які події надсилати: ["order.code", "order.cancelled"] (за замовчуванням обидві) |
curl
Python
Node.js
# Збережіть 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_ERRORGET/v1/webhook
Вебхук вашого акаунта, зокрема secret, pausedAt і consecutiveFailures. data дорівнює null, якщо вебхук не зареєстровано.
Прикладcurl
Python
Node.js
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
}
}Можливі помилки: UNAUTHORIZEDDELETE/v1/webhook
Видаляє вебхук; події, що чекають у черзі, відкидаються. Завжди 200 — data.deleted дорівнює false, якщо видаляти не було чого.
Прикладcurl
Python
Node.js
curl -X DELETE "https://otpgmail.net/v1/webhook" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"deleted": true
}
}Можливі помилки: UNAUTHORIZEDPOST/v1/webhook/test
Одразу надсилає подію ping на ваш URL і повідомляє результат: ok (ваш сервер відповів 2xx), httpStatus, durationMs, error. Успішний тест відновлює вебхук на паузі. Не більше 10 тестів на хвилину; 503 MAINTENANCE, поки вебхуки вимкнено в усій системі.
Прикладcurl
Python
Node.js
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, MAINTENANCEGET/v1/webhook/deliveries
Останні доставки, спершу нові (зберігаються 7 днів): status = queued (наступна спроба в nextAt) | delivered | failed | paused, з attempts, lastHttpStatus і lastError. Тіла подій не повертаються.
Параметри| Назва | Тип | Обов'язковий | Опис |
|---|---|---|---|
limit | number | Ні | Кількість доставок, 1–100 (за замовчуванням 100) |
curl
Python
Node.js
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