Documentação da API
API pública /v1 — autentique-se com uma chave de API e automatize o aluguel de caixas de entrada a partir do seu próprio software
Sua chave de API
Você não está conectado — entre para obter sua própria chave de API.
Envie esta chave no cabeçalho Authorization: Bearer <key> ou no parâmetro de consulta ?api_key=<key>. Clique no ícone de olho para ver a chave completa; clique em Gerar nova chave quando precisar substituí-la (a chave antiga para de funcionar na hora).Visão geral
URL base
https://otpgmail.netAutenticação
Inclua sua chave de API em toda requisição, no cabeçalho
Authorization: Bearer og_xxx... (recomendado) ou na query string ?api_key=og_xxx....Idempotency-Key (evita pedidos duplicados)
Toda requisição
POST /v1/orders deve incluir o cabeçalho Idempotency-Key: <novo UUID por pedido>. Se a requisição der timeout ou a conexão cair, envie-a de novo com a mesma chave — o servidor devolve a resposta original em vez de criar outro pedido. Gere um novo UUID para cada novo pedido e reutilize um UUID somente ao repetir uma requisição.Muitos pedidos de uma vez: status em uma só chamada
Aluga dezenas ou centenas de caixas? Consulte o status delas em conjunto:
POST /v1/orders/status retorna até 100 pedidos por chamada (conta como uma única requisição no seu limite) e GET /v1/orders lista seus pedidos — depois de reiniciar, ?status=waiting_code devolve todos os pedidos que ainda esperam um código. Cada erro também diz se vale tentar de novo (error.retryable, error.retryAfterSec). Detalhes: a seção Status de vários pedidos.Tipo de caixa: Gmail ou iCloud
Por padrão você recebe uma caixa
@gmail.com. Para alugar uma caixa @icloud.com, adicione "domain": "icloud.com" ao corpo de POST /v1/orders. Omitir domain equivale a gmail.com — as integrações existentes continuam funcionando sem alterações. O iCloud só está disponível para serviços cujo campo icloud não seja null em GET /v1/services; o preço do iCloud pode ser diferente do preço do Gmail (veja icloud.price). Toda resposta de pedido inclui um campo domain que informa qual tipo de caixa o pedido usa. Alguns serviços também oferecem caixas @gmx.com (atualmente só o Pokemon, código pkm, armazém 2): envie "domain": "gmx.com" — um serviço o oferece quando o seu offers[] em GET /v1/services tem uma entrada com domain = "gmx.com"; qualquer outro serviço ⇒ DOMAIN_UNAVAILABLE. O gmx.com não está disponível para lotes.Armazéns: um serviço, várias fontes de caixas
Um serviço pode ser vendido por vários armazéns, cada um com preço, estoque e regras de código próprios (o array
offers[] em GET /v1/services). Envie "warehouse": 2 ao criar um pedido para escolher um; "warehouse": 99 ⇒ o sistema escolhe o armazém mais barato com estoque e passa para o próximo se ele esgotar (ideal para serviços que costumam esgotar, como Facebook e Instagram); 0 ⇒ o armazém mais barato com estoque. Clientes existentes não precisam mudar nada: omitir warehouse ⇒ armazém 1, como antes, e price, stock e icloud descrevem o armazém 1 (serviços sem armazém 1 ⇒ o armazém mais barato com estoque). Detalhes: veja Armazéns.Aluguel em lote (lotes)
Os lotes são comprados na web (página Aluguel em lote); a API permite listar lotes e ler códigos. Gmail (Armazém 1): cada caixa tem um
proxyUrl — um link de código que não precisa de chave de API. iCloud (Armazém 3): caixas privadas; leia os códigos com GET /v1/batches/{id}/mails/{mailId}/codes ou abra readUrl na web. Detalhes: seção Aluguel em lote.Comprar contas Gmail temporárias (mail shop)
Além de alugar caixas, você pode comprar contas Gmail completas de 2 armazéns — armazém 1: e-mail, senha, cookies + link da caixa de entrada (também em JSON); armazém 2: e-mail, senha + link de login rápido; o armazém 99 escolhe primeiro o mais barato (página Comprar Gmail temporário na web). A API faz tudo o que essa página faz: ver armazéns, preços e estoque, comprar (
Idempotency-Key obrigatório), acompanhar a entrega, baixar as contas e ler os e-mails delas. Detalhes: seção Comprar Gmail temporário.Deixe seus clientes verem os códigos sozinhos (página /otp)
Revende caixas que você alugou (pela web ou pela API)? Ative um código de compartilhamento na página Conta e mande ao cliente o link
https://otpgmail.net/pt/otp#k7m2q9:[email protected] (k7m2q9 = seu código de compartilhamento) — ele vê os códigos mais recentes desse e-mail sem fazer login e nunca vê preços nem o seu saldo; a página só faz ações gratuitas (nunca cobra de você).Link JSON para ferramentas / bots (seus ou do seu cliente): use o link em formato de caminho
https://otpgmail.net/pt/otp/k7m2q9:[email protected] (ele abre a página /otp igual ao link com #) e acrescente ?json ⇒ o servidor devolve um JSON com exatamente o que a página mostra: cards[] — um cartão por serviço alugado nesse e-mail (email, serviceName, state, items[] = até 5 códigos / links mais recentes, o mais novo primeiro) — e serverTime. state: waiting aguardando o primeiro código · listening aguardando um código novo · received já tem códigos · needs_owner o vendedor precisa estender · expired expirado · banned caixa bloqueada. Vários e-mails: separe com vírgulas (até 50). Sem chave de API nem cookies, CORS aberto; somente leitura (“Carregar novo OTP” / “Alugar este e-mail novamente” continuam na página /otp). Mesmos limites contra adivinhação da página: código errado / e-mail sem pedido para mostrar ⇒ 404 NOT_FOUND; erros repetidos ou mais de 30 leituras por minuto para um e-mail ⇒ 429 RATE_LIMITED com retryAfter (segundos). Acrescentar ?json a um link /otp#… NÃO devolve JSON: o navegador nunca envia ao servidor o que vem depois de #.curl "https://otpgmail.net/pt/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
}
}Código de exemplo completo no GitHub
Clientes prontos para rodar em Python, Node.js, PHP, C# (.NET) e curl — alugue uma caixa, espere o código, cancele automaticamente para ser reembolsado e alterne entre iCloud e Gmail quando o estoque acabar: github.com/letoan0902/otpgmail-email-otp-api (inglês) · thue-otp-gmail (vietnamita). Observação sobre Python: use
requests em vez do urllib puro (o User-Agent padrão dele recebe 403 da CDN).Webhooks
Em vez de consultar cada pedido sem parar, registre uma URL e receba um POST assinado assim que um pedido receber um novo código ou for cancelado / reembolsado — veja Webhooks.
Endpoints
GET/v1/balance
Retorna o saldo da conta (em VND) da chave de API usada na requisição.
Exemplocurl
Python
Node.js
curl "https://otpgmail.net/v1/balance" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"balance": 25000
}
}Erros possíveis: UNAUTHORIZEDGET/v1/services
Lista os serviços ativos — código, nome, preço no Gmail (em VND) e estoque atual de Gmail, um bloco icloud com o preço e o estoque das caixas @icloud.com e um array offers[]: todos os armazéns que vendem o serviço, uma entrada por armazém × tipo de caixa (preço, estoque, número de códigos, regras). Os dados ficam em cache: o estoque é atualizado a cada 30–60 segundos e os preços com menos frequência.
icloud = null significa que o serviço ainda não suporta iCloud (o fornecedor não o oferece ou ele está temporariamente desativado) — só dá para alugar Gmail. Para serviços com um bloco icloud, envie "domain": "icloud.com" ao criar o pedido para receber uma caixa @icloud.com por icloud.price. price / stock / icloud.price descrevem o armazém 1 — para onde vão os pedidos sem warehouse (serviços sem armazém 1 ⇒ o armazém mais barato com estoque); preços e estoque por armazém estão em offers[] (veja Armazéns). stock é o número que a fonte publica (armazém 1: atualizado mais ou menos a cada 30 segundos), não uma promessa de que uma única chamada consiga alugar tudo isso: as caixas são atribuídas uma de cada vez e a fonte pode acabar no meio — então POST /v1/orders retorna menos pedidos e um campo partial. Os armazéns 2 e 4 também mostram os números das suas fontes.
Exemplocurl
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
}
]
}Erros possíveis: UNAUTHORIZEDPOST/v1/orders
Cria um pedido de uma caixa que recebe códigos de verificação — Gmail por padrão, ou iCloud quando você envia domain: "icloud.com". Seu saldo é cobrado assim que o fornecedor atribui a caixa. Retorna uma lista de pedidos (um, ou vários quando quantity > 1).
Entrega parcial: quantity é um máximo
- As caixas são atribuídas uma de cada vez. Se o fornecedor ficar sem caixas no meio (por exemplo, o armazém 1 não tem mais caixas para aquele serviço naquele momento), a chamada para e retorna HTTP 200 com os pedidos criados até ali —
datapode ser menor que quantity, e você só é cobrado pelos pedidos emdata. OstockdeGET /v1/servicesnão garante quantas uma chamada vai receber. - Nesse caso a resposta traz também um campo de nível superior
partialao lado dedata(ausente quando tudo foi entregue — bots que só leemdatacontinuam funcionando):requested,delivered,missingereason={code, message, retryable, retryAfterSec}, em quecodeé o erro que interrompeu a chamada (NO_MAILS_AVAILABLE,OUT_OF_STOCK,ALL_WAREHOUSES_EXHAUSTED,INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED…). - Ficar sem caixas costuma ser temporário — no armazém 1 o fornecedor muitas vezes volta a ter caixas poucos segundos depois. Quando
reason.retryable= true, esperereason.retryAfterSecsegundos (3–5 s) e peça de novo as caixas que faltam (missing) com umaIdempotency-KeyNOVA (a chave antiga só repete esta mesma resposta); repita até ter todas. - Logo depois de uma entrega parcial, a próxima chamada pode retornar 409
WAREHOUSE_UNAVAILABLE/OUT_OF_STOCKou 503NO_MAILS_AVAILABLE— é normal: continue esperando 3–5 s entre as chamadas e só desista quando muitas chamadas seguidas falharem (por exemplo 10 tentativas, cerca de um minuto); aí tente mais tarde ou envie"warehouse": 99para o sistema passar a outros armazéns.retryable= false (INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED…) ⇒ resolva a causa primeiro (recarregue o saldo ou espere os pedidos abertos terminarem). - As respostas de erro trazem os mesmos dois campos dentro de
error:error.retryableeerror.retryAfterSec(por exemplo, 409OUT_OF_STOCK⇒true, 5; 402INSUFFICIENT_BALANCE⇒false,null) — veja Códigos de erro.
O cabeçalho Idempotency-Key (um UUID) é obrigatório. Gere um novo UUID para cada novo aluguel; ao repetir após um timeout, reutilize a MESMA chave para não criar um pedido duplicado. Esta chamada espera o fornecedor atribuir uma caixa, então normalmente leva cerca de 0,3 segundo, mas pode levar até 10 segundos em horários de pico (raro) — defina o timeout do seu cliente em cerca de 15 segundos em vez de desistir após 5 segundos e repetir cedo demais.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
service | string | Sim | Código do serviço (de GET /v1/services, ex.: "ig", "tg", "fb") |
quantity | number | Não | Número MÁXIMO de caixas a alugar (padrão 1; no máximo 200 por requisição) — se o armazém acabar no meio, você recebe menos: conte data[] e leia partial |
domain | string | Não | Tipo de caixa: "gmail.com" (padrão), "icloud.com" ou "gmx.com". Omitido = gmail.com — integrações existentes não precisam de mudanças. O iCloud só pode ser alugado para serviços cujo campo icloud não é null em GET /v1/services; "gmx.com" só para serviços com offers[].domain = "gmx.com" (hoje apenas o Pokemon, código pkm) — caso contrário a chamada falha com DOMAIN_UNAVAILABLE. Qualquer outro valor ⇒ VALIDATION_ERROR. |
warehouse | number | Não | Armazém de onde alugar. Omitido = ARMAZÉM 1, exatamente como antes dos vários armazéns (serviços sem armazém 1 usam o mais barato) — clientes existentes não precisam de mudanças. 0 = o armazém mais barato com estoque (só um armazém é tentado). 1–4 = exatamente esse armazém (de offers[].warehouse em GET /v1/services); se ele não vende este serviço/tipo de caixa, está pausado ou sem estoque ⇒ WAREHOUSE_UNAVAILABLE. 99 = AUTOMÁTICO: os armazéns com estoque são tentados do mais barato ao mais caro, passando para o próximo quando um está sem estoque ou falhando; nenhum restando ⇒ ALL_WAREHOUSES_EXHAUSTED — recomendado para serviços que costumam esgotar (Facebook, Instagram…). Veja Armazéns. |
curl
Python
Node.js
# Gere um Idempotency-Key aleatório para cada pedido NOVO
# REUTILIZE a mesma chave ao repetir após um timeout (evita pedidos duplicados)
# domain: "gmail.com" (padrão, opcional) | "icloud.com" (alugar uma caixa @icloud.com) | "gmx.com" (só nos serviços que o oferecem, ex.: Pokemon)
# warehouse: 0 | 1–4 | 99 (opcional — omita para manter o armazém 1 como antes; 0 = armazém mais barato com estoque; 99 = automático, passa para o próximo quando um armazém esgota; veja offers[] em 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
}
]
}Resposta de exemplo — entrega parcial (quantity 5, 2 caixas entregues)
Erros possíveis: {
"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}
Retorna um pedido pelo ID, incluindo os códigos recebidos até agora (otp[]). Consulte este endpoint a cada 3–5 segundos para pegar o código enquanto o pedido está no status waiting_code.
O array otp[] fica em ordem de chegada: o mais antigo primeiro, o MAIS NOVO POR ÚLTIMO — leia o código mais recente com otp[otp.length - 1]. A cada chamada, o sistema consulta o fornecedor para os pedidos ainda abertos, no ritmo próprio de cada armazém (a cada poucos segundos) — chamar com mais frequência só devolve os dados salvos. Depois do primeiro código, continue consultando: os códigos seguintes (se o serviço enviar mais de um) entram no fim de otp[] sem custo extra dentro do período do pedido — o limite e a janela de tempo aparecem antes de você alugar (maxCodes e fee em offers[]); para garantir que o sistema está esperando um novo código, chame POST /v1/orders/{id}/next-code (qualquer armazém; só o armazém 2 fora da janela grátis cobra taxa). Se o serviço permite apenas um código por caixa, otp[] para nesse código — as respostas continuam normais, sem erro. O campo warehouse informa qual armazém forneceu a caixa (1–4); nextCodeFee é a taxa por mais códigos (pedidos dos armazéns 2 e 3; null nos armazéns 1 e 4) — veja Armazéns. Desde 2026-10-02 os pedidos também trazem nextCodeBlocked (true = a caixa não pode receber mais códigos) e nextCodeBlockReason — o motivo: "email_banned" (o e-mail foi bloqueado pelo serviço — alugue outro), "activation_gone" (o fornecedor encerrou a caixa), "hard_expired" (a janela de códigos terminou), "max_codes" (o número máximo de códigos foi recebido), null (não bloqueado / pedido antigo sem motivo registrado). São campos adicionados — clientes existentes não precisam de mudanças. Consultando muitos pedidos? Envie até 100 IDs por chamada para POST /v1/orders/status em vez de um GET por pedido — os mesmos objetos de pedido e uma única requisição no seu limite.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do pedido (o orderId devolvido quando o pedido foi criado) |
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
}
}Erros possíveis: UNAUTHORIZED, NOT_FOUNDPOST/v1/orders/{id}/cancel
Cancela o pedido e reembolsa 100% do preço. Permitido apenas enquanto o pedido está no status waiting_code E nenhum código foi recebido.
Um pedido que já recebeu um código (mesmo que só um) não pode ser cancelado nem reembolsado — vale tanto para o site quanto para a API. Pedidos com rules.cancellable = false (serviços de estoque próprio, como o Cointiply) não podem ser cancelados: depois que a caixa é atribuída, não há cancelamento nem reembolso — a chamada de cancelamento retorna ORDER_NOT_CANCELLABLE. Pedidos do armazém 3 de serviços comuns (rules.cancellable = true) podem ser cancelados como qualquer outro pedido antes de um código chegar.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do pedido a cancelar |
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
}
}Erros possíveis: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE, PROVIDER_ERRORPOST/v1/orders/{id}/rerent
Aluga a MESMA caixa de novo para um pedido de um serviço de estoque próprio (provider = "inventory", ex.: Cointiply): cria um pedido NOVO nessa caixa, com uma nova janela de recebimento, ao preço rerentPriceVnd do pedido antigo.
Só funciona quando o pedido antigo tem canRerent = true (o pedido mais recente dessa caixa, cuja janela anterior terminou ou recebeu todos os seus e-mails). A cobrança ocorre assim que a chamada dá certo — envie um Idempotency-Key e reutilize-o ao repetir para nunca ser cobrado duas vezes. A resposta é { order, message }; order tem o mesmo formato de GET /v1/orders/{id}. Armazém 2 (desde 2026-10-09): um pedido cancelado, expirado por tempo ou vencido (caixa fechada) — chame quando
ParâmetroscanRerent = true (o pedido mais recente desse e-mail, sem outro pedido ativo nele). O sistema reabre EXATAMENTE esse e-mail como um pedido NOVO (status = waiting_code) ao preço rerentPriceVnd (hoje 100% do seu preço) e espera um e-mail por 20 minutos como qualquer pedido do armazém 2 — se nada chegar, é cancelado e reembolsado 100%. E-mail bloqueado pelo serviço ⇒ RERENT_EMAIL_BANNED; o fornecedor não consegue mais reabri-lo ⇒ RERENT_MAIL_GONE; armazém 2 fora do ar temporariamente ⇒ PROVIDER_ERROR — nenhum dos três cobra de você.| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do pedido anterior da caixa (o que tem canRerent = true) |
curl
Python
Node.js
# REUTILIZE a mesma chave ao repetir após um timeout (evita pedidos duplicados)
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"
}
}Erros possíveis: 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
Pede o próximo código na MESMA caixa — funciona para pedidos de QUALQUER armazém (warehouse 1–4) que já receberam o primeiro código (status = completed). É grátis, exceto em pedidos do armazém 2 cuja janela grátis já terminou (nextCodeFee.feePriceVnd > 0): nesse caso envie confirmFee: true para pagar a taxa e abrir outra janela de código (até o limite rígido nextCodeFee.hardUntil). Seu bot não precisa saber o armazém: continue consultando GET /v1/orders/{id} — novos códigos entram no fim de otp[] — e chame este endpoint quando quiser que o sistema espere um novo código.
O que esta chamada faz em cada armazém
- Armazém 1: o sistema espera sozinho o próximo código depois de cada código dentro do período do pedido (
rentExpiresAt) — a chamada retorna 200 e não muda nada; se a caixa não puder mais receber códigos (limite atingido, caixa fechada, prazo encerrado) ⇒ 409 comnextCodeBlockReason. - Armazém 2: reabre a caixa no fornecedor (
reordered= true) e espera um e-mail por 20 minutos; grátis dentro da janela grátis (nextCodeFee.feePriceVnd= 0), fora dela é preciso enviarconfirmFee= true (sem isso ⇒ 409NEXT_CODE_FEE_CONFIRM, nada é cobrado). A taxa só fica retida se chegar um novo código — sem código em 20 minutos ⇒ reembolso automático. Depois de cada código, o sistema continua esperando o próximo por mais 25 minutos (listening= true,listenUntil). - Armazém 3 (serviços comuns): reabre uma escuta curta, grátis dentro da janela grátis (
nextCodeFee.freeUntil); depois dela ⇒ 409 — usePOST /v1/orders/{id}/rerent. Serviços de estoque próprio como o Cointiply (rules.cancellable= false) não têm esta chamada (409): cada e-mail aparece sozinho durante a janela. - Armazém 4: relê o pedido no fornecedor e espera um novo código, grátis até
rentExpiresAt.
200 = o sistema está esperando um novo código para este pedido (listening = true, listenUntil = quando para de esperar; reordered = true quando a caixa acabou de ser reaberta; chargedVnd quando houve cobrança) ou um novo código chegou bem na hora da chamada (já está em order.otp). 409 = não foi possível abrir uma nova espera — leia error.code; o motivo de a caixa não aceitar mais códigos está em order.nextCodeBlockReason (GET /v1/orders/{id}). Pedido ainda esperando o primeiro código (waiting_code) ⇒ 409, ainda não precisa chamar. Envie um Idempotency-Key nas chamadas pagas (reutilize ao tentar de novo para nunca ser cobrado duas vezes). Fornecedor inacessível ⇒ 502 PROVIDER_ERROR, tente mais tarde. Códigos de erro 409 desta chamada: NEXT_CODE_FEE_CONFIRM (o armazém 2 exige a taxa — chame de novo com confirmFee: true), EMAIL_BANNED (o serviço bloqueou o e-mail — alugue outro; error.details.blockReason = "email_banned"), ORDER_EXPIRED (prazo encerrado, caixa fechada, limite de códigos atingido ou o serviço não tem "próximo código" — o motivo está em order.nextCodeBlockReason), ORDER_NOT_CANCELLABLE (o pedido ainda não recebeu o primeiro código). Bots que tratam qualquer 409 desta chamada como "não há mais códigos" continuam funcionando sem mudanças. refresh: true (armazém 2): primeiro verifica se há um e-mail novo (um que acabou de chegar é entregue, sem reabrir), depois reabre a caixa no fornecedor — dentro da janela grátis a espera recomeça por 20 minutos; fora dela a espera mantém o fim (listenUntil) e nada é cobrado. A resposta inclui refreshed (true = reaberta agora) e refreshAvailableAt (segundos unix; antes disso outra atualização só verifica e-mails; null = já pode). Outros armazéns: sem efeito (refreshed = false). Sem refresh a chamada funciona exatamente como antes.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do pedido (orderId) — qualquer armazém |
confirmFee | boolean | Não | true = você concorda em pagar nextCodeFee.feePriceVnd para abrir outra janela de código. Desnecessário enquanto feePriceVnd = 0. |
refresh | boolean | Não | true = armazém 2, o pedido aguarda um novo código (listening = true) mas ele não chega: reabre já a caixa no fornecedor (atualiza a espera) — a espera anterior pode ter parado de receber e-mails do lado do fornecedor. Grátis; no máximo uma vez a cada 5 s por pedido. Pedido sem espera ⇒ igual a uma chamada normal. |
curl
Python
Node.js
# Grátis (nextCodeFee é null ou feePriceVnd = 0 — qualquer armazém, exceto o 2 depois da janela grátis): não precisa de confirmFee
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
-H "Authorization: Bearer og_xxx..."
# Armazém 2 depois da janela grátis: envie confirmFee = true para pagar 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
}
}Erros possíveis: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, NEXT_CODE_FEE_CONFIRM, INSUFFICIENT_BALANCE, EMAIL_BANNED, ORDER_EXPIRED, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR, DUPLICATE_REQUEST, MAINTENANCEStatus de vários pedidos em uma só chamada
Revendedores e bots que alugam muitas caixas não devem enviar um
GET /v1/orders/{id} por pedido: POST /v1/orders/status lê até 100 pedidos por chamada com os mesmos objetos de pedido, e GET /v1/orders lista seus pedidos a partir dos dados salvos.- Consulte a cada 3–5 segundos com
POST /v1/orders/status, até 100 IDs de pedido por chamada — cada chamada conta como uma única requisição no seu limite. - Em cada chamada o sistema consulta o fornecedor para no máximo 20 pedidos abertos da lista (primeiro os que esperam o primeiro código, depois os que estão há mais tempo sem consulta) e devolve o restante com os dados salvos; esses são consultados nas próximas chamadas. Uma chamada responde em cerca de 3 segundos.
- Tire um pedido da sua lista quando ele estiver finalizado: cancelled (reembolsado) ou completed quando você não precisa de mais códigos.
- IDs que não existem ou pertencem a outra conta voltam em
missing(sem 404) — tire-os também. - Depois de reiniciar, recupere todos os pedidos abertos com
GET /v1/orders?status=waiting_code(siganextCursor) e volte a consultar comPOST /v1/orders/status.
POST/v1/orders/status
Retorna até 100 dos seus pedidos em uma chamada. Cada elemento é exatamente o que GET /v1/orders/{id} retorna para aquele pedido (status, otp[] com todos os códigos recebidos, warehouse…). Use para consultar muitos pedidos abertos de uma vez.
data mantém a ordem de ids; IDs repetidos são ignorados. IDs que não existem ou pertencem a outra conta aparecem em missing (sem 404). Para pedidos abertos, o sistema consulta o fornecedor como GET /v1/orders/{id}, mas no máximo para 20 pedidos por chamada — primeiro os que esperam o primeiro código, depois os que estão há mais tempo sem consulta — e devolve os demais com os dados salvos (eles são consultados nas próximas chamadas); uma chamada responde em cerca de 3 segundos. Cada chamada conta como uma única requisição no seu limite, não importa quantos IDs traga. Mais de 100 IDs, lista vazia ou IDs que não sejam strings ⇒ 400 VALIDATION_ERROR.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
ids | string[] | Sim | IDs dos pedidos (orderId) — de 1 a 100 por chamada |
curl
Python
Node.js
# IDs dos pedidos que você está esperando (orderId de POST /v1/orders) — até 100 por chamada
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"
]
}Erros possíveis: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITEDGET/v1/orders
Lista seus pedidos, do mais novo ao mais antigo, só com os dados salvos — não pede códigos novos ao fornecedor (quem faz isso é POST /v1/orders/status). Filtre por status e data de criação; pagine com nextCursor.
Passe nextCursor como cursor para obter a próxima página; nextCursor = null ⇒ última página. As páginas nunca repetem nem pulam um pedido, mesmo quando muitos pedidos foram criados no mesmo segundo. Status internos nunca aparecem (só waiting_code, completed, cancelled e queued). Depois que seu bot reiniciar: GET /v1/orders?status=waiting_code&limit=200, siga nextCursor e então consulte esses pedidos com POST /v1/orders/status.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
status | string | Não | Só este status: waiting_code | completed | cancelled | queued (omitido ⇒ todos) |
since | number | Não | Só pedidos criados neste horário unix (segundos) ou depois |
limit | number | Não | Pedidos por página: 1–200 (padrão 50) |
cursor | string | Não | nextCursor da página anterior, sem alterar |
curl
Python
Node.js
# Depois de reiniciar: reúna todos os pedidos que ainda esperam um código, página por página
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"
}Erros possíveis: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITEDArmazéns
Cada entrada de
offers[] é uma oferta = (serviço, armazém, tipo de caixa). Só aparecem os armazéns que estão vendendo no momento; um armazém temporariamente sem estoque continua na lista com stock = 0. Os números dos armazéns são fixos: 1, 2, 3, 4 — passe o mesmo número no parâmetro warehouse ao criar um pedido.Clientes existentes não precisam mudar nada. Omitir
warehouse ⇒ o mesmo comportamento de antes (armazém 1: espera de 25 minutos, códigos extras chegam automaticamente dentro do período de 24 horas do pedido quando o serviço envia mais de um); enviar 0 ⇒ armazém mais barato com estoque; enviar 99 ⇒ armazém automático, passando para o próximo quando um esgota (veja logo abaixo); todo campo existente continua como está — só entram campos novos (offers[], warehouse, nextCodeFee, listening, nextCodeBlocked…).Armazém automático: warehouse = 99 (recomendado para serviços que costumam esgotar)
Campos de offers[]Envie
"warehouse": 99 e o sistema aluga do armazém mais barato com estoque; se esse armazém estiver sem estoque ou falhando, ele passa para o próximo na mesma chamada. Ideal para serviços que costumam esgotar, como Facebook e Instagram — seu bot não precisa mais ler offers[] e tentar armazém por armazém.- Ordem de tentativa: preço do menor para o maior (ex.: 1.500 → 1.600 → 1.700 VND); preços iguais ⇒ menor número de armazém primeiro.
- Armazéns que informam estoque zero (
stock= 0 emoffers[]) nunca são chamados — sem perder tempo. - Passa para o próximo quando o armazém atual está sem estoque ou falhando (
OUT_OF_STOCK,NO_MAILS_AVAILABLE,WAREHOUSE_UNAVAILABLE,PROVIDER_ERROR). - Outros erros são retornados na hora, sem tentar o próximo armazém:
INSUFFICIENT_BALANCE,WAITING_LIMIT_REACHED,QUANTITY_EXCEEDED,MAINTENANCE,SERVICE_DISABLED,DOMAIN_UNAVAILABLE… quantity> 1: se o primeiro armazém não der conta, o restante vem do próximo — os pedidos podem abranger vários armazéns; cada pedido traz owarehousee opricedo armazém que o emitiu (e segue as regras de código desse armazém).- Se todos os armazéns foram tentados e nenhuma caixa pôde ser alugada ⇒
ALL_WAREHOUSES_EXHAUSTED(HTTP 409, nada cobrado) — tente de novo mais tarde. Se só algumas caixas foram alugadas, você ainda recebe 200 com esses pedidos.
curl
Python
Node.js
# warehouse: 99 — armazém mais barato com estoque; sem estoque ou falhando ⇒ próximo armazém automaticamente
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}'¹ Serviços vendidos apenas do estoque próprio, como o Cointiply (
nextCodeFee — taxa por mais códigos (pedidos dos armazéns 2 e 3)provider = "inventory", rules.cancellable = false), seguem rules: até rules.maxCodes e-mails em rules.windowMinutes minutos, sem cancelamento e sem reembolso se o tempo acabar sem nenhum e-mail.Vem em
GET /v1/orders/{id} e em toda resposta de pedido; null nos pedidos dos armazéns 1 e 4 (códigos extras são grátis). Armazém 2: chame POST /v1/orders/{id}/next-code quando precisar de outro código — se feePriceVnd for maior que 0, é preciso enviar confirmFee = true. Armazém 3 (serviços comuns): terminada a janela grátis, feePriceVnd é o preço para alugar a mesma caixa de novo (rerent).curl
Python
Node.js
# warehouse: 0 | 1–4 | 99 (opcional — omita para manter o armazém 1 como antes; 0 = armazém mais barato com estoque; 99 = automático, passa para o próximo quando um armazém esgota; veja offers[] em 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
}
}Aluguel em lote (lotes)
Os lotes são comprados na web (página Aluguel em lote) — a API permite listar lotes e ler códigos. O domínio do e-mail define o armazém: Gmail = Armazém 1, iCloud = Armazém 3 (caixas privadas).
- Um lote é mantido por 24 horas a partir da compra — leia os códigos de graça nesse período, sem limite de quantidade de códigos.
- Caixas que não recebem código não são reembolsadas; só a falta de quantidade é reembolsada (automaticamente).
- Armazém 1 (Gmail): cada caixa tem um
proxyUrl— umGETretorna{"status":1,"code":"482913","all_codes":[...]}sem chave de API (o link é o segredo — mantenha-o privado). O endpoint de código abaixo também funciona. - Armazém 3 (iCloud):
proxyUrl=null— leia os códigos com o endpoint abaixo ou abrareadUrl(página web). Cada caixa fica vinculada à sua conta para esse serviço; depois que o lote expira, você ainda pode alugá-la de novo na web. - E-mails sem código extraível (e-mails de boas-vindas, newsletters…) voltam na íntegra em
codecomisRaw: true(HTML ou texto, até 20.000 caracteres).
GET/v1/batches
Seus lotes, do mais novo ao mais antigo (paginados com page / pageSize, até 100). Cada lote tem warehouse (1 = Gmail, 3 = iCloud), status (active | expired | failed), expiresAt, codeCount e readUrl.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
page | number | Não | Página (padrão 1) |
pageSize | number | Não | Lotes por página (padrão 20, máx. 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
}
}Erros possíveis: UNAUTHORIZEDGET/v1/batches/{id}
Um lote com seu mails[] — pegue o id de cada caixa para ler os códigos dela.
Lotes Gmail (armazém 1): o proxyUrl de cada caixa funciona nos seus scripts exatamente como antes. Lotes iCloud (armazém 3): proxyUrl = null, leia os códigos com o endpoint abaixo. Lotes iCloud expirados trazem rerentPriceVnd — o preço para alugar uma caixa de novo individualmente (feito na web).
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do lote (exibido na página Aluguel em lote e em 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
}
]
}
}Erros possíveis: UNAUTHORIZED, NOT_FOUNDGET/v1/batches/{id}/mails/{mailId}/codes
Lê os códigos de uma caixa de um lote: o sistema consulta o fornecedor se a caixa estiver na hora, e então retorna TODOS os códigos recebidos, do mais antigo ao mais novo — o código mais recente é o último de codes[].
Consulte a cada 5–10 segundos enquanto espera (chamar com mais frequência só devolve os dados armazenados — polledAt não muda). E-mails sem código extraível voltam na íntegra em code com isRaw: true. Lotes expirados ainda devolvem seus códigos armazenados, mas não são mais verificados.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do lote |
mailId | number | Sim | ID da caixa (mails[].id) |
poll | 0 | 1 | Não | 0 = só lê os códigos armazenados, sem consultar o fornecedor (padrão 1) |
curl
Python
Node.js
# Consulte de novo a cada 5–10 segundos enquanto espera; o código mais recente é o último de 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
}
}
}Erros possíveis: 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- As contas do armazém 1 (
gm_cookie) vêm sem e-mail de recuperação — você recebe e-mail, senha, cookies e o link da caixa de entrada (inboxUrl/inboxJsonUrl), nada além disso. - Forma recomendada de entrar: importe os cookies (
cookies, JSON) em um navegador — com uma extensão de importação de cookies ou com a importação de cookies do seu navegador antidetect — e abra o Gmail: a sessão já está conectada, normalmente sem pedir senha nem verificação. - Só precisa dos códigos ou dos e-mails? Leia pelo
inboxUrl(página web) ou peloinboxJsonUrl(JSON) — sem chave de API e sem login no Google. - Entrar com a senha em um dispositivo ou IP novo pode fazer o Google pedir verificação (e-mail de recuperação, telefone ou um código) — essas contas não têm como passar por isso, então use os cookies ou o link da caixa de entrada.
- Revende? Entregue aos seus clientes os cookies e o link da caixa de entrada — o download .txt (
GET /v1/mail-shop/orders/{id}/download) trazemail|password|cookies|inboxUrlem uma linha — e oriente-os a entrar com os cookies, não com a senha. - As contas do armazém 2 (
gm_login) vêm, em vez disso, com um link de login rápido (loginUrl) — abra-o em um navegador de computador para entrar.
- 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.
Exemplocurl
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
]
}
}
}Erros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
warehouse | 1 | 2 | 99 | Não | Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product |
product | string | Não | Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2) |
quantity | number | Não | 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
}
]
}
}Erros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
warehouse | 1 | 2 | 99 | Não | Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product |
product | string | Não | Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2) |
quantity | number | Não | Number of accounts (default 1) |
Idempotency-Key | string | Sim | Header: a new UUID for every new purchase — reuse it only to retry that same purchase |
curl
Python
Node.js
# Idempotency-Key obrigatório: um UUID novo por compra — reutilize a mesma chave só para repetir após um timeout
# 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"
}
]
}
}Erros possíveis: 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).
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
page | number | Não | Page (default 1) |
pageSize | number | Não | 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
}
}Erros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | 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"
}
]
}
}Erros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | 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/Q7xK2mP9vL4tR8wN3cB6yH1dErros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | Order ID (data.id returned by the purchase) |
lineNo | number | Sim | Account number within the order (items[].lineNo) |
max | number | Não | 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
}
}Erros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | Order ID (data.id returned by the purchase) |
lineNo | number | Sim | Account number within the order (items[].lineNo) |
action | string | Sim | "regenerate" = new link (the old one stops working immediately) · "off" = turn the link off |
curl
Python
Node.js
# Link novo (o antigo para de funcionar na hora); envie {"action":"off"} para desativá-lo
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
}
}Erros possíveis: 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.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | Order ID (data.id returned by the purchase) |
lineNo | number | Sim | 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
}
}Erros possíveis: 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).
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | Order ID (data.id returned by the purchase) |
lineNo | number | Sim | 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
}
}Erros possíveis: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERRORCiclo de vida do pedido
Observações importantes:
- Consulte
GET /v1/orders/{id}a cada 3–5 segundos enquanto o pedido estiver em waiting_code para pegar o código rapidamente. - Assim que o primeiro código chega, o status muda para completed imediatamente.
- Sem código até
codeDeadlineAt(no máximo 25 minutos; armazém 2: 20 minutos; algumas fontes do armazém 4 têm menos) ⇒ o sistema cancela o pedido e reembolsa 100% (status = cancelled). - O array
otp[]é ordenado do mais antigo ao mais novo. Leia o código mais recente comotp[otp.length - 1](Python:otp[-1]). - Um pedido completed continua recebendo códigos automaticamente: continue consultando
GET /v1/orders/{id}e cada novo código aparece no fim deotp[]. Códigos extras não custam nada dentro do período do pedido quando o serviço envia mais de um; o limite e a janela de tempo aparecem antes de você alugar (maxCodesefeeemoffers[]; no armazém 1, a caixa costuma ficar aberta cerca de 25 minutos depois do último código). Para pedir outro código explicitamente (ex.: entrar depois de se cadastrar):POST /v1/orders/{id}/next-code— funciona em qualquer armazém, e só o armazém 2 fora da janela grátis cobra taxa. - Se o serviço permite apenas um código por caixa,
otp[]para nesse código. As respostas continuam normais, sem erro — você decide quando parar de consultar. otp[].codecostuma ser um código curto (dígitos / letras) ou um link. Um e-mail do qual não foi possível extrair o código (boas-vindas, newsletters, formatos de código incomuns…) é entregue na íntegra (HTML ou texto) emotp[].code— trate como e-mail completo quando tiver mais de 80 caracteres, quebra de linha ou for HTML, e procure você mesmo o código ou o link.POST /v1/ordersespera o fornecedor atribuir uma caixa, então normalmente leva ~0,3 segundo, mas pode levar até 10 segundos em horários de pico (raro). Defina um timeout de ≈ 15 segundos no cliente em vez de desistir após 5 segundos e repetir cedo demais — e, ao repetir, reutilize o mesmoIdempotency-Key.- Um pedido que já recebeu um código não pode ser cancelado nem reembolsado — a chamada de cancelamento retorna
ORDER_NOT_CANCELLABLE. - Para alugar uma caixa @icloud.com, envie
"domain": "icloud.com"ao criar o pedido (omita para Gmail; bots existentes não precisam de mudanças). Se o serviço não suporta iCloud, ou o iCloud está temporariamente desativado, você recebeDOMAIN_UNAVAILABLE— mude para Gmail e o aluguel passa na hora. O ciclo de vida, as regras de cancelamento e reembolso e a forma de ler os códigos são exatamente os mesmos do Gmail. - Serviços de estoque próprio (
provider="inventory", ex.: Cointiply — vejaGET /v1/services) são só iCloud; cada e-mail chega como um link emotp[].code, junto comkind,labele o texto completo do e-mail emtext. Um pedido recebe atérules.maxCodese-mails emrules.windowMinutesminutos (atéwindowEndsAt), não pode ser cancelado (ORDER_NOT_CANCELLABLE) e, se o tempo acabar sem nenhum e-mail, vira cancelled sem reembolso. Para continuar recebendo na mesma caixa, chamePOST /v1/orders/{id}/rerentenquantocanRerentfor true (preço:rerentPriceVnd). Todos os outros serviços / pedidos têmprovider="standard"— desde 2026-10-24providersó tem dois valores,"standard"|"inventory"(o armazém real está emwarehouse); compare apenas com"inventory". - Pedidos do armazém 2 (
warehouse= 2): os códigos são grátis porfee.freeHourshoras; quando a janela grátis fecha, novos códigos param de chegar — chamePOST /v1/orders/{id}/next-codecomconfirmFee= true (pagandonextCodeFee.feePriceVnd) para abrir outra janela, aténextCodeFee.hardUntil. Pedidos do armazém 3 de serviços comuns: códigos grátis por 24 horas, canceláveis antes de chegar um código; depois, usererent. Pedidos do armazém 4: novos códigos entram emotp[]quando você lê o pedido, grátis atérentExpiresAt; com uma fonte de código único, depois do primeiro códigonextCodeBlockReason="max_codes". - Muitos pedidos de uma vez? Consulte até 100 por chamada com
POST /v1/orders/status— veja a seção Status de vários pedidos em uma só chamada.
Limites de requisições
Os limites valem por chave de API (uma conta pode ter limites próprios; caso contrário, vale o padrão do site):
- 100 requisições/segundo — picos curtos de alguns segundos
- 3000 requisições/minuto — tráfego total por minuto
Passar de um limite retorna HTTP 429 com um cabeçalho
Retry-After: <seconds> e o corpo {"success":false,"error":{"code":"RATE_LIMITED",...}}. Espere o número de segundos indicado no cabeçalho antes de tentar de novo. O corpo do erro traz o mesmo número de segundos em error.retryAfterSec (com error.retryable = true).Códigos de erro
Todo erro tem o mesmo formato:
{"success":false,"error":{"code":"...","message":"...","retryable":false,"retryAfterSec":null}}Desde 2026-10-17 todo erro de /v1 também tem
Respostas de erro de exemploerror.retryable e error.retryAfterSec (campos adicionados — clientes existentes não precisam mudar nada; details não muda). retryable = true ⇒ a mesma chamada pode dar certo mais tarde: espere retryAfterSec segundos (um inteiro ≥ 1) e envie de novo — sem estoque / sem caixas / armazém pausado / erro do fornecedor ⇒ 5 s; 429 RATE_LIMITED ⇒ o cabeçalho Retry-After; 409 DUPLICATE_REQUEST enquanto a primeira requisição com a mesma Idempotency-Key ainda está em andamento ⇒ 2 s. retryable = false (retryAfterSec = null) ⇒ chamar de novo não vai adiantar até você resolver a causa (saldo, limite de pedidos em espera, parâmetros, chave de API…). partial.reason de POST /v1/orders segue as mesmas regras. Diante de uma resposta 409 de falta de estoque (OUT_OF_STOCK, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED), aguarde os retryAfterSec segundos inteiros antes de tentar de novo — tentar antes costuma devolver a mesma resposta — e verifique stock / offers[].stock em GET /v1/services antes de alugar novamente; acompanhe os pedidos que já tem com webhooks em vez de consultar sem parar.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
}
}Webhooks (notificações push)
Registre uma URL por conta e enviaremos a ela, via POST, um evento JSON assinado assim que um pedido receber um novo código (
Configuração em 3 passosorder.code) ou for cancelado / reembolsado (order.cancelled). Os webhooks são opcionais e cobrem todos os pedidos da sua conta (web e API). A entrega é de melhor esforço — mantenha uma consulta espaçada como reserva (veja abaixo).PUT /v1/webhookcom a sua URL (e, se quiser,events) — guardedata.secret(64 caracteres hexadecimais): ele é necessário para verificar cada entrega. Chamar PUT de novo mantém o mesmo secret.POST /v1/webhook/test— enviamos na hora um eventopinge devolvemos o que o seu servidor respondeu (ok,httpStatus,error).- Seu endpoint deve responder qualquer 2xx em até 5 segundos — responda primeiro e processe o evento depois. Qualquer outra coisa (3xx, 4xx, 5xx, tempo esgotado, erro de conexão) conta como falha e é reenviada.
A URL deve começar com
Formato da requisiçãohttp:// ou https:// (com certificado válido) e ser acessível pela internet: localhost, endereços privados, link-local, multicast e reservados (10.x, 172.16–31.x, 192.168.x, 169.254.x, fc00::/7, fe80::/10…) e os próprios servidores da OTPGmail são recusados com VALIDATION_ERROR. O nome do host é resolvido de novo antes de cada entrega. Redirecionamentos não são seguidos — registre a URL final.Cada entrega é um POST HTTP para a sua URL com corpo JSON (UTF-8) e estes cabeçalhos:
Calcule o HMAC-SHA256 com o seu secret como chave sobre
Exemplos de receptor (Python e Node usam só a biblioteca padrão; o comando curl envia ao seu próprio receptor uma entrega de teste assinada corretamente)X-OTPGmail-Timestamp + . + o corpo bruto da requisição, em hex minúsculo, e compare em tempo constante com o valor após sha256=. A chave é a própria string secret de 64 caracteres (seus bytes UTF-8 — não a decodifique como hex). Use os bytes brutos: interpretar e serializar o JSON de novo os altera. Recuse carimbos de tempo que se afastem mais de 5 minutos do seu relógio (proteção contra repetição).curl
Python
Node.js
# Teste local: envie ao seu receptor uma entrega assinada exatamente como as nossas
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
}
]
}Corpo de exemplo — 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"
}Corpo de exemplo — ping{
"event": "ping",
"deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f",
"sentAt": 1760700000
}Reenvios, pausa e histórico- Uma entrega com falha é reenviada após 10 s, 1 min, 5 min, 30 min e 2 h (5 reenvios); se o último também falhar, o evento é descartado (
failed). - 50 tentativas com falha seguidas (somando todos os eventos) pausam o webhook:
GET /v1/webhookmostrapausedAt, nada é enviado e novos eventos não entram na fila. - Para retomar: chame
PUT /v1/webhookde novo (pode ser a mesma URL) ou obtenha umPOST /v1/webhook/testbem-sucedido — os eventos que aguardam na fila são então enviados. GET /v1/webhook/deliverieslista as últimas 100 entregas com status, tentativas e último erro; o histórico fica guardado por 7 dias.
O mesmo evento pode chegar mais de uma vez (por exemplo, quando a sua resposta 2xx se perdeu) e os eventos podem chegar fora de ordem (vários são enviados em paralelo). Guarde os
Mantenha a consulta como reservadeliveryId já processados e ignore as repetições; para cada pedido, confie nos dados mais recentes (otp, status) e não na ordem de chegada.Webhooks podem atrasar (reenvios) ou se perder (webhook pausado, seu servidor fora do ar). Mantenha uma consulta espaçada — por exemplo,
GET /v1/orders/{id} a cada 30–60 s para pedidos ainda em waiting_code — e considere o objeto de pedido da API a fonte da verdade.PUT/v1/webhook
Registra ou atualiza o webhook da sua conta. A primeira chamada o cria e devolve um novo secret; as chamadas seguintes mantêm o secret, reativam o webhook e o retomam se estava pausado. Sem events: os dois eventos para um webhook novo, sem mudança para um existente. A URL precisa ser um endereço público — caso contrário, 400 VALIDATION_ERROR (error.details.problem diz o motivo).
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
url | string | Sim | Seu endpoint (http:// ou https://, endereço público, no máximo 2048 caracteres) |
events | string[] | Não | Quais eventos enviar: ["order.code", "order.cancelled"] (padrão: os dois) |
curl
Python
Node.js
# Guarde data.secret — ele é necessário para verificar cada entrega
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
}
}Erros possíveis: UNAUTHORIZED, VALIDATION_ERRORGET/v1/webhook
O webhook da sua conta, com secret, pausedAt e consecutiveFailures. data é null quando não há webhook registrado.
Exemplocurl
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
}
}Erros possíveis: UNAUTHORIZEDDELETE/v1/webhook
Remove o webhook; os eventos que ainda aguardam na fila são descartados. Sempre 200 — data.deleted é false quando não havia nada para remover.
Exemplocurl
Python
Node.js
curl -X DELETE "https://otpgmail.net/v1/webhook" \
-H "Authorization: Bearer og_xxx..."{
"success": true,
"data": {
"deleted": true
}
}Erros possíveis: UNAUTHORIZEDPOST/v1/webhook/test
Envia agora mesmo um evento ping para a sua URL e informa o resultado: ok (seu servidor respondeu 2xx), httpStatus, durationMs, error. Um teste bem-sucedido retoma um webhook pausado. No máximo 10 testes por minuto; 503 MAINTENANCE enquanto os webhooks estiverem desligados em todo o sistema.
Exemplocurl
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"
}
}Resposta 200 quando o seu servidor não responde 2xx
Erros possíveis: {
"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
Entregas mais recentes, as novas primeiro (guardadas por 7 dias): status = queued (próxima tentativa em nextAt) | delivered | failed | paused, com attempts, lastHttpStatus e lastError. O corpo dos eventos não é devolvido.
Parâmetros| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
limit | number | Não | Número de entregas, 1–100 (padrão 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
}
]
}Erros possíveis: UNAUTHORIZED