Документация 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-аккаунтов на короткий срок (mail shop)
Помимо аренды ящиков, можно покупать Gmail-аккаунты целиком с 2 складов — склад 1: e-mail, пароль, cookies + ссылка на ящик (есть и JSON); склад 2: e-mail, пароль + ссылка для быстрого входа; склад 99 сам берёт сначала самый дешёвый (страница «Купить Gmail-аккаунт на короткий срок» на сайте). API умеет всё то же, что эта страница: склады, цены и остатки, покупка (нужен
Idempotency-Key), отслеживание выдачи, скачивание аккаунтов и чтение их писем. Подробнее: раздел Покупка Gmail-аккаунтов на короткий срок.Пусть ваши клиенты сами смотрят коды (страница /otp)
Перепродаёте арендованные ящики (через сайт или API)? Включите код доступа на странице «Профиль» и отправьте клиенту ссылку
https://otpgmail.net/ru/otp#k7m2q9:[email protected] (k7m2q9 — ваш код доступа): он увидит последние коды этого ящика без входа в аккаунт и никогда не увидит цены или ваш баланс; страница выполняет только бесплатные действия (никогда ничего не списывает с вас).JSON-ссылка для программ и ботов (ваших или клиента): возьмите ссылку в виде пути
https://otpgmail.net/ru/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 открыт; только чтение («Загрузить новый код» / «Арендовать этот e-mail снова» остаются на странице /otp). Те же ограничения против подбора, что и у страницы: неверный код / для email нет заказов для показа ⇒ 404 NOT_FOUND; повторные промахи или больше 30 запросов в минуту для одного email ⇒ 429 RATE_LIMITED с retryAfter (секунды). Если добавить ?json к ссылке /otp#…, JSON не вернётся: браузер никогда не отправляет на сервер часть после #.curl "https://otpgmail.net/ru/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 по умолчанию CDN отвечает 403).Вебхуки
Вместо постоянного опроса каждого заказа зарегистрируйте один 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" (ящик заблокирован сервисом — арендуйте новый), "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 (последний заказ этого e-mail, и на нём нет других активных заказов). Система заново открывает ТОТ ЖЕ e-mail как НОВЫЙ заказ (status = waiting_code) по цене rerentPriceVnd (сейчас 100 % вашей цены) и ждёт письмо 20 минут, как любой заказ склада 2, — нет письма ⇒ заказ отменяется с возвратом 100 %. Ящик заблокирован сервисом ⇒ 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
Запрашивает следующий код на ТОМ ЖЕ ящике — работает для заказов ЛЮБОГО склада (warehouse 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 (ящик заблокирован сервисом — арендуйте новый; 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 можно получать список пакетов и читать коды. Домен почты определяет склад: 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) идут без резервной почты — вы получаете e-mail, пароль, 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-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
}
}Вебхуки (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