Aplicar desconto personalizadoQuem aluga OTP Gmail > 2000 Gmails por dia para qualquer serviço (exceto Facebook, Instagram e Apple) e quiser um desconto exclusivo, pode me mandar mensagem! Pode chegar a até 50% de desconto sobre o preço original para quem registra contas em volumes grandes o suficiente.
New update 29/09I have just added 4 new warehouses for all services + short-live accounts shop (24h) with email + pass for you to log in, feel free to check them out when you have time

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.net
Autenticaçã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.
Exemplo
curl "https://otpgmail.net/v1/balance" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "success": true,
  "data": {
    "balance": 25000
  }
}
Erros possíveis: UNAUTHORIZED
GET/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.
Exemplo
curl "https://otpgmail.net/v1/services" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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: UNAUTHORIZED
POST/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 — data pode ser menor que quantity, e você só é cobrado pelos pedidos em data. O stock de GET /v1/services não garante quantas uma chamada vai receber.
  • Nesse caso a resposta traz também um campo de nível superior partial ao lado de data (ausente quando tudo foi entregue — bots que só leem data continuam funcionando): requested, delivered, missing e reason = {code, message, retryable, retryAfterSec}, em que code é 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, espere reason.retryAfterSec segundos (3–5 s) e peça de novo as caixas que faltam (missing) com uma Idempotency-Key NOVA (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_STOCK ou 503 NO_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": 99 para 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.retryable e error.retryAfterSec (por exemplo, 409 OUT_OF_STOCK ⇒ true, 5; 402 INSUFFICIENT_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
NomeTipoObrigatórioDescrição
servicestringSimCódigo do serviço (de GET /v1/services, ex.: "ig", "tg", "fb")
quantitynumberNãoNú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
domainstringNãoTipo 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.
warehousenumberNãoArmazé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.
Exemplo
# 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"}'
Resposta de exemplo
{
  "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)
{
  "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
    }
  }
}
Erros possíveis: UNAUTHORIZED, VALIDATION_ERROR, SERVICE_DISABLED, INSUFFICIENT_BALANCE, OUT_OF_STOCK, DOMAIN_UNAVAILABLE, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED, NO_MAILS_AVAILABLE, QUANTITY_EXCEEDED, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCE
GET/v1/orders/{id}
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
NomeTipoObrigatórioDescrição
idstringSimID do pedido (o orderId devolvido quando o pedido foi criado)
Exemplo
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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_FOUND
POST/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
NomeTipoObrigatórioDescrição
idstringSimID do pedido a cancelar
Exemplo
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json"
Resposta de exemplo
{
  "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_ERROR
POST/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 canRerent = 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ê.
Parâmetros
NomeTipoObrigatórioDescrição
idstringSimID do pedido anterior da caixa (o que tem canRerent = true)
Exemplo
# 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)"
Resposta de exemplo
{
  "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, MAINTENANCE
POST/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 com nextCodeBlockReason.
  • 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 enviar confirmFee = true (sem isso ⇒ 409 NEXT_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 — use POST /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
NomeTipoObrigatórioDescrição
idstringSimID do pedido (orderId) — qualquer armazém
confirmFeebooleanNãotrue = você concorda em pagar nextCodeFee.feePriceVnd para abrir outra janela de código. Desnecessário enquanto feePriceVnd = 0.
refreshbooleanNãotrue = 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.
Exemplo
# 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}'
Resposta de exemplo
{
  "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, MAINTENANCE

Status 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 (siga nextCursor) e volte a consultar com POST /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
NomeTipoObrigatórioDescrição
idsstring[]SimIDs dos pedidos (orderId) — de 1 a 100 por chamada
Exemplo
# 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"]}'
Resposta de exemplo
{
  "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_LIMITED
GET/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
NomeTipoObrigatórioDescrição
statusstringNãoSó este status: waiting_code | completed | cancelled | queued (omitido ⇒ todos)
sincenumberNãoSó pedidos criados neste horário unix (segundos) ou depois
limitnumberNãoPedidos por página: 1–200 (padrão 50)
cursorstringNãonextCursor da página anterior, sem alterar
Exemplo
# 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..."
Resposta de exemplo
{
  "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_LIMITED

Armazé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)
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 em offers[]) 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 o warehouse e o price do 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.
Exemplo: alugar Gmail do Facebook com warehouse = 99
# 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}'
Campos de offers[]
CampoTipoDescrição
warehouse1 | 2 | 3 | 4Número do armazém: 1, 2, 3 ou 4.
domainstringTipo de caixa da oferta: gmail.com, icloud.com ou gmx.com (domínio extra — só alguns serviços, atualmente o Pokemon).
pricenumberPreço de uma caixa deste armazém (VND).
stocknumberCaixas restantes. 0 = sem estoque — alugar do armazém 1 ou 2 retorna WAREHOUSE_UNAVAILABLE; o armazém 4 ainda é tentado (suas fontes costumam entregar mesmo informando 0) e retorna OUT_OF_STOCK se nada estiver disponível.
maxCodesnumber | nullMáximo de códigos que uma caixa pode receber; null = ilimitado / desconhecido (veja fee).
rulesobject | nullRegras de janela do armazém 3 (windowMinutes, maxCodes, cancellable, refundOnTimeout, rerentRatio); null nos armazéns 1, 2 e 4.
feeobject | nullJanela de códigos extras: freeHours horas grátis a partir do aluguel; depois, cada prorrogação custa paidPct% do preço do aluguel, até maxHours horas a partir do aluguel (null = sem limite). fee = null: sem taxa — os códigos extras não custam nada dentro do período do pedido quando o serviço envia mais de um (até maxCodes), como no armazém 1.
isDefaultbooleantrue para o armazém mais barato com estoque (empate ⇒ menor número de armazém) — o usado com warehouse = 0 e o primeiro tentado com 99.
Regras por armazém
RegraArmazém 1Armazém 2Armazém 3Armazém 4
Espera pelo primeiro código25 minutos20 minutos25 minutos ¹Até 25 minutos (menos em algumas fontes — o prazo real é o codeDeadlineAt do pedido)
Sem códigoCancelado automaticamente, reembolso de 100%Cancelado automaticamente, reembolso de 100%Cancelado automaticamente, reembolso de 100% ¹Cancelado automaticamente, reembolso de 100%
Cancelar antes de um códigoSimSimSim ¹Sim
Códigos por caixaConforme maxCodesIlimitadoIlimitado ¹Conforme maxCodes (normalmente null = sem limite; 1 em algumas ofertas)
Mais códigosSem custo extra dentro do período de 24 horas do pedido se o serviço enviar mais códigos; automático (sem chamada extra). A caixa costuma ficar aberta cerca de 25 minutos depois do último código.Grátis por fee.freeHours horas (hoje Gmail 6 horas, iCloud 12 horas); depois, cada prorrogação custa fee.paidPct% do preço via next-code, até fee.maxHours (Gmail 24 horas, iCloud 10 dias)Sem custo extra por 24 horas; depois, alugue a mesma caixa de novo (rerent) por 50% do preçoSem custo extra dentro do período de 24 horas do pedido, como no armazém 1 — os novos códigos aparecem ao ler o pedido; next-code relê o pedido na hora.
¹ Serviços vendidos apenas do estoque próprio, como o Cointiply (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.
nextCodeFee — taxa por mais códigos (pedidos dos armazéns 2 e 3)
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).
CampoTipoDescrição
feePriceVndnumberValor cobrado se você chamar next-code agora; 0 = ainda dentro da janela grátis.
freeUntilnumber | nullFim da janela grátis atual (segundos unix).
hardUntilnumber | nullLimite rígido (segundos unix) — nenhum código a mais depois desse horário.
paidPctnumber% do preço do aluguel cobrado por cada janela extra.
freeHoursnumberHoras grátis de uma janela de código (armazém 2: Gmail 6, iCloud 12; armazém 3: 24).
Exemplo: listar os armazéns e depois alugar de um deles
# 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}'
Exemplo de pedido do armazém 2 (GET /v1/orders/{id})
{
  "success": true,
  "data": {
    "orderId": "ghi789rst",
    "service": "dr",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "completed",
    "price": 350,
    "otp": [
      {
        "code": "482913",
        "receivedAt": 1753268300
      }
    ],
    "codeDeadlineAt": 1753269700,
    "rentExpiresAt": 1753354200,
    "createdAt": 1753267800,
    "warehouse": 2,
    "nextCodeFee": {
      "feePriceVnd": 175,
      "freeUntil": 1753289400,
      "hardUntil": 1753354200,
      "paidPct": 50,
      "freeHours": 6
    },
    "listening": false,
    "listenUntil": null,
    "nextCodeBlocked": false,
    "nextCodeBlockReason": null
  }
}

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 — um GET retorna {"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 abra readUrl (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 code com isRaw: true (HTML ou texto, até 20.000 caracteres).
Campos de uma caixa em um lote (mails[])
CampoTipoDescrição
idnumberID da caixa — o parâmetro {mailId} do endpoint de código
emailstringEndereço de e-mail
urlstringArmazém 1: = proxyUrl · armazém 3: = readUrl (mantido para scripts / arquivos de exportação antigos)
proxyUrlstring | nullArmazém 1: link de código que não exige login · armazém 3: null
readUrlstringPágina web para ler os códigos do lote
codeCountnumberNúmero de códigos recebidos
lastCodestring | nullCódigo mais recente (e-mail bruto: primeiros 300 caracteres — o texto completo está no endpoint de código)
lastIsRawbooleantrue = o código mais recente é um e-mail bruto
checkedAtnumber | nullÚltima vez que o sistema consultou o fornecedor sobre esta caixa (segundos unix; null = nunca)
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
NomeTipoObrigatórioDescrição
pagenumberNãoPágina (padrão 1)
pageSizenumberNãoLotes por página (padrão 20, máx. 100)
Exemplo
curl "https://otpgmail.net/v1/batches?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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: UNAUTHORIZED
GET/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
NomeTipoObrigatórioDescrição
idstringSimID do lote (exibido na página Aluguel em lote e em readUrl)
Exemplo
curl "https://otpgmail.net/v1/batches/BATCH_ID" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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_FOUND
GET/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
NomeTipoObrigatórioDescrição
idstringSimID do lote
mailIdnumberSimID da caixa (mails[].id)
poll0 | 1Não0 = só lê os códigos armazenados, sem consultar o fornecedor (padrão 1)
Exemplo
# 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..."
Resposta de exemplo
{
  "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_LIMITED

Buy live Gmail accounts (2 warehouses)

Buy short-lived Gmail accounts outright: you are charged when you order and the accounts arrive within seconds. There are two mail-shop warehouses (numbered separately from the OTP-rental warehouses 1–4), each with ONE price for any quantity. Prices returned by the API are YOUR account's prices (custom price if the admin set one) — exactly what you are charged.
Warehouses
WarehouseEach account deliversDefault priceStock
1gm_cookieemail · password · cookies · inboxUrl (inbox link) · inboxJsonUrl (emails as JSON) · twoFaUrl (2FA link — just the 6 digits)3,300 VNDA number — published by the source, refreshed every minute. The source checks that accounts are live at purchase time, so you may get fewer — the shortfall is refunded (a partial order; warehouse 99: the rest goes to the next warehouse)
2gm_loginemail · password · loginUrl (quick login link — open it in a desktop browser) · otpUrl (Vinted OTP link — when delivers lists it)6,000 VNDA REFERENCE number from a source sharing the same stock (refreshed every minute; null when it cannot be read) — may be off by a few, never blocks a purchase; a shortfall becomes a partial order with the rest refunded
99As the warehouse that supplied each account (items[].warehouse)Price of the cheapest warehouse on sale (auto.priceVnd)Buys the cheapest warehouse first (auto.order); if it runs out midway the rest goes to the next warehouse at that warehouse's price (the difference is charged — refunded instead if your balance is short); every warehouse out ⇒ MAILSHOP_OUT_OF_STOCK, nothing charged
Entrar nas contas do armazém 1 (sem e-mail de recuperação)
  • 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 pelo inboxJsonUrl (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) traz email|password|cookies|inboxUrl em 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.
3 steps
  1. See warehouses & prices: GET /v1/mail-shop/products ⇒ products[] (per warehouse: priceVnd, stock, available, maxQty) + auto (warehouse 99: order, priceVnd). To see the total first, call GET /v1/mail-shop/quote.
  2. Buy: POST /v1/mail-shop/orders with {"warehouse": 1 | 2 | 99, "quantity": N} + header Idempotency-Key (a new UUID per purchase). The response is the order, usually already completed with items[].
  3. Get the accounts: read items[] (table below) or download the TXT GET /v1/mail-shop/orders/{id}/download. status = pending ⇒ poll GET /v1/mail-shop/orders/{id} every 5 seconds until completed / partial / failed.
# 1) warehouses, your prices, stock
curl "https://otpgmail.net/v1/mail-shop/products" -H "Authorization: Bearer og_xxx..."

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

# 3) status "pending"? read the order again (or download the .txt)
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" -H "Authorization: Bearer og_xxx..."
Fields of an account (items[])
CampoTipoDescrição
lineNonumberPosition in the order (1, 2, …) — the {lineNo} of the inbox endpoints
warehouse1 | 2Warehouse that supplied this account
emailstringGmail address
passwordstringPassword
recoverystring?Recovery email — only when the source provides one; warehouse 1 (gm_cookie) accounts have NONE (sign in with the cookies, see the sign-in notes above)
cookiesstring | nullWarehouse 1: login cookies (JSON)
inboxUrlstring | nullWarehouse 1: inbox page without an API key (https://otpgmail.net/mail/{code}); null = link turned off
inboxJsonUrlstring | nullWarehouse 1: the same link + ?json ⇒ emails as JSON (section below)
twoFaUrlstring | nullWarehouse 1: the 2FA LINK (https://otpgmail.net/2fa/{code} — same code + expiry as inboxUrl): GET / open it in a browser ⇒ JUST the 6-digit 2FA code (section below). null = link turned off. The .txt file has this column when downloaded with ?twofa=1
inboxLinkExpiresAtnumber | nullWarehouse 1: when the link expires (unix seconds); null = no limit
loginUrlstring | nullWarehouse 2: quick login link
otpUrlstring | nullWarehouse 2: the account's Vinted OTP link — open it in a browser to see the code, ?json ⇒ JSON (section below). null = this account has no link
otpService"vinted" | nullWarehouse 2: the service of otpUrl
rawLinestringThe whole account on one line (cookies compacted) — copy it as is
Read the inbox as JSON (inboxJsonUrl)
GET {inboxJsonUrl} (= inboxUrl + ?json) returns the same JSON as GET /api/mailbox/{code}: email, letters[] (≤ max latest emails: from, subject, receivedAt, code — extracted code / link, content — sanitized HTML), total, etag, nextAt. No API key needed (the code in the link is the key — keep it private), CORS is open. Add &max=20|50|100|200 for more emails (default 10), &etag={previous etag} ⇒ unchanged emails come back as empty letters + unchanged: true. Reading one link faster than every 10 seconds ⇒ 429 (retryAfter); link off / wrong / expired ⇒ 404; account locked ⇒ 410.
curl "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json&max=20"
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "letters": [
      {
        "key": "h:9f2c4e1a7b3d5c80",
        "from": "Instagram [email protected]",
        "subject": "350038 is your Instagram code",
        "receivedAt": 1760000520,
        "code": "350038",
        "content": "<html><body><p>Enter this confirmation code in the app: 350038</p></body></html>"
      }
    ],
    "max": 10,
    "total": 8,
    "fetchedAt": 1760000530,
    "cached": false,
    "etag": "f1c2a9d4e0b37a65",
    "unchanged": false,
    "nextAt": 1760000540,
    "expiresAt": 1762592400,
    "serverTime": 1760000530
  }
}
Read the Vinted OTP code (otpUrl)
A warehouse-2 account with otpUrl ⇒ open it in a browser to see the latest Vinted code (the page refreshes itself); GET {otpUrl}?json returns {status, code, service, email, checkedAt, retryAfter}: status = waiting (no code yet — ask again after retryAfter seconds) · ok (code = the latest code) · dead (codes can no longer be read for this account) · error (temporarily unreadable — ask again). No API key needed (the code in the link is the key — keep it private), CORS is open; the server reads the source at most every few seconds per account; more than 120 calls / minute / IP ⇒ 429.
curl "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c?json"
{
  "success": true,
  "data": {
    "status": "ok",
    "code": "4829",
    "service": "vinted",
    "email": "[email protected]",
    "checkedAt": 1760000530,
    "retryAfter": 5
  }
}
The 2FA code (twoFaUrl)
Some warehouse-1 accounts have 2-step verification (2FA) on — a password sign-in makes Google ask for a 6-digit code. GET {twoFaUrl} (or open it in a browser) returns text/plain with JUST the current 6 digits (no newline), a Refresh header = seconds until the next code + 1 (browsers reload by themselves), Cache-Control: no-store, open CORS — you can hand the link to your customers. Account without 2FA ⇒ 404 no 2fa; wrong / off / expired link ⇒ 404 not found; locked account ⇒ 410 gone; too fast / source hiccup ⇒ 429 / 502 retry (Retry-After header). With an API key: GET /v1/mail-shop/orders/{id}/items/{lineNo}/2fa ⇒ {none, code, expiresIn}. The server caches the code until it expires. The .txt file (GET /v1/mail-shop/orders/{id}/download): add ?twofa=1 ⇒ a last twoFaUrl column (off by default — the old layout is unchanged).
curl "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d"
# 424819
Order statuses
StatusDescrição
pendingBeing delivered — accounts that arrived are in items[]; poll again in 5 seconds
completedFully delivered. Final.
partialDelivered short — the shortfall was refunded (refundVnd; message = the reason + the source's own words when it gave any). Final.
failedNothing delivered — fully refunded (message as above). Final.
refundedRefunded by an admin (refundVnd). Final.
Rules
  • Money: chargedVnd = Σ route[].quantity × unitPriceVnd (a 99 order split across warehouses ⇒ two legs in route). Shortfalls are refunded automatically (refundVnd). Delivered accounts are not refunded — the only warranty is the warehouse's terms (read them first).
  • Idempotency-Key is required (400 without it): retrying with the SAME key after a timeout returns the same order and charges nothing more; a new key buys again. Use a client timeout of at least 60 seconds.
  • Limits: at most 500 accounts per order (and the warehouse's maxQty), 10 purchases per minute for your account (web + API share it), 180 inbox reads per hour. Above ⇒ 429 RATE_LIMITED.
  • Common errors: MAILSHOP_OUT_OF_STOCK 409 (details.stock = how many you can buy now) · MAILSHOP_QTY_LIMIT 400 (details.max) · INSUFFICIENT_BALANCE 402 · MAILSHOP_DISABLED 503 (warehouse / section paused) · MAILSHOP_SUPPLIER_ERROR 502 (the source refused at once — a failed order was recorded and refunded, details.orderId) · VALIDATION_ERROR 400 (warehouse not 1 | 2 | 99, warehouse and product disagree, missing Idempotency-Key…).
  • Terms (warranty only for the first sign-in within 2 hours, accounts last ~6–24 hours) are in each warehouse's terms; the API does not ask you to accept them (the web asks once). API orders show up in your web history (channel = api) and vice versa.
GET/v1/mail-shop/products
The two mail-shop warehouses + automatic warehouse 99. products[]: warehouse (1 | 2), code (gm_cookie | gm_login), delivers (fields of every account), priceVnd (YOUR price), listPriceVnd (list price), stock, available, maxQty (most one order can take now). auto: order (the order warehouse 99 buys in), priceVnd (cheapest warehouse), available.
Warehouse 1 stock = what the source publishes (refreshed every minute; the source checks accounts are live at purchase time, so you may get fewer — the shortfall is refunded). Warehouse 2 = a reference number from a source sharing the same stock, may be off by a few (null = could not be read — just buy); a shortfall becomes a partial order with the rest refunded. available = false ⇒ do not buy from that warehouse (out of stock / paused). Prices can change — read the catalog again before a large purchase.
Exemplo
curl "https://otpgmail.net/v1/mail-shop/products" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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: UNAUTHORIZED
GET/v1/mail-shop/quote
Call quote to see the total first (nothing is reserved). Warehouse 99 ⇒ route[] = planned split by known stock; available = false ⇒ that quantity cannot be bought right now.
Parâmetros
NomeTipoObrigatórioDescrição
warehouse1 | 2 | 99NãoMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringNãoInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberNãoNumber of accounts (default 1)
Exemplo
curl "https://otpgmail.net/v1/mail-shop/quote?warehouse=99&quantity=10" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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_FOUND
POST/v1/mail-shop/orders
Buy: you are charged at once, then the system buys from the source and delivers. The response is the order — usually already completed with items[]; status = pending ⇒ poll GET /v1/mail-shop/orders/{id} every 5 seconds. The call waits up to about 16 seconds for the delivery.
Idempotency-Key is required (400 without it). Not enough stock ⇒ MAILSHOP_OUT_OF_STOCK (error.details.stock = how many you can buy now, nothing charged); over the per-order limit ⇒ MAILSHOP_QTY_LIMIT (error.details.max). 502 MAILSHOP_SUPPLIER_ERROR = the source refused at once: a failed order was recorded and fully refunded (error.details.orderId) — retry with a new key.
Parâmetros
NomeTipoObrigatórioDescrição
warehouse1 | 2 | 99NãoMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringNãoInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberNãoNumber of accounts (default 1)
Idempotency-KeystringSimHeader: a new UUID for every new purchase — reuse it only to retry that same purchase
Exemplo
# 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}'
Resposta de exemplo
{
  "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, MAINTENANCE
GET/v1/mail-shop/orders
Your mail-shop orders, newest first (page / pageSize, up to 100 per page) — without items[]: fetch an order to get its accounts. Orders bought on the web are listed too (channel = web).
Parâmetros
NomeTipoObrigatórioDescrição
pagenumberNãoPage (default 1)
pageSizenumberNãoOrders per page (default 20, max 100)
Exemplo
curl "https://otpgmail.net/v1/mail-shop/orders?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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: UNAUTHORIZED
GET/v1/mail-shop/orders/{id}
One order with every account delivered so far (an order still delivering already shows what has arrived). route[] = which warehouse, how many, at what price.
Poll every 5 seconds while status = pending; completed / partial / failed / refunded are final. An order of many warehouse-1 accounts (cookies) can weigh several MB — stop polling once it is final.
Parâmetros
NomeTipoObrigatórioDescrição
idstringSimOrder ID (data.id returned by the purchase)
Exemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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_FOUND
GET/v1/mail-shop/orders/{id}/download
The delivered accounts as .txt (text/plain), separated by |. The FIRST LINE is the column header: warehouse 1 email|password|cookies|inboxUrl · warehouse 2 email|password|loginUrl · a 99 order with both warehouses ⇒ email|password|cookies|inboxUrl|loginUrl (missing cells empty).
cookies is one-line JSON and may contain | — split email and password at the first two |, the link at the last | (with extra columns ⇒ the links after cookies at the last | separators — links never contain |; read the header line for their order). Nothing delivered yet ⇒ 409 VALIDATION_ERROR. Add <c>?otp=1</c> ⇒ warehouse 2 gets a LAST column <c>otpUrl</c> (the Vinted OTP link — empty when the account has none): email|password|loginUrl|otpUrl. Without it ⇒ exactly the columns above. Add <c>?twofa=1</c> ⇒ warehouse 1 gets a LAST column <c>twoFaUrl</c> (the 2FA link — same code as inboxUrl; GET ⇒ just the 6 digits; empty when the link is off / expired): email|password|cookies|inboxUrl|twoFaUrl. Combines with <c>otp=1</c> — fixed column order: …|loginUrl|otpUrl|twoFaUrl (a 99 order with both warehouses). Without it ⇒ the default layout above.
Parâmetros
NomeTipoObrigatórioDescrição
idstringSimOrder ID (data.id returned by the purchase)
Exemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
  -H "Authorization: Bearer og_xxx..." \
  -o mail-ORDER_ID.txt
Resposta de exemplo
email|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d
Erros possíveis: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR
GET/v1/mail-shop/orders/{id}/items/{lineNo}/inbox
Latest emails of a WAREHOUSE-1 account (sender, subject, time, extracted code / link, sanitized full email). Without an API key: GET the account's inboxJsonUrl (same emails, JSON).
max = 10 (default) | 20 | 50 | 100 | 200 — more emails take longer (up to about a minute). Cached for 15 seconds; every real read counts toward the hourly limit. 409 VALIDATION_ERROR = a warehouse-2 account (no inbox reading); 409 PROVIDER_ERROR with details.dead = the account was locked or removed.
Parâmetros
NomeTipoObrigatórioDescrição
idstringSimOrder ID (data.id returned by the purchase)
lineNonumberSimAccount number within the order (items[].lineNo)
maxnumberNãoNumber of latest emails: 10 (default), 20, 50, 100 or 200
Exemplo
# 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"
Resposta de exemplo
{
  "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_ERROR
GET/v1/mail-shop/orders/{id}/items/{lineNo}/status
Is a bought warehouse-1 account still alive: live = true / false, or null when it cannot be determined. Counts toward the hourly inbox-read limit.
Parâmetros
NomeTipoObrigatórioDescrição
idstringSimOrder ID (data.id returned by the purchase)
lineNonumberSimAccount number within the order (items[].lineNo)
Exemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/status" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "live": true,
    "reason": null,
    "checkedAt": 1760000600
  }
}
Erros possíveis: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR
GET/v1/mail-shop/orders/{id}/items/{lineNo}/2fa
The current 2FA code (6-digit TOTP, changes every 30 seconds) of a warehouse-1 account — some warehouse-1 accounts have 2-step verification on (Google asks for a code at a password sign-in). none = true ⇒ the account has no 2FA (code / expiresIn null). expiresIn = seconds the code stays valid — ask again after that. Without an API key: GET the account's twoFaUrl (the body is just the 6 digits).
The server caches the code until it expires (every call within those 30 seconds gets the same code) — it does NOT count toward the hourly inbox-read limit. Calling too fast ⇒ 429 (retryAfterSec); a warehouse-2 account ⇒ 409; a locked account ⇒ 409 (details.dead).
Parâmetros
NomeTipoObrigatórioDescrição
idstringSimOrder ID (data.id returned by the purchase)
lineNonumberSimAccount number within the order (items[].lineNo)
Exemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/2fa" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "success": true,
  "data": {
    "none": false,
    "code": "424819",
    "expiresIn": 24
  }
}
Erros possíveis: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR

Ciclo de vida do pedido

StatusDescrição
Aguardando códigoPedido recém-criado, aguardando o primeiro código. Consulte GET /v1/orders/{id} a cada 3–5 segundos.
ConcluídoO primeiro código chegou — o status muda na hora. Ainda podem chegar mais códigos se o serviço enviar mais de um (sem custo extra dentro do período do pedido; limite e janela por armazém); continue consultando e cada novo código aparece no fim de otp[].
CanceladoReembolso de 100%. Acontece quando você chama /cancel (antes de chegar um código) ou quando nenhum código chega até codeDeadlineAt (no máximo 25 minutos).
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 com otp[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 de otp[]. 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 (maxCodes e fee em offers[]; 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[].code costuma 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) em otp[].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/orders espera 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 mesmo Idempotency-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ê recebe DOMAIN_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 — veja GET /v1/services) são só iCloud; cada e-mail chega como um link em otp[].code, junto com kind, label e o texto completo do e-mail em text. Um pedido recebe até rules.maxCodes e-mails em rules.windowMinutes minutos (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, chame POST /v1/orders/{id}/rerent enquanto canRerent for true (preço: rerentPriceVnd). Todos os outros serviços / pedidos têm provider = "standard" — desde 2026-10-24 provider só tem dois valores, "standard" | "inventory" (o armazém real está em warehouse); compare apenas com "inventory".
  • Pedidos do armazém 2 (warehouse = 2): os códigos são grátis por fee.freeHours horas; quando a janela grátis fecha, novos códigos param de chegar — chame POST /v1/orders/{id}/next-code com confirmFee = true (pagando nextCodeFee.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, use rerent. Pedidos do armazém 4: novos códigos entram em otp[] quando você lê o pedido, grátis até rentExpiresAt; com uma fonte de código único, depois do primeiro código nextCodeBlockReason = "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 error.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.
Respostas de erro de exemplo
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
  }
}
Código de erroHTTPretryable · retryAfterSecSignificado
UNAUTHORIZED401false · nullA chave de API é inválida ou está ausente.
ACCOUNT_LOCKED403false · nullA conta está bloqueada — toda requisição feita com a chave dela é recusada. Fale com o suporte.
FORBIDDEN403false · nullA conta deixou de estar ativa enquanto o aluguel era processado (acabou de ser bloqueada).
NOT_FOUND404false · nullNão há pedido / lote / caixa com este ID na sua conta (pedidos de outra conta também retornam 404).
VALIDATION_ERROR400false · nullOs parâmetros da requisição são inválidos (tipo errado, campo obrigatório ausente etc.).
INSUFFICIENT_BALANCE402false · nullO saldo da conta é insuficiente para pagar o pedido.
SERVICE_DISABLED409false · nullO código do serviço não existe ou o serviço está desativado temporariamente. Pegue o código exato em GET /v1/services.
OUT_OF_STOCK409true · 5O serviço não tem caixas disponíveis (para o tipo de caixa escolhido) no momento da requisição.
DOMAIN_UNAVAILABLE409false · nullO serviço não oferece o tipo de caixa escolhido — o iCloud ainda não é suportado / está temporariamente desativado, ou o serviço não tem caixas gmx.com. Envie domain "gmail.com" (ou omita o campo) para alugar Gmail.
WAREHOUSE_UNAVAILABLE409true · 5 / falseO armazém escolhido não vende este serviço/tipo de caixa, está pausado ou acabou de esgotar. Escolha outro armazém em offers[] ou envie warehouse = 99 para o sistema escolher um com estoque. Se esse armazém (ou todos os armazéns que vendem este serviço) não estiver disponível para a sua conta, retryable = false — chamar de novo não ajuda, use outro armazém.
ALL_WAREHOUSES_EXHAUSTED409true · 5Somente com warehouse = 99: todos os armazéns com estoque foram tentados (do mais barato ao mais caro) e todos estavam sem estoque ou falhando — ou todos os armazéns informam estoque zero. Nada foi cobrado; tente de novo mais tarde.
NO_MAILS_AVAILABLE503true · 5O fornecedor não tem mais caixas para este serviço (esgotado temporariamente).
QUANTITY_EXCEEDED400false · nullquantity excede o máximo por requisição (hoje 200).
WAITING_LIMIT_REACHED429false · nullHá pedidos demais esperando código ao mesmo tempo (passou do limite de pedidos waiting_code simultâneos da sua conta) — espere alguns terminarem e alugue de novo.
ORDER_NOT_CANCELLABLE409false · nullO pedido não pode ser cancelado — pode já ter um código, ou não estar mais no status waiting_code. next-code também retorna este código quando o pedido ainda não recebeu o primeiro código.
RERENT_NOT_AVAILABLE409false · nullO pedido não pode ser alugado de novo. Armazém 3: não é o pedido mais recente da caixa, a caixa não está mais atribuída à sua conta ou a janela de recebimento anterior ainda está aberta. Armazém 2: o pedido ainda não foi fechado (ainda recebe códigos — use next-code), é antigo demais para alugar de novo, o e-mail tem outro pedido ativo ou o serviço está pausado. Os armazéns 1 e 4 não têm essa opção.
RERENT_EMAIL_BANNED409false · nullAlugar novamente no armazém 2: o fornecedor informa que o serviço bloqueou o e-mail (email banned) — não é possível alugar de novo; nada é cobrado.
RERENT_MAIL_GONE409false · nullAlugar novamente no armazém 2: o fornecedor não consegue mais reabrir este e-mail — alugue outro; nada é cobrado.
NEXT_CODE_FEE_CONFIRM409false · nullA janela grátis do pedido do armazém 2 terminou — chame next-code de novo com confirmFee: true para pagar a taxa (nextCodeFee.feePriceVnd). Nada foi cobrado.
EMAIL_BANNED409false · nullO e-mail foi bloqueado pelo serviço (o fornecedor informa email banned) — não pode receber mais códigos; alugue outro. Desde 2026-10-02 (antes era ORDER_EXPIRED, mesmo HTTP 409).
ORDER_EXPIRED409false · nullO pedido não pode receber mais códigos — o prazo acabou (rentExpiresAt / nextCodeFee.hardUntil), o fornecedor fechou a caixa, o limite de códigos (maxCodes) foi atingido ou o serviço não tem "próximo código". O motivo está em order.nextCodeBlockReason.
RATE_LIMITED429true · = Retry-AfterMais de 100 req/segundo ou 3000 req/minuto na sua chave de API (uma conta pode ter limites próprios). O cabeçalho Retry-After informa quantos segundos esperar. No armazém 2, POST /v1/orders e /rerent também retornam este código quando a conta cancelou muitos pedidos do armazém 2 sem código nos últimos 60 minutos e muito poucos receberam código — o aluguel no armazém 2 fica pausado até a taxa de cancelamento cair ("warehouse": 99 também para ali com este erro — enquanto isso, escolha outro armazém). Facebook / Instagram (fb, fbb, ig) estão isentos.
DUPLICATE_REQUEST409true · 2 / falseO Idempotency-Key já foi usado com outro conteúdo ou outro endpoint, ou a primeira requisição com essa chave ainda está em andamento (tente de novo em instantes). Gere uma chave nova para criar outro pedido.
PROVIDER_ERROR502true · 5Erro do fornecedor de caixas (temporário — tente de novo após alguns segundos).
MAINTENANCE503false · nullO sistema está em manutenção — a criação e o cancelamento de pedidos estão pausados.
MAILSHOP_DISABLED503false · nullA compra de Gmail temporário está pausada, ou este produto está desativado — tente de novo mais tarde.
MAILSHOP_PRODUCT_NOT_FOUND404false · nullNenhum produto com este código — pegue o código exato em GET /v1/mail-shop/products.
MAILSHOP_OUT_OF_STOCK409false · nullEstoque insuficiente para esta quantidade no preço atual — error.details.stock = quantas contas você pode comprar agora.
MAILSHOP_QTY_LIMIT400false · nullquantity excede o máximo por pedido (hoje 500) — error.details.max.
MAILSHOP_SUPPLIER_ERROR502false · nullA fonte recusou a compra na hora: um pedido failed foi registrado e o valor foi reembolsado (error.details.orderId). Tente de novo mais tarde com um Idempotency-Key novo.
INTERNAL500false · nullErro inesperado no servidor — tente mais tarde; se continuar, fale com o suporte.

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 (order.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).
Configuração em 3 passos
  1. PUT /v1/webhook com a sua URL (e, se quiser, events) — guarde data.secret (64 caracteres hexadecimais): ele é necessário para verificar cada entrega. Chamar PUT de novo mantém o mesmo secret.
  2. POST /v1/webhook/test — enviamos na hora um evento ping e devolvemos o que o seu servidor respondeu (ok, httpStatus, error).
  3. 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 http:// 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.
Formato da requisição
Cada entrega é um POST HTTP para a sua URL com corpo JSON (UTF-8) e estes cabeçalhos:
CabeçalhoValor
Content-Typeapplication/json
User-AgentOTPGmail-Webhook/1
X-OTPGmail-EventNome do evento: order.code, order.cancelled ou ping
X-OTPGmail-DeliveryID único do evento (UUID) — igual a deliveryId no corpo e o mesmo em todos os reenvios
X-OTPGmail-TimestampHora Unix (segundos) em que esta tentativa foi assinada — igual a sentAt no corpo
X-OTPGmail-Signaturesha256= + hex minúsculo do HMAC-SHA256 de timestamp + "." + corpo bruto, com o seu secret como chave
Verificar a assinatura
Calcule o HMAC-SHA256 com o seu secret como chave sobre 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).
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)
# 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"
Eventos
EventoEnviado quando
order.codeUm pedido recebeu um novo código — um evento por código, incluindo os códigos seguintes na mesma caixa de entrada. code é o novo código; otp lista todos os códigos do pedido até aquele momento, do mais antigo ao mais novo (como em GET /v1/orders/{id}; só o novo código se o corpo passar de 64 KB).
order.cancelledUm pedido foi cancelado ou reembolsado — uma vez por pedido: nenhum código chegou antes do prazo (timeout), cancelado por você (user) ou por um administrador (admin), ou reembolsado porque o armazém falhou (provider). refundVnd = VND devolvidos ao seu saldo (0 quando as regras do serviço não reembolsam).
pingSomente a partir de POST /v1/webhook/test.
Campos do evento
CampoTipoDescrição
eventstringNome do evento (também no cabeçalho X-OTPGmail-Event)
deliveryIdstringID único do evento (UUID, = X-OTPGmail-Delivery) — guarde-o para ignorar duplicatas
sentAtnumberHora Unix desta tentativa (= X-OTPGmail-Timestamp)
orderIdstringID do pedido — o mesmo de GET /v1/orders/{id}
servicestringCódigo do serviço
warehouse1 | 2 | 3 | 4 | nullArmazém 1–4 que forneceu a caixa de entrada; null se o pedido nunca recebeu uma
domainstringDomínio da caixa: gmail.com, icloud.com ou gmx.com
emailstring | nullEndereço da caixa de entrada alugada
codestringSó order.code: o novo código — também pode ser um link ou um e-mail completo, como otp[].code
receivedAtnumberSó order.code: hora Unix em que o código chegou
statusstringStatus do pedido naquele momento: completed para order.code, cancelled para order.cancelled (quando um administrador reembolsa um pedido que já tinha códigos, ele continua completed)
otparraySó order.code: todos os códigos até aquele momento, [{code, receivedAt}], do mais antigo ao mais novo
reasonstringSó order.cancelled: timeout | user | admin | provider
refundVndnumberSó order.cancelled: VND reembolsados ao seu saldo (0 = sem reembolso)
Corpo de exemplo — order.code
{
  "event": "order.code",
  "deliveryId": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8",
  "sentAt": 1760700125,
  "orderId": "k3m9x2p7q1w8e5r4",
  "service": "fb",
  "warehouse": 1,
  "domain": "gmail.com",
  "email": "[email protected]",
  "code": "482913",
  "receivedAt": 1760700124,
  "status": "completed",
  "otp": [
    {
      "code": "482913",
      "receivedAt": 1760700124
    }
  ]
}
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/webhook mostra pausedAt, nada é enviado e novos eventos não entram na fila.
  • Para retomar: chame PUT /v1/webhook de novo (pode ser a mesma URL) ou obtenha um POST /v1/webhook/test bem-sucedido — os eventos que aguardam na fila são então enviados.
  • GET /v1/webhook/deliveries lista as últimas 100 entregas com status, tentativas e último erro; o histórico fica guardado por 7 dias.
Duplicatas e ordem
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 deliveryId já processados e ignore as repetições; para cada pedido, confie nos dados mais recentes (otp, status) e não na ordem de chegada.
Mantenha a consulta como reserva
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
NomeTipoObrigatórioDescrição
urlstringSimSeu endpoint (http:// ou https://, endereço público, no máximo 2048 caracteres)
eventsstring[]NãoQuais eventos enviar: ["order.code", "order.cancelled"] (padrão: os dois)
Exemplo
# 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"]}'
Resposta de exemplo
{
  "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_ERROR
GET/v1/webhook
O webhook da sua conta, com secret, pausedAt e consecutiveFailures. data é null quando não há webhook registrado.
Exemplo
curl "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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: UNAUTHORIZED
DELETE/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.
Exemplo
curl -X DELETE "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "success": true,
  "data": {
    "deleted": true
  }
}
Erros possíveis: UNAUTHORIZED
POST/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.
Exemplo
curl -X POST "https://otpgmail.net/v1/webhook/test" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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
{
  "success": true,
  "data": {
    "ok": false,
    "httpStatus": 500,
    "durationMs": 88,
    "error": "HTTP 500",
    "deliveryId": "7d2b9f4e-1c6a-4e3b-8d5f-0a9c2e7b1f4d"
  }
}
Erros possíveis: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED, MAINTENANCE
GET/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
NomeTipoObrigatórioDescrição
limitnumberNãoNúmero de entregas, 1–100 (padrão 100)
Exemplo
curl "https://otpgmail.net/v1/webhook/deliveries?limit=20" \
  -H "Authorization: Bearer og_xxx..."
Resposta de exemplo
{
  "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