Aplicar descuento personalizadoCualquiera que alquile OTP Gmail > 2000 Gmails al día para cualquier servicio (excepto Facebook, Instagram y Apple) y quiera un descuento exclusivo, ¡solo escríbame! Puede haber hasta un 50% de descuento sobre el precio original para quienes registren cuentas en volúmenes suficientemente grandes.
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

Documentación de la API

API pública /v1: autentícate con una clave de API y automatiza el alquiler de buzones desde tu propio software

Tu clave de API
No has iniciado sesión: inicia sesión para obtener tu propia clave de API.
Envía esta clave en el encabezado Authorization: Bearer <key> o en el parámetro de consulta ?api_key=<key>. Pulsa el icono del ojo para ver la clave completa; pulsa Generar nueva clave cuando necesites reemplazarla (la clave anterior deja de funcionar al instante).

Resumen

URL base
https://otpgmail.net
Autenticación
Añade tu clave de API a cada solicitud, ya sea en el encabezado Authorization: Bearer og_xxx... (recomendado) o en la cadena de consulta ?api_key=og_xxx....
Idempotency-Key (evita pedidos duplicados)
Toda solicitud POST /v1/orders debería incluir el encabezado Idempotency-Key: <UUID nuevo por pedido>. Si la solicitud agota el tiempo de espera o se corta la conexión, envíala de nuevo con la misma clave: el servidor devuelve la respuesta original en lugar de crear otro pedido. Genera un UUID nuevo para cada pedido nuevo y reutiliza un UUID solo al reintentar.
Muchos pedidos a la vez: estado en una sola llamada
¿Alquilas decenas o cientos de buzones? Consulta su estado en bloque: POST /v1/orders/status devuelve hasta 100 pedidos por llamada (cuenta como una sola solicitud en tu límite) y GET /v1/orders lista tus pedidos; tras un reinicio, ?status=waiting_code te devuelve todos los pedidos que siguen esperando un código. Cada error indica además si conviene reintentar (error.retryable, error.retryAfterSec). Detalles: la sección Estado de muchos pedidos.
Tipo de buzón: Gmail o iCloud
De forma predeterminada obtienes un buzón @gmail.com. Para alquilar un buzón @icloud.com, añade "domain": "icloud.com" al cuerpo de POST /v1/orders. Si omites domain, se entiende gmail.com: las integraciones existentes siguen funcionando sin cambios. iCloud solo está disponible para los servicios cuyo campo icloud no sea null en GET /v1/services; el precio de iCloud puede ser distinto del de Gmail (consulta icloud.price). Cada respuesta de un pedido incluye un campo domain que indica qué tipo de buzón usa el pedido. Algunos servicios también ofrecen buzones @gmx.com (por ahora solo Pokemon, código pkm, almacén 2): envía "domain": "gmx.com"; un servicio lo ofrece cuando su offers[] en GET /v1/services tiene una entrada con domain = "gmx.com"; con cualquier otro servicio ⇒ DOMAIN_UNAVAILABLE. gmx.com no está disponible para lotes.
Almacenes: un servicio, varias fuentes de buzones
Un servicio puede venderse desde varios almacenes, cada uno con su propio precio, stock y reglas de códigos (el array offers[] de GET /v1/services). Envía "warehouse": 2 al crear un pedido para elegir uno; "warehouse": 99 ⇒ el sistema elige el almacén más barato con stock y pasa al siguiente si se agota (ideal para servicios que se agotan a menudo, como Facebook e Instagram); 0 ⇒ el almacén más barato con stock. Los clientes existentes no necesitan cambios: si omites warehouse ⇒ almacén 1 como antes, y price, stock e icloud describen el almacén 1 (servicios sin almacén 1 ⇒ el almacén más barato con stock). Detalles: consulta Almacenes.
Alquiler por lotes
Los lotes se compran en la web (la página Alquiler por lotes); la API permite listar lotes y leer códigos. Gmail (Almacén 1): cada buzón tiene un proxyUrl, un enlace de código que no necesita clave de API. iCloud (Almacén 3): buzones privados; lee los códigos con GET /v1/batches/{id}/mails/{mailId}/codes o abre readUrl en la web. Detalles: sección Alquiler por lotes.
Comprar cuentas de Gmail de vida corta (mail shop)
Además de alquilar buzones, puedes comprar cuentas de Gmail completas de 2 almacenes — almacén 1: correo, contraseña, cookies + enlace del buzón (también en JSON); almacén 2: correo, contraseña + enlace de inicio de sesión rápido; el almacén 99 elige primero el más barato (la página Comprar Gmail de vida corta en la web). La API hace todo lo que hace esa página: ver almacenes, precios y stock, comprar (con Idempotency-Key obligatoria), seguir la entrega, descargar las cuentas y leer sus correos. Detalles: sección Comprar Gmail de vida corta.
Que tus clientes vean los códigos por sí mismos (página /otp)
¿Revendes buzones que alquilaste (en la web o por la API)? Activa un código para compartir en tu página Cuenta y envía a tu cliente el enlace https://otpgmail.net/es/otp#k7m2q9:[email protected] (k7m2q9 = tu código para compartir): verá los últimos códigos de ese correo sin iniciar sesión y nunca verá precios ni tu saldo; la página solo realiza acciones gratuitas (nunca te cobra).
Enlace JSON para herramientas / bots (tuyos o de tu cliente): toma el enlace con forma de ruta https://otpgmail.net/es/otp/k7m2q9:[email protected] (abre la página /otp igual que el enlace con #) y añade ?json ⇒ el servidor devuelve un JSON con exactamente lo que muestra la página: cards[] — una tarjeta por cada servicio alquilado en ese correo (email, serviceName, state, items[] = hasta 5 códigos / enlaces más recientes, primero el más nuevo) — y serverTime. state: waiting espera el primer código · listening espera un código nuevo · received ya tiene códigos · needs_owner el vendedor debe ampliarlo · expired caducado · banned buzón bloqueado. Varios correos: sepáralos con comas (hasta 50). Sin clave de API ni cookies, CORS abierto; solo lectura (“Cargar nuevo OTP” / “Alquilar este correo de nuevo” siguen en la página /otp). Mismos límites antiadivinanza que la página: código incorrecto / el correo no tiene pedidos que mostrar ⇒ 404 NOT_FOUND; fallos repetidos o más de 30 lecturas por minuto para un correo ⇒ 429 RATE_LIMITED con retryAfter (segundos). Añadir ?json a un enlace /otp#… NO devuelve JSON: el navegador nunca envía al servidor lo que va después de #.
curl "https://otpgmail.net/es/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 ejemplo completo en GitHub
Clientes listos para ejecutar en Python, Node.js, PHP, C# (.NET) y curl: alquilar un buzón, esperar el código, cancelar automáticamente para obtener el reembolso y alternar entre iCloud y Gmail cuando se agota el stock: github.com/letoan0902/otpgmail-email-otp-api (inglés) · thue-otp-gmail (vietnamita). Nota sobre Python: usa requests en lugar de urllib a secas (su User-Agent predeterminado recibe un 403 de la CDN).
Webhooks
En lugar de consultar cada pedido sin parar, registra una URL y recibe un POST firmado en cuanto un pedido recibe un código nuevo o se cancela / reembolsa — consulta Webhooks.

Endpoints

GET/v1/balance
Devuelve el saldo de la cuenta (en VND) correspondiente a la clave de API usada en la solicitud.
Ejemplo
curl "https://otpgmail.net/v1/balance" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "success": true,
  "data": {
    "balance": 25000
  }
}
Errores posibles: UNAUTHORIZED
GET/v1/services
Lista los servicios habilitados: código, nombre, precio de Gmail (en VND) y stock actual de Gmail, un bloque icloud con el precio y el stock de los buzones @icloud.com, y un array offers[]: cada almacén que vende el servicio, con una entrada por almacén × tipo de buzón (precio, stock, número de códigos, reglas). Los datos se guardan en caché: el stock se actualiza cada 30–60 segundos aproximadamente y los precios con menos frecuencia.
icloud = null significa que el servicio aún no admite iCloud (el proveedor no lo ofrece o está desactivado temporalmente): solo se puede alquilar Gmail. Para los servicios con un bloque icloud, envía "domain": "icloud.com" al crear el pedido para obtener un buzón @icloud.com al precio icloud.price. price / stock / icloud.price describen el almacén 1, adonde van los pedidos sin warehouse (servicios sin almacén 1 ⇒ el almacén más barato con stock); los precios y el stock de cada almacén están en offers[] (consulta Almacenes). stock es la cifra que publica la fuente (almacén 1: se actualiza aproximadamente cada 30 segundos), no una promesa de que una sola llamada pueda alquilar tantos: los buzones se asignan de uno en uno y la fuente puede agotarse a mitad; entonces POST /v1/orders devuelve menos pedidos y un campo partial. Los almacenes 2 y 4 también muestran las cifras de sus fuentes.
Ejemplo
curl "https://otpgmail.net/v1/services" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
    }
  ]
}
Errores posibles: UNAUTHORIZED
POST/v1/orders
Crea un pedido de un buzón que recibe códigos de verificación: Gmail de forma predeterminada, o iCloud si envías domain: "icloud.com". Tu saldo se cobra en cuanto el proveedor asigna el buzón. Devuelve una lista de pedidos (uno, o varios cuando quantity > 1).
Entrega parcial: quantity es un máximo
  • Los buzones se asignan de uno en uno. Si el proveedor se queda sin buzones a mitad (por ejemplo, el almacén 1 no tiene más buzones para ese servicio en ese momento), la llamada se detiene y devuelve HTTP 200 con los pedidos creados hasta entonces: data puede ser más corto que quantity y solo se cobran los pedidos de data. El stock de GET /v1/services no garantiza cuántos obtendrá una llamada.
  • En ese caso la respuesta incluye además un campo de primer nivel partial junto a data (no aparece si se entregó todo, así que los bots que solo leen data siguen funcionando): requested, delivered, missing y reason = {code, message, retryable, retryAfterSec}, donde code es el error que detuvo la llamada (NO_MAILS_AVAILABLE, OUT_OF_STOCK, ALL_WAREHOUSES_EXHAUSTED, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED…).
  • Quedarse sin buzones suele ser temporal: en el almacén 1 el proveedor a menudo vuelve a tener buzones a los pocos segundos. Si reason.retryable = true, espera reason.retryAfterSec segundos (3–5 s) y vuelve a pedir los buzones que faltan (missing) con una Idempotency-Key NUEVA (la clave anterior solo repite esta misma respuesta); repite hasta tenerlos todos.
  • Justo después de una entrega parcial, la siguiente llamada puede devolver 409 WAREHOUSE_UNAVAILABLE / OUT_OF_STOCK o 503 NO_MAILS_AVAILABLE: es normal; sigue esperando 3–5 s entre llamadas y abandona solo cuando fallen muchas llamadas seguidas (por ejemplo 10 intentos, alrededor de un minuto); entonces inténtalo más tarde o envía "warehouse": 99 para que el sistema pase a otros almacenes. retryable = false (INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED…) ⇒ resuelve primero la causa (recarga saldo o espera a que terminen los pedidos abiertos).
  • Las respuestas de error llevan los mismos dos campos dentro de error: error.retryable y error.retryAfterSec (por ejemplo, 409 OUT_OF_STOCK ⇒ true, 5; 402 INSUFFICIENT_BALANCE ⇒ false, null); consulta Códigos de error.
El encabezado Idempotency-Key (un UUID) es obligatorio. Genera un UUID nuevo para cada alquiler nuevo; al reintentar tras un timeout, reutiliza la MISMA clave para no crear un pedido duplicado. Esta llamada espera a que el proveedor asigne un buzón, por lo que normalmente tarda unos 0,3 segundos pero puede tardar hasta 10 segundos en horas punta (poco frecuente): configura el timeout de tu cliente en unos 15 segundos en lugar de rendirte a los 5 segundos y reintentar demasiado pronto.
Parámetros
NombreTipoObligatorioDescripción
servicestringSíCódigo del servicio (de GET /v1/services, p. ej. "ig", "tg", "fb")
quantitynumberNoNúmero MÁXIMO de buzones que alquilar (1 por defecto; como mucho 200 por solicitud): si el almacén se agota a mitad, recibes menos; cuenta data[] y lee partial
domainstringNoTipo de buzón: "gmail.com" (predeterminado), "icloud.com" o "gmx.com". Si se omite = gmail.com: las integraciones existentes no necesitan cambios. iCloud solo se puede alquilar para servicios cuyo campo icloud no sea null en GET /v1/services; "gmx.com" solo para servicios con offers[].domain = "gmx.com" (por ahora solo Pokemon, código pkm); en caso contrario la llamada falla con DOMAIN_UNAVAILABLE. Cualquier otro valor ⇒ VALIDATION_ERROR.
warehousenumberNoAlmacén donde alquilar. Si se omite = ALMACÉN 1, exactamente como antes de los múltiples almacenes (los servicios sin almacén 1 usan el más barato): los clientes existentes no necesitan cambios. 0 = el almacén más barato con stock (solo se prueba un almacén). 1–4 = ese almacén concreto (de offers[].warehouse en GET /v1/services); si no vende este servicio/tipo de buzón, está en pausa o se ha agotado ⇒ WAREHOUSE_UNAVAILABLE. 99 = AUTOMÁTICO: se prueban los almacenes con stock del más barato al más caro, pasando al siguiente cuando uno se agota o falla; si no queda ninguno ⇒ ALL_WAREHOUSES_EXHAUSTED. Recomendado para servicios que se agotan a menudo (Facebook, Instagram…). Consulta Almacenes.
Ejemplo
# Genera una Idempotency-Key aleatoria para cada pedido NUEVO
# REUTILIZA la misma clave al reintentar tras un timeout (evita pedidos duplicados)
# domain: "gmail.com" (predeterminado, opcional) | "icloud.com" (alquila un buzón @icloud.com) | "gmx.com" (solo servicios que lo ofrecen, p. ej. Pokemon)
# warehouse: 0 | 1–4 | 99 (opcional: si lo omites, se usa el almacén 1 como antes; 0 = almacén más barato con stock; 99 = automático, pasa al siguiente cuando un almacén se agota; consulta offers[] en 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"}'
Respuesta de ejemplo
{
  "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
    }
  ]
}
Respuesta de ejemplo: entrega parcial (quantity 5, 2 buzones entregados)
{
  "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
    }
  }
}
Errores posibles: 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}
Devuelve un pedido por su ID, incluidos los códigos recibidos hasta ahora (otp[]). Consulta este endpoint cada 3–5 segundos para recoger el código mientras el pedido está en estado waiting_code.
El array otp[] está ordenado por llegada: el más antiguo primero, el MÁS RECIENTE AL FINAL: lee el último código con otp[otp.length - 1]. En cada llamada el sistema consulta al proveedor por los pedidos aún abiertos, al ritmo propio de cada almacén (cada pocos segundos); llamar más a menudo solo devuelve los datos guardados. Tras el primer código sigue consultando: los códigos siguientes (si el servicio envía más de uno) se añaden al final de otp[] sin costo extra dentro del periodo del pedido; el límite y la ventana de tiempo se muestran antes de alquilar (maxCodes y fee en offers[]). Para asegurarte de que el sistema espera un código nuevo, llama a POST /v1/orders/{id}/next-code (cualquier almacén; solo el almacén 2 fuera de su ventana sin costo cobra un cargo). Si el servicio solo permite un código por buzón, otp[] se detiene en ese código: las respuestas siguen siendo normales, sin error. El campo warehouse indica qué almacén suministró el buzón (1–4); nextCodeFee es el cargo por más códigos (pedidos de los almacenes 2 y 3; null en los almacenes 1 y 4): consulta Almacenes. Desde el 2026-10-02 los pedidos también incluyen nextCodeBlocked (true = el buzón no puede recibir más códigos) y nextCodeBlockReason, el motivo: "email_banned" (el servicio bloqueó el correo: alquila uno nuevo), "activation_gone" (el proveedor cerró el buzón), "hard_expired" (terminó la ventana de códigos), "max_codes" (se alcanzó el número máximo de códigos), null (sin bloqueo / pedido antiguo sin motivo registrado). Son campos añadidos: los clientes existentes no necesitan cambios. ¿Consultas muchos pedidos? Envía hasta 100 ID por llamada a POST /v1/orders/status en lugar de un GET por pedido: los mismos objetos de pedido y una sola solicitud en tu límite.
Parámetros
NombreTipoObligatorioDescripción
idstringSíID del pedido (el orderId devuelto al crear el pedido)
Ejemplo
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: UNAUTHORIZED, NOT_FOUND
POST/v1/orders/{id}/cancel
Cancela el pedido y reembolsa el 100 % del precio. Solo se permite mientras el pedido está en estado waiting_code Y no se ha recibido ningún código.
Un pedido que ha recibido un código (aunque sea uno solo) no se puede cancelar ni reembolsar: esto se aplica tanto al sitio web como a la API. Los pedidos con rules.cancellable = false (servicios de inventario propio como Cointiply) no se pueden cancelar: una vez asignado el buzón no hay cancelación ni reembolso; la llamada de cancelación devuelve ORDER_NOT_CANCELLABLE. Los pedidos del almacén 3 de servicios normales (rules.cancellable = true) se pueden cancelar como cualquier otro pedido antes de que llegue un código.
Parámetros
NombreTipoObligatorioDescripción
idstringSíID del pedido que se va a cancelar
Ejemplo
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json"
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR
POST/v1/orders/{id}/rerent
Alquila de NUEVO el MISMO buzón para un pedido de un servicio de inventario propio (provider = "inventory", p. ej. Cointiply): crea un pedido NUEVO en ese buzón con una ventana de recepción nueva, al precio rerentPriceVnd del pedido anterior.
Solo funciona cuando el pedido anterior tiene canRerent = true (el último pedido de ese buzón, cuya ventana anterior ya terminó o recibió todos sus correos). El cobro se hace en cuanto la operación se completa: envía una Idempotency-Key y reutilízala al reintentar para que nunca se te cobre dos veces. La respuesta es { order, message }; order tiene la misma forma que GET /v1/orders/{id}. Almacén 2 (desde el 2026-10-09): un pedido cancelado, agotado por tiempo o vencido (buzón cerrado); llámalo cuando canRerent = true (el último pedido de ese correo, sin otro pedido activo en él). El sistema reabre EXACTAMENTE ese correo como un pedido NUEVO (status = waiting_code) al precio rerentPriceVnd (ahora el 100% de tu precio) y espera un correo 20 minutos como cualquier pedido del almacén 2; si no llega nada, se cancela y se reembolsa el 100%. Correo bloqueado por el servicio ⇒ RERENT_EMAIL_BANNED; el proveedor ya no puede reabrirlo ⇒ RERENT_MAIL_GONE; almacén 2 caído temporalmente ⇒ PROVIDER_ERROR; en ninguno de los tres casos se cobra.
Parámetros
NombreTipoObligatorioDescripción
idstringSíID del pedido anterior del buzón (el que tiene canRerent = true)
Ejemplo
# REUTILIZA la misma clave al reintentar tras un timeout (evita pedidos duplicados)
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/rerent" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Idempotency-Key: $(uuidgen)"
Respuesta de ejemplo
{
  "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"
  }
}
Errores posibles: 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
Pide el siguiente código en el MISMO buzón: funciona para pedidos de CUALQUIER almacén (warehouse 1–4) que ya recibieron su primer código (status = completed). Es gratis, salvo en los pedidos del almacén 2 cuya ventana sin costo ya terminó (nextCodeFee.feePriceVnd > 0): en ese caso envía confirmFee: true para pagar el cargo y abrir otra ventana de códigos (hasta el límite máximo nextCodeFee.hardUntil). Tu bot no necesita saber el almacén: sigue consultando GET /v1/orders/{id} (los códigos nuevos se añaden al final de otp[]) y llama a este endpoint cuando quieras que el sistema espere un código nuevo.
Qué hace esta llamada en cada almacén
  • Almacén 1: el sistema espera solo el siguiente código después de cada código dentro del periodo del pedido (rentExpiresAt); la llamada devuelve 200 y no cambia nada; si el buzón ya no puede recibir más códigos (límite alcanzado, buzón cerrado, caducado) ⇒ 409 con nextCodeBlockReason.
  • Almacén 2: reabre el buzón en el proveedor (reordered = true) y espera un correo durante 20 minutos; es gratis dentro de la ventana sin costo (nextCodeFee.feePriceVnd = 0) y fuera de ella debes enviar confirmFee = true (si falta ⇒ 409 NEXT_CODE_FEE_CONFIRM, sin cobro). El cargo solo se conserva si llega un código nuevo: sin código en 20 minutos ⇒ reembolso automático. Después de cada código, el sistema sigue esperando el siguiente 25 minutos más (listening = true, listenUntil).
  • Almacén 3 (servicios normales): vuelve a abrir una escucha breve, gratis dentro de la ventana sin costo (nextCodeFee.freeUntil); pasada la ventana ⇒ 409: usa POST /v1/orders/{id}/rerent. Los servicios de inventario propio como Cointiply (rules.cancellable = false) no admiten esta llamada (409): cada correo aparece solo durante la ventana.
  • Almacén 4: vuelve a leer el pedido en el proveedor y espera un código nuevo, gratis hasta rentExpiresAt.
200 = el sistema está esperando un código nuevo para este pedido (listening = true, listenUntil = cuándo deja de esperar; reordered = true si el buzón se acaba de reabrir; chargedVnd si se cobró un cargo), o llegó un código nuevo justo al llamar (ya está en order.otp). 409 = no se pudo abrir una nueva espera: lee error.code; el motivo por el que el buzón no admite más códigos está en order.nextCodeBlockReason (GET /v1/orders/{id}). Un pedido que aún espera su primer código (waiting_code) ⇒ 409, todavía no hace falta llamar. Envía un Idempotency-Key en las llamadas de pago (reutilízalo al reintentar para que nunca se cobre dos veces). Proveedor inaccesible ⇒ 502 PROVIDER_ERROR, inténtalo más tarde. Códigos de error 409 de esta llamada: NEXT_CODE_FEE_CONFIRM (el almacén 2 requiere el cargo: vuelve a llamar con confirmFee: true), EMAIL_BANNED (el servicio bloqueó el correo: alquila otro; error.details.blockReason = "email_banned"), ORDER_EXPIRED (caducado, buzón cerrado, límite de códigos alcanzado o el servicio no tiene «siguiente código»; el motivo está en order.nextCodeBlockReason), ORDER_NOT_CANCELLABLE (el pedido aún no recibió su primer código). Los bots que tratan cualquier 409 de esta llamada como «no hay más códigos» siguen funcionando sin cambios. refresh: true (almacén 2): primero se comprueba si hay un correo nuevo (si acaba de llegar se entrega y no se reabre), luego se reabre el buzón en el proveedor — dentro de la ventana sin costo la espera se reinicia por 20 minutos; fuera de ella la espera conserva su fin (listenUntil) y no se cobra nada. La respuesta añade refreshed (true = reabierto ahora) y refreshAvailableAt (segundos unix; antes de ese momento otra actualización solo comprueba el correo; null = ya se puede). Otros almacenes: sin efecto (refreshed = false). Sin refresh la llamada funciona exactamente como antes.
Parámetros
NombreTipoObligatorioDescripción
idstringSíID del pedido (orderId), de cualquier almacén
confirmFeebooleanNotrue = aceptas pagar nextCodeFee.feePriceVnd para abrir otra ventana de códigos. No hace falta mientras feePriceVnd = 0.
refreshbooleanNotrue = almacén 2, el pedido espera un código nuevo (listening = true) pero no llega: reabre ya el buzón en el proveedor (actualiza la espera) — la espera anterior puede haber dejado de recibir correos del lado del proveedor. Gratis; como máximo una vez cada 5 s por pedido. Si el pedido no está esperando ⇒ igual que una llamada normal.
Ejemplo
# Gratis (nextCodeFee es null o feePriceVnd = 0: cualquier almacén salvo el 2 tras su ventana sin costo): no hace falta confirmFee
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
  -H "Authorization: Bearer og_xxx..."

# Almacén 2 tras su ventana sin costo: envía 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}'
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, NEXT_CODE_FEE_CONFIRM, INSUFFICIENT_BALANCE, EMAIL_BANNED, ORDER_EXPIRED, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR, DUPLICATE_REQUEST, MAINTENANCE

Estado de muchos pedidos en una sola llamada

Los revendedores y bots que alquilan muchos buzones no deberían enviar un GET /v1/orders/{id} por pedido: POST /v1/orders/status lee hasta 100 pedidos por llamada con los mismos objetos de pedido, y GET /v1/orders lista tus pedidos a partir de los datos guardados.
  • Consulta cada 3–5 segundos con POST /v1/orders/status, hasta 100 ID de pedido por llamada; cada llamada cuenta como una sola solicitud en tu límite.
  • En cada llamada el sistema consulta al proveedor como máximo 20 pedidos abiertos de la lista (primero los que esperan su primer código, después los que llevan más tiempo sin consultarse) y devuelve el resto con los datos guardados; esos se consultan en las llamadas siguientes. Una llamada responde en unos 3 segundos.
  • Quita un pedido de tu lista cuando sea definitivo: cancelled (reembolsado) o completed si ya no necesitas más códigos.
  • Los ID que no existen o pertenecen a otra cuenta vuelven en missing (sin 404): quítalos también.
  • Tras un reinicio, recupera todos los pedidos abiertos con GET /v1/orders?status=waiting_code (sigue nextCursor) y retoma las consultas con POST /v1/orders/status.
POST/v1/orders/status
Devuelve hasta 100 de tus pedidos en una llamada. Cada elemento es exactamente lo que GET /v1/orders/{id} devuelve para ese pedido (status, otp[] con todos los códigos recibidos, warehouse…). Úsalo para consultar muchos pedidos abiertos a la vez.
data respeta el orden de ids; los ID repetidos se ignoran. Los ID que no existen o pertenecen a otra cuenta aparecen en missing (sin 404). Para los pedidos abiertos, el sistema consulta al proveedor igual que GET /v1/orders/{id}, pero como máximo para 20 pedidos por llamada (primero los que esperan su primer código y después los que llevan más tiempo sin consultarse) y devuelve el resto con los datos guardados (se consultan en las llamadas siguientes); una llamada responde en unos 3 segundos. Cada llamada cuenta como una sola solicitud en tu límite, lleve los ID que lleve. Más de 100 ID, una lista vacía o ID que no sean cadenas ⇒ 400 VALIDATION_ERROR.
Parámetros
NombreTipoObligatorioDescripción
idsstring[]SíID de pedido (orderId): de 1 a 100 por llamada
Ejemplo
# ID de los pedidos que esperas (orderId de POST /v1/orders): hasta 100 por llamada
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"]}'
Respuesta de ejemplo
{
  "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"
  ]
}
Errores posibles: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED
GET/v1/orders
Lista tus pedidos, del más reciente al más antiguo, solo con los datos guardados: no pide códigos nuevos al proveedor (eso lo hace POST /v1/orders/status). Filtra por estado y fecha de creación; pagina con nextCursor.
Pasa nextCursor como cursor para obtener la página siguiente; nextCursor = null ⇒ última página. Las páginas nunca repiten ni se saltan un pedido, aunque se hayan creado muchos en el mismo segundo. Los estados internos nunca se listan (solo waiting_code, completed, cancelled y queued). Tras reiniciar tu bot: GET /v1/orders?status=waiting_code&limit=200, sigue nextCursor y luego consulta esos pedidos con POST /v1/orders/status.
Parámetros
NombreTipoObligatorioDescripción
statusstringNoSolo este estado: waiting_code | completed | cancelled | queued (sin indicar ⇒ todos)
sincenumberNoSolo los pedidos creados en esta hora unix (segundos) o después
limitnumberNoPedidos por página: 1–200 (50 por defecto)
cursorstringNonextCursor de la página anterior, sin modificar
Ejemplo
# Tras un reinicio: reúne todos los pedidos que siguen esperando un código, página a página
curl "https://otpgmail.net/v1/orders?status=waiting_code&limit=200" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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"
}
Errores posibles: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED

Almacenes

Cada entrada de offers[] es una oferta = (servicio, almacén, tipo de buzón). Solo se listan los almacenes que venden en este momento; un almacén sin stock temporalmente permanece en la lista con stock = 0. Los números de almacén son fijos: 1, 2, 3, 4; pasa el mismo número en el parámetro warehouse al crear un pedido.
Los clientes existentes no necesitan cambios. Si omites warehouse ⇒ el mismo comportamiento de antes (almacén 1: espera de 25 minutos, los códigos adicionales llegan automáticamente dentro del periodo del pedido de 24 horas cuando el servicio envía más de uno); si envías 0 ⇒ almacén más barato con stock; si envías 99 ⇒ almacén automático, que pasa al siguiente cuando uno se agota (más abajo); todos los campos existentes se mantienen: solo se añaden campos nuevos (offers[], warehouse, nextCodeFee, listening, nextCodeBlocked…).
Almacén automático: warehouse = 99 (recomendado para servicios que se agotan a menudo)
Envía "warehouse": 99 y el sistema alquila en el almacén más barato con stock; si ese almacén se agota o falla, pasa al siguiente dentro de la misma llamada. Ideal para servicios que se agotan a menudo, como Facebook e Instagram: tu bot ya no tiene que leer offers[] ni reintentar almacén por almacén.
  • Orden de prueba: precio de menor a mayor (p. ej. 1500 → 1600 → 1700 VND); a igual precio ⇒ primero el almacén de número menor.
  • Los almacenes que informan de stock cero (stock = 0 en offers[]) nunca se llaman: no se pierde tiempo.
  • Pasa al siguiente cuando el almacén actual está agotado o falla (OUT_OF_STOCK, NO_MAILS_AVAILABLE, WAREHOUSE_UNAVAILABLE, PROVIDER_ERROR).
  • Los demás errores se devuelven de inmediato, sin probar el siguiente almacén: INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, QUANTITY_EXCEEDED, MAINTENANCE, SERVICE_DISABLED, DOMAIN_UNAVAILABLE…
  • quantity > 1: si el primer almacén se queda corto, el resto sale del siguiente: los pedidos pueden abarcar varios almacenes; cada pedido lleva el warehouse y el price del almacén que lo emitió (y sigue las reglas de códigos de ese almacén).
  • Si se probaron todos los almacenes y no se pudo alquilar ningún buzón ⇒ ALL_WAREHOUSES_EXHAUSTED (HTTP 409, sin cobro): inténtalo más tarde. Si solo se alquilaron algunos buzones, recibes igualmente 200 con esos pedidos.
Ejemplo: alquilar un Gmail de Facebook con warehouse = 99
# warehouse: 99: almacén más barato con stock; si se agota o falla ⇒ pasa automáticamente al siguiente
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[]
CampoTipoDescripción
warehouse1 | 2 | 3 | 4Número de almacén: 1, 2, 3 o 4.
domainstringTipo de buzón de la oferta: gmail.com, icloud.com o gmx.com (dominio adicional: solo algunos servicios, actualmente Pokemon).
pricenumberPrecio de un buzón de este almacén (VND).
stocknumberBuzones disponibles. 0 = agotado: alquilar en el almacén 1 o 2 devuelve WAREHOUSE_UNAVAILABLE; el almacén 4 se intenta igualmente (sus fuentes suelen entregar aunque reporten 0) y devuelve OUT_OF_STOCK si no hay nada.
maxCodesnumber | nullMáximo de códigos que puede recibir un buzón; null = sin límite / desconocido (consulta fee).
rulesobject | nullReglas de ventana del almacén 3 (windowMinutes, maxCodes, cancellable, refundOnTimeout, rerentRatio); null en los almacenes 1, 2 y 4.
feeobject | nullVentana de códigos adicionales: freeHours horas sin costo desde el alquiler y, después, cada ampliación cuesta el paidPct % del precio del alquiler, hasta maxHours horas desde el alquiler (null = sin límite). fee = null: sin costo; los códigos adicionales no cuestan nada extra dentro del periodo del pedido cuando el servicio envía más de uno (hasta maxCodes), como en el almacén 1.
isDefaultbooleantrue para el almacén más barato con stock (a igual precio ⇒ el de número menor): el que se usa con warehouse = 0 y el primero que se prueba con 99.
Reglas por almacén
ReglaAlmacén 1Almacén 2Almacén 3Almacén 4
Espera del primer código25 minutos20 minutos25 minutos ¹Hasta 25 minutos (menos en algunas fuentes: el plazo real es el codeDeadlineAt del pedido)
Sin códigoCancelación automática, reembolso del 100 %Cancelación automática, reembolso del 100 %Cancelación automática, reembolso del 100 % ¹Cancelación automática, reembolso del 100 %
Cancelar antes de un códigoSíSíSí ¹Sí
Códigos por buzónSegún maxCodesSin límiteSin límite ¹Según maxCodes (normalmente null = sin límite; 1 en algunas ofertas)
Más códigosSin costo extra dentro del periodo del pedido de 24 horas si el servicio envía más códigos; automático (sin llamada adicional). El buzón suele permanecer abierto unos 25 minutos después del último código.Sin costo durante fee.freeHours horas (actualmente 6 horas en Gmail, 12 horas en iCloud); después, cada ampliación cuesta el fee.paidPct % del precio mediante next-code, hasta fee.maxHours (24 horas en Gmail, 10 días en iCloud)Sin costo extra durante 24 horas; después, alquila de nuevo el mismo buzón (rerent) por el 50 % del precioSin costo extra dentro del periodo del pedido de 24 horas, como el almacén 1: los códigos nuevos aparecen al leer el pedido; next-code lo vuelve a leer al instante.
¹ Los servicios que se venden solo desde el inventario propio, como Cointiply (provider = "inventory", rules.cancellable = false), siguen rules: hasta rules.maxCodes correos en rules.windowMinutes minutos, sin cancelación y sin reembolso si se acaba el tiempo sin recibir ningún correo.
nextCodeFee: cargo por más códigos (pedidos de los almacenes 2 y 3)
Se incluye en GET /v1/orders/{id} y en cada respuesta de pedido; null en los pedidos de los almacenes 1 y 4 (los códigos adicionales son gratis). Almacén 2: llama a POST /v1/orders/{id}/next-code cuando necesites otro código; si feePriceVnd es mayor que 0, debes enviar confirmFee = true. Almacén 3 (servicios normales): terminada la ventana sin costo, feePriceVnd es el precio de volver a alquilar el mismo buzón (rerent).
CampoTipoDescripción
feePriceVndnumberImporte que se cobra si llamas a next-code ahora; 0 = aún dentro de la ventana sin costo.
freeUntilnumber | nullFin de la ventana sin costo actual (segundos unix).
hardUntilnumber | nullLímite máximo (segundos unix): después de este momento no llegan más códigos.
paidPctnumber% del precio del alquiler que se cobra por cada ventana adicional.
freeHoursnumberHoras sin costo de una ventana de códigos (almacén 2: Gmail 6, iCloud 12; almacén 3: 24).
Ejemplo: lista los almacenes y alquila en uno
# warehouse: 0 | 1–4 | 99 (opcional: si lo omites, se usa el almacén 1 como antes; 0 = almacén más barato con stock; 99 = automático, pasa al siguiente cuando un almacén se agota; consulta offers[] en 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}'
Pedido de ejemplo del almacén 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
  }
}

Alquiler por lotes

Los lotes se compran en la web (la página Alquiler por lotes); la API permite listar lotes y leer códigos. El dominio del correo determina el almacén: Gmail = Almacén 1, iCloud = Almacén 3 (buzones privados).
  • Un lote se conserva durante 24 horas desde la compra: lee los códigos sin costo durante ese tiempo, sin límite en el número de códigos.
  • Los buzones que no reciben ningún código no se reembolsan; solo se reembolsa (automáticamente) una diferencia en la cantidad.
  • Almacén 1 (Gmail): cada buzón tiene un proxyUrl: un GET devuelve {"status":1,"code":"482913","all_codes":[...]} sin clave de API (el enlace es el secreto: mantenlo en privado). El endpoint de códigos de abajo también funciona.
  • Almacén 3 (iCloud): proxyUrl = null: lee los códigos con el endpoint de abajo o abre readUrl (página web). Cada buzón queda vinculado a tu cuenta para ese servicio; tras la caducidad, aún puedes alquilarlo de nuevo en la web.
  • Los correos sin código extraíble (correos de bienvenida, boletines…) se devuelven tal cual en code con isRaw: true (HTML o texto, hasta 20 000 caracteres).
Campos de un buzón de un lote (mails[])
CampoTipoDescripción
idnumberID del buzón: el parámetro {mailId} del endpoint de códigos
emailstringDirección de correo
urlstringAlmacén 1: = proxyUrl · almacén 3: = readUrl (se mantiene por compatibilidad con scripts antiguos / archivos exportados)
proxyUrlstring | nullAlmacén 1: enlace de código que no requiere iniciar sesión · almacén 3: null
readUrlstringPágina web para leer los códigos del lote
codeCountnumberNúmero de códigos recibidos
lastCodestring | nullCódigo más reciente (correo sin procesar: primeros 300 caracteres; el texto completo está en el endpoint de códigos)
lastIsRawbooleantrue = el código más reciente es un correo sin procesar
checkedAtnumber | nullÚltima vez que el sistema consultó al proveedor por este buzón (segundos unix; null = nunca)
GET/v1/batches
Tus lotes, del más reciente al más antiguo (paginados con page / pageSize, hasta 100). Cada lote tiene warehouse (1 = Gmail, 3 = iCloud), status (active | expired | failed), expiresAt, codeCount y readUrl.
Parámetros
NombreTipoObligatorioDescripción
pagenumberNoPágina (1 por defecto)
pageSizenumberNoLotes por página (20 por defecto, máx. 100)
Ejemplo
curl "https://otpgmail.net/v1/batches?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: UNAUTHORIZED
GET/v1/batches/{id}
Un lote con su mails[]: toma el id de cada buzón para leer sus códigos.
Lotes de Gmail (almacén 1): el proxyUrl de cada buzón funciona en tus scripts exactamente como antes. Lotes de iCloud (almacén 3): proxyUrl = null, lee los códigos con el endpoint de abajo. Los lotes de iCloud caducados incluyen rerentPriceVnd: el precio para alquilar de nuevo un buzón de forma individual (se hace en la web).
Parámetros
NombreTipoObligatorioDescripción
idstringSíID del lote (se muestra en la página Alquiler por lotes y en readUrl)
Ejemplo
curl "https://otpgmail.net/v1/batches/BATCH_ID" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
      }
    ]
  }
}
Errores posibles: UNAUTHORIZED, NOT_FOUND
GET/v1/batches/{id}/mails/{mailId}/codes
Lee los códigos de un buzón de un lote: el sistema consulta al proveedor si toca revisar el buzón y luego devuelve TODOS los códigos recibidos, del más antiguo al más reciente: el código más reciente es el último de codes[].
Consulta cada 5–10 segundos mientras esperas (llamar con más frecuencia solo devuelve los datos almacenados: polledAt no cambia). Los correos sin código extraíble se devuelven tal cual en code con isRaw: true. Los lotes caducados siguen devolviendo sus códigos almacenados, pero ya no se comprueban.
Parámetros
NombreTipoObligatorioDescripción
idstringSíID del lote
mailIdnumberSíID del buzón (mails[].id)
poll0 | 1No0 = solo lee los códigos almacenados, no consulta al proveedor (1 por defecto)
Ejemplo
# Vuelve a llamar cada 5–10 segundos mientras esperas; el código más reciente es el último de codes[]
curl "https://otpgmail.net/v1/batches/BATCH_ID/mails/MAIL_ID/codes" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
    }
  }
}
Errores posibles: 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
Iniciar sesión en las cuentas del almacén 1 (sin correo de recuperación)
  • Las cuentas del almacén 1 (gm_cookie) vienen sin correo de recuperación: recibes el correo, la contraseña, las cookies y el enlace del buzón (inboxUrl / inboxJsonUrl), nada más.
  • Forma recomendada de iniciar sesión: importar las cookies (cookies, JSON) en un navegador, con una extensión de importación de cookies o con la importación de cookies de tu navegador antidetect, y luego abrir Gmail: la sesión ya está iniciada, normalmente sin pedir contraseña ni verificación.
  • ¿Solo necesitas los códigos o los correos? Léelos con inboxUrl (página web) o inboxJsonUrl (JSON), sin clave de API y sin iniciar sesión en Google.
  • Iniciar sesión con la contraseña en un dispositivo o una IP nuevos puede hacer que Google pida una verificación (correo de recuperación, teléfono o un código); estas cuentas no tienen con qué superarla, así que usa las cookies o el enlace del buzón.
  • ¿Revendes? Entrega a tus clientes las cookies y el enlace del buzón (la descarga .txt, GET /v1/mail-shop/orders/{id}/download, trae email|password|cookies|inboxUrl en una sola línea) y diles que inicien sesión con las cookies, no con la contraseña.
  • Las cuentas del almacén 2 (gm_login) traen en su lugar un enlace de inicio de sesión rápido (loginUrl): ábrelo en un navegador de escritorio 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[])
CampoTipoDescripción
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
EstadoDescripción
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.
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/products" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
      ]
    }
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
warehouse1 | 2 | 99NoMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringNoInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberNoNumber of accounts (default 1)
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/quote?warehouse=99&quantity=10" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
      }
    ]
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
warehouse1 | 2 | 99NoMail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstringNoInstead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumberNoNumber of accounts (default 1)
Idempotency-KeystringSíHeader: a new UUID for every new purchase — reuse it only to retry that same purchase
Ejemplo
# Idempotency-Key obligatoria: un UUID nuevo por compra — reutiliza la misma clave solo para reintentar tras un 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}'
Respuesta de ejemplo
{
  "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"
      }
    ]
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
pagenumberNoPage (default 1)
pageSizenumberNoOrders per page (default 20, max 100)
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/orders?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
idstringSíOrder ID (data.id returned by the purchase)
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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"
      }
    ]
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
idstringSíOrder ID (data.id returned by the purchase)
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
  -H "Authorization: Bearer og_xxx..." \
  -o mail-ORDER_ID.txt
Respuesta de ejemplo
email|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d
Errores posibles: 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
NombreTipoObligatorioDescripción
idstringSíOrder ID (data.id returned by the purchase)
lineNonumberSíAccount number within the order (items[].lineNo)
maxnumberNoNumber of latest emails: 10 (default), 20, 50, 100 or 200
Ejemplo
# 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"
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
idstringSíOrder ID (data.id returned by the purchase)
lineNonumberSíAccount number within the order (items[].lineNo)
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/status" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "live": true,
    "reason": null,
    "checkedAt": 1760000600
  }
}
Errores posibles: 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
NombreTipoObligatorioDescripción
idstringSíOrder ID (data.id returned by the purchase)
lineNonumberSíAccount number within the order (items[].lineNo)
Ejemplo
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/2fa" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "success": true,
  "data": {
    "none": false,
    "code": "424819",
    "expiresIn": 24
  }
}
Errores posibles: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR

Ciclo de vida del pedido

EstadoDescripción
Esperando códigoPedido recién creado, a la espera del primer código. Consulta GET /v1/orders/{id} cada 3–5 segundos.
CompletadoYa llegó el primer código: el estado cambia al instante. Aún pueden llegar más códigos si el servicio envía más de uno (sin costo extra dentro del periodo del pedido; límite y ventana según el almacén); sigue consultando y cada código nuevo aparece al final de otp[].
CanceladoReembolso del 100%. Ocurre cuando llamas a /cancel (antes de que llegue un código) o cuando no llega ningún código antes de codeDeadlineAt (como máximo 25 minutos).
Notas importantes:
  • Consulta GET /v1/orders/{id} cada 3–5 segundos mientras el pedido esté en waiting_code para recoger el código sin demora.
  • En cuanto llega el primer código, el estado cambia a completed de inmediato.
  • Sin código al llegar codeDeadlineAt (como máximo 25 minutos; almacén 2: 20 minutos; algunas fuentes del almacén 4 tienen menos) ⇒ el sistema cancela el pedido y reembolsa el 100% (status = cancelled).
  • El array otp[] está ordenado del más antiguo al más reciente. Lee el último código con otp[otp.length - 1] (Python: otp[-1]).
  • Un pedido completed sigue recibiendo códigos automáticamente: sigue consultando GET /v1/orders/{id} y cada código nuevo aparece al final de otp[]. Los códigos adicionales no cuestan nada extra dentro del periodo del pedido cuando el servicio envía más de uno; el límite y la ventana de tiempo se muestran antes de alquilar (maxCodes y fee en offers[]; en el almacén 1 el buzón suele permanecer abierto unos 25 minutos después del último código). Para pedir otro código de forma explícita (p. ej. para iniciar sesión tras registrarte): POST /v1/orders/{id}/next-code, que funciona en cualquier almacén; solo el almacén 2 fuera de su ventana sin costo cobra un cargo.
  • Si el servicio permite solo un código por buzón, otp[] se detiene en ese código. Las respuestas siguen siendo normales, sin error: tú decides cuándo dejar de consultar.
  • otp[].code suele ser un código corto (dígitos / letras) o un enlace. Un correo del que no se pudo extraer el código (correos de bienvenida, boletines, formatos de código poco comunes…) se entrega íntegro (HTML o texto) en otp[].code: trátalo como un correo completo si tiene más de 80 caracteres, contiene un salto de línea o es HTML, y busca tú mismo el código o el enlace.
  • POST /v1/orders espera a que el proveedor asigne un buzón, por lo que normalmente tarda ~0,3 segundos pero puede tardar hasta 10 segundos en horas punta (poco frecuente). Configura un timeout de cliente de ≈ 15 segundos en lugar de rendirte a los 5 segundos y reintentar demasiado pronto; y cuando reintentes, reutiliza la misma Idempotency-Key.
  • Un pedido que ha recibido un código no se puede cancelar ni reembolsar: la llamada de cancelación devuelve ORDER_NOT_CANCELLABLE.
  • Para alquilar un buzón @icloud.com, envía "domain": "icloud.com" al crear el pedido (omítelo para Gmail; los bots existentes no necesitan cambios). Si el servicio no admite iCloud, o iCloud está desactivado temporalmente, recibes DOMAIN_UNAVAILABLE: cambia a Gmail y el alquiler se completa de inmediato. El ciclo de vida, las reglas de cancelación y reembolso y la forma de leer los códigos son exactamente los mismos que con Gmail.
  • Los servicios de inventario propio (provider = "inventory", p. ej. Cointiply; consulta GET /v1/services) son solo de iCloud; cada correo llega como un enlace en otp[].code, junto con kind, label y el texto completo del correo en text. Un pedido recibe hasta rules.maxCodes correos en rules.windowMinutes minutos (hasta windowEndsAt), no se puede cancelar (ORDER_NOT_CANCELLABLE) y, si se acaba el tiempo sin recibir ningún correo, pasa a cancelled sin reembolso. Para seguir recibiendo en el mismo buzón, llama a POST /v1/orders/{id}/rerent mientras canRerent sea true (precio: rerentPriceVnd). Todos los demás servicios / pedidos tienen provider = "standard": desde el 2026-10-24 provider solo tiene dos valores, "standard" | "inventory" (el almacén real está en warehouse); compara solo con "inventory".
  • Pedidos del almacén 2 (warehouse = 2): los códigos no tienen costo durante fee.freeHours horas; cuando se cierra la ventana sin costo, dejan de llegar códigos nuevos: llama a POST /v1/orders/{id}/next-code con confirmFee = true (pagando nextCodeFee.feePriceVnd) para abrir otra ventana, hasta nextCodeFee.hardUntil. Pedidos del almacén 3 de servicios normales: códigos sin costo durante 24 horas, cancelables antes de que llegue un código y, después, usa rerent. Pedidos del almacén 4: los códigos nuevos entran en otp[] al leer el pedido, gratis hasta rentExpiresAt; con una fuente de un solo código, tras el primer código nextCodeBlockReason = "max_codes".
  • ¿Muchos pedidos a la vez? Consulta hasta 100 por llamada con POST /v1/orders/status: mira la sección Estado de muchos pedidos en una sola llamada.

Límites de uso

Los límites se aplican por clave de API (una cuenta puede tener límites propios; si no, rige el límite predeterminado del sitio):
  • 100 solicitudes/segundo: ráfagas cortas de unos segundos
  • 3000 solicitudes/minuto: tráfico total por minuto
Si superas un límite, se devuelve HTTP 429 con un encabezado Retry-After: <seconds> y el cuerpo {"success":false,"error":{"code":"RATE_LIMITED",...}}. Espera el número de segundos indicado en el encabezado antes de reintentar. El cuerpo del error lleva el mismo número de segundos en error.retryAfterSec (con error.retryable = true).

Códigos de error

Todos los errores tienen la misma estructura: {"success":false,"error":{"code":"...","message":"...","retryable":false,"retryAfterSec":null}}
Desde el 2026-10-17 todo error de /v1 incluye además error.retryable y error.retryAfterSec (campos añadidos: los clientes existentes no necesitan cambios; details no cambia). retryable = true ⇒ la misma llamada puede funcionar más tarde: espera retryAfterSec segundos (un entero ≥ 1) y vuelve a enviarla; sin existencias / sin buzones / almacén en pausa / error del proveedor ⇒ 5 s; 429 RATE_LIMITED ⇒ el encabezado Retry-After; 409 DUPLICATE_REQUEST mientras la primera solicitud con el mismo Idempotency-Key sigue en curso ⇒ 2 s. retryable = false (retryAfterSec = null) ⇒ volver a llamar no servirá hasta que corrijas la causa (saldo, límite de pedidos en espera, parámetros, clave API…). partial.reason de POST /v1/orders sigue las mismas reglas. Ante una respuesta 409 de falta de stock (OUT_OF_STOCK, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED), espere los retryAfterSec segundos completos antes de reintentar — reintentar antes suele devolver la misma respuesta — y consulte stock / offers[].stock en GET /v1/services antes de volver a alquilar; siga los pedidos que ya tiene con webhooks en lugar de consultar sin parar.
Respuestas de error de ejemplo
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 errorHTTPretryable · retryAfterSecSignificado
UNAUTHORIZED401false · nullLa clave de API no es válida o falta.
ACCOUNT_LOCKED403false · nullLa cuenta está bloqueada: se rechaza toda solicitud hecha con su clave. Contacta con soporte.
FORBIDDEN403false · nullLa cuenta dejó de estar activa mientras se procesaba el alquiler (se acaba de bloquear).
NOT_FOUND404false · nullNo hay ningún pedido / lote / buzón con este ID en tu cuenta (los pedidos de otra cuenta también devuelven 404).
VALIDATION_ERROR400false · nullLos parámetros de la solicitud no son válidos (tipo incorrecto, falta un campo obligatorio, etc.).
INSUFFICIENT_BALANCE402false · nullEl saldo de la cuenta es insuficiente para pagar el pedido.
SERVICE_DISABLED409false · nullEl código de servicio no existe o el servicio está desactivado temporalmente. Toma el código exacto de GET /v1/services.
OUT_OF_STOCK409true · 5El servicio no tiene buzones disponibles (para el tipo de buzón seleccionado) en el momento de la solicitud.
DOMAIN_UNAVAILABLE409false · nullEl servicio no ofrece el tipo de buzón seleccionado: iCloud aún no es compatible / está desactivado temporalmente, o el servicio no tiene buzones gmx.com. Envía domain "gmail.com" (o omite el campo) para alquilar Gmail.
WAREHOUSE_UNAVAILABLE409true · 5 / falseEl almacén elegido no vende este servicio/tipo de buzón, está en pausa o acaba de agotarse. Elige otro almacén de offers[] o envía warehouse = 99 para que el sistema elija uno con stock. Si ese almacén (o todos los almacenes que venden este servicio) no está disponible para tu cuenta, retryable = false: volver a llamar no sirve, usa otro almacén.
ALL_WAREHOUSES_EXHAUSTED409true · 5Solo con warehouse = 99: se probaron todos los almacenes con stock (del más barato al más caro) y todos estaban agotados o fallaban, o todos los almacenes informan de stock cero. No se cobró nada; inténtalo más tarde.
NO_MAILS_AVAILABLE503true · 5El proveedor no tiene buzones disponibles para este servicio (agotado temporalmente).
QUANTITY_EXCEEDED400false · nullquantity supera el máximo por solicitud (actualmente 200).
WAITING_LIMIT_REACHED429false · nullHay demasiados pedidos esperando un código a la vez (se superó el límite de pedidos waiting_code simultáneos de tu cuenta): espera a que terminen algunos y vuelve a alquilar.
ORDER_NOT_CANCELLABLE409false · nullEl pedido no se puede cancelar: puede que ya tenga un código o que ya no esté en estado waiting_code. next-code también devuelve este código si el pedido aún no recibió su primer código.
RERENT_NOT_AVAILABLE409false · nullEl pedido no se puede volver a alquilar. Almacén 3: no es el último pedido del buzón, el buzón ya no está asignado a tu cuenta o la ventana de recepción anterior sigue abierta. Almacén 2: el pedido aún no está cerrado (todavía puede recibir códigos: usa next-code), es demasiado antiguo para volver a alquilarlo, el correo tiene otro pedido activo o el servicio está en pausa. Los almacenes 1 y 4 no tienen esta opción.
RERENT_EMAIL_BANNED409false · nullVolver a alquilar en el almacén 2: el proveedor informa que el servicio bloqueó el correo (email banned); no se puede volver a alquilar y no se cobra.
RERENT_MAIL_GONE409false · nullVolver a alquilar en el almacén 2: el proveedor ya no puede reabrir este correo; alquila uno nuevo. No se cobra.
NEXT_CODE_FEE_CONFIRM409false · nullLa ventana sin costo del pedido del almacén 2 ha terminado: vuelve a llamar a next-code con confirmFee: true para pagar el cargo (nextCodeFee.feePriceVnd). No se ha cobrado nada.
EMAIL_BANNED409false · nullEl servicio ha bloqueado el correo (el proveedor informa de email banned): no puede recibir más códigos; alquila uno nuevo. Desde el 2026-10-02 (antes ORDER_EXPIRED, mismo HTTP 409).
ORDER_EXPIRED409false · nullEl pedido no puede recibir más códigos: pasó su límite de tiempo (rentExpiresAt / nextCodeFee.hardUntil), el proveedor cerró el buzón, se alcanzó el límite de códigos (maxCodes) o el servicio no tiene «siguiente código». El motivo está en order.nextCodeBlockReason.
RATE_LIMITED429true · = Retry-AfterMás de 100 solicitudes/segundo o 3000 solicitudes/minuto con tu clave de API (una cuenta puede tener límites propios). El encabezado Retry-After indica cuántos segundos esperar. En el almacén 2, POST /v1/orders y /rerent también devuelven este código cuando la cuenta canceló muchos pedidos del almacén 2 sin código en los últimos 60 minutos y muy pocos recibieron código: el alquiler en el almacén 2 se pausa hasta que baje la tasa de cancelación ("warehouse": 99 también se detiene ahí con este error; mientras tanto, elige otro almacén). Facebook / Instagram (fb, fbb, ig) están exentos.
DUPLICATE_REQUEST409true · 2 / falseEl Idempotency-Key ya se usó con otro contenido u otro endpoint, o la primera solicitud con esta clave aún se está procesando (reintenta en un momento). Genera una clave nueva para crear otro pedido.
PROVIDER_ERROR502true · 5Error del proveedor de buzones (transitorio: reintenta tras unos segundos).
MAINTENANCE503false · nullEl sistema está en mantenimiento: la creación y la cancelación de pedidos están en pausa.
MAILSHOP_DISABLED503false · nullLa compra de Gmail de vida corta está en pausa, o este producto está desactivado — inténtalo más tarde.
MAILSHOP_PRODUCT_NOT_FOUND404false · nullNo existe ningún producto con este código — toma el código exacto de GET /v1/mail-shop/products.
MAILSHOP_OUT_OF_STOCK409false · nullNo hay stock suficiente para esta cantidad al precio actual — error.details.stock = cuántas cuentas puedes comprar ahora.
MAILSHOP_QTY_LIMIT400false · nullquantity supera el máximo por pedido (actualmente 500) — error.details.max.
MAILSHOP_SUPPLIER_ERROR502false · nullLa fuente rechazó la compra de inmediato: se registró un pedido failed y se reembolsó el importe (error.details.orderId). Inténtalo más tarde con una Idempotency-Key nueva.
INTERNAL500false · nullError inesperado del servidor: inténtalo más tarde y, si se repite, contacta con soporte.

Webhooks (notificaciones push)

Registra una URL por cuenta y le enviaremos por POST un evento JSON firmado en cuanto un pedido reciba un código nuevo (order.code) o se cancele / reembolse (order.cancelled). Los webhooks son opcionales y cubren todos los pedidos de tu cuenta (web y API). La entrega es de mejor esfuerzo: mantén una consulta espaciada como respaldo (ver abajo).
Configuración en 3 pasos
  1. PUT /v1/webhook con tu URL (y opcionalmente events) — guarda data.secret (64 caracteres hexadecimales): lo necesitas para verificar cada entrega. Volver a llamar a PUT conserva el mismo secret.
  2. POST /v1/webhook/test — enviamos al instante un evento ping y devolvemos lo que respondió tu servidor (ok, httpStatus, error).
  3. Tu endpoint debe responder cualquier 2xx en menos de 5 segundos: responde primero y procesa el evento después. Cualquier otra cosa (3xx, 4xx, 5xx, tiempo de espera agotado, error de conexión) cuenta como fallo y se reintenta.
La URL debe empezar por http:// o https:// (con certificado válido) y ser accesible desde internet: localhost, las direcciones privadas, link-local, multicast y reservadas (10.x, 172.16–31.x, 192.168.x, 169.254.x, fc00::/7, fe80::/10…) y los propios servidores de OTPGmail se rechazan con VALIDATION_ERROR. El nombre de host se vuelve a resolver antes de cada entrega. No se siguen redirecciones: registra la URL final.
Formato de la petición
Cada entrega es un POST HTTP a tu URL con cuerpo JSON (UTF-8) y estas cabeceras:
CabeceraValor
Content-Typeapplication/json
User-AgentOTPGmail-Webhook/1
X-OTPGmail-EventNombre del evento: order.code, order.cancelled o ping
X-OTPGmail-DeliveryID único del evento (UUID): igual a deliveryId del cuerpo y no cambia en los reintentos
X-OTPGmail-TimestampHora Unix (segundos) en que se firmó este intento: igual a sentAt del cuerpo
X-OTPGmail-Signaturesha256= + hex en minúsculas del HMAC-SHA256 de timestamp + "." + cuerpo sin procesar, con tu secret como clave
Verificar la firma
Calcula el HMAC-SHA256 con tu secret como clave sobre X-OTPGmail-Timestamp + . + el cuerpo sin procesar de la petición, en hex en minúsculas, y compáralo en tiempo constante con el valor que sigue a sha256=. La clave es la propia cadena secret de 64 caracteres (sus bytes UTF-8; no la decodifiques como hex). Usa los bytes sin procesar: analizar y volver a serializar el JSON los cambia. Rechaza las marcas de tiempo que se alejen más de 5 minutos de tu reloj (protección contra repeticiones).
Ejemplos de receptor (Python y Node solo usan la biblioteca estándar; el comando curl envía a tu propio receptor una entrega de prueba firmada correctamente)
# Prueba local: envía a tu receptor una entrega firmada exactamente como las nuestras
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
EventoSe envía cuando
order.codeUn pedido recibió un código nuevo: un evento por código, incluidos los códigos siguientes en el mismo buzón. code es el código nuevo; otp enumera todos los códigos del pedido hasta ese momento, del más antiguo al más nuevo (como en GET /v1/orders/{id}; solo el código nuevo si el cuerpo superara los 64 KB).
order.cancelledUn pedido se canceló o se reembolsó, una vez por pedido: no llegó ningún código antes del plazo (timeout), lo cancelaste tú (user) o un administrador (admin), o se reembolsó porque falló el almacén (provider). refundVnd = VND devueltos a tu saldo (0 cuando las reglas del servicio no reembolsan).
pingSolo desde POST /v1/webhook/test.
Campos del evento
CampoTipoDescripción
eventstringNombre del evento (también en la cabecera X-OTPGmail-Event)
deliveryIdstringID único del evento (UUID, = X-OTPGmail-Delivery): guárdalo para omitir duplicados
sentAtnumberHora Unix de este intento (= X-OTPGmail-Timestamp)
orderIdstringID del pedido: el mismo que en GET /v1/orders/{id}
servicestringCódigo del servicio
warehouse1 | 2 | 3 | 4 | nullAlmacén 1–4 que entregó el buzón; null si el pedido nunca recibió uno
domainstringDominio del buzón: gmail.com, icloud.com o gmx.com
emailstring | nullDirección del buzón alquilado
codestringSolo order.code: el código nuevo; también puede ser un enlace o un correo completo, como otp[].code
receivedAtnumberSolo order.code: hora Unix en que llegó el código
statusstringEstado del pedido en ese momento: completed para order.code, cancelled para order.cancelled (si un administrador reembolsa un pedido que ya tenía códigos, se queda en completed)
otparraySolo order.code: todos los códigos hasta ese momento, [{code, receivedAt}], del más antiguo al más nuevo
reasonstringSolo order.cancelled: timeout | user | admin | provider
refundVndnumberSolo order.cancelled: VND reembolsados a tu saldo (0 = sin reembolso)
Cuerpo de ejemplo — 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
    }
  ]
}
Cuerpo de ejemplo — 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"
}
Cuerpo de ejemplo — ping
{
  "event": "ping",
  "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f",
  "sentAt": 1760700000
}
Reintentos, pausa e historial
  • Una entrega fallida se reintenta tras 10 s, 1 min, 5 min, 30 min y 2 h (5 reintentos); si el último también falla, el evento se descarta (failed).
  • 50 intentos fallidos seguidos (sumando todos los eventos) pausan el webhook: GET /v1/webhook muestra pausedAt, no se envía nada y los eventos nuevos no se ponen en cola.
  • Para reanudar: vuelve a llamar a PUT /v1/webhook (puede ser la misma URL) o logra un POST /v1/webhook/test correcto; después se envían los eventos que esperan en la cola.
  • GET /v1/webhook/deliveries muestra las últimas 100 entregas con su estado, intentos y último error; el historial se guarda 7 días.
Duplicados y orden
El mismo evento puede llegar más de una vez (por ejemplo, si se perdió tu respuesta 2xx) y los eventos pueden llegar desordenados (se envían varios en paralelo). Guarda los deliveryId ya procesados y omite las repeticiones; para cada pedido, confía en sus datos más recientes (otp, status) y no en el orden de llegada.
Mantén la consulta como respaldo
Los webhooks pueden retrasarse (reintentos) o perderse (webhook en pausa, tu servidor caído). Mantén una consulta espaciada —por ejemplo, GET /v1/orders/{id} cada 30–60 s para los pedidos que siguen en waiting_code— y toma el objeto de pedido de la API como fuente de verdad.
PUT/v1/webhook
Registra o actualiza el webhook de tu cuenta. La primera llamada lo crea y devuelve un secret nuevo; las siguientes conservan el secret, vuelven a activar el webhook y lo reanudan si estaba en pausa. Sin events: ambos eventos para un webhook nuevo, sin cambios para uno existente. La URL debe ser una dirección pública; si no, 400 VALIDATION_ERROR (error.details.problem indica el motivo).
Parámetros
NombreTipoObligatorioDescripción
urlstringSíTu endpoint (http:// o https://, dirección pública, máximo 2048 caracteres)
eventsstring[]NoQué eventos enviar: ["order.code", "order.cancelled"] (por defecto ambos)
Ejemplo
# Guarda data.secret: lo necesitas 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"]}'
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: UNAUTHORIZED, VALIDATION_ERROR
GET/v1/webhook
El webhook de tu cuenta, con su secret, pausedAt y consecutiveFailures. data es null si no hay ningún webhook registrado.
Ejemplo
curl "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
  }
}
Errores posibles: UNAUTHORIZED
DELETE/v1/webhook
Elimina el webhook; los eventos que esperan en la cola se descartan. Siempre 200: data.deleted es false si no había nada que eliminar.
Ejemplo
curl -X DELETE "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "success": true,
  "data": {
    "deleted": true
  }
}
Errores posibles: UNAUTHORIZED
POST/v1/webhook/test
Envía ahora mismo un evento ping a tu URL e informa del resultado: ok (tu servidor respondió 2xx), httpStatus, durationMs, error. Una prueba correcta reanuda un webhook en pausa. Como máximo 10 pruebas por minuto; 503 MAINTENANCE mientras los webhooks estén desactivados en todo el sistema.
Ejemplo
curl -X POST "https://otpgmail.net/v1/webhook/test" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "success": true,
  "data": {
    "ok": true,
    "httpStatus": 200,
    "durationMs": 143,
    "error": null,
    "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f"
  }
}
Respuesta 200 cuando tu servidor no responde 2xx
{
  "success": true,
  "data": {
    "ok": false,
    "httpStatus": 500,
    "durationMs": 88,
    "error": "HTTP 500",
    "deliveryId": "7d2b9f4e-1c6a-4e3b-8d5f-0a9c2e7b1f4d"
  }
}
Errores posibles: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED, MAINTENANCE
GET/v1/webhook/deliveries
Últimas entregas, las más recientes primero (se guardan 7 días): status = queued (próximo intento en nextAt) | delivered | failed | paused, con attempts, lastHttpStatus y lastError. No se devuelve el cuerpo de los eventos.
Parámetros
NombreTipoObligatorioDescripción
limitnumberNoNúmero de entregas, 1–100 (por defecto 100)
Ejemplo
curl "https://otpgmail.net/v1/webhook/deliveries?limit=20" \
  -H "Authorization: Bearer og_xxx..."
Respuesta de ejemplo
{
  "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
    }
  ]
}
Errores posibles: UNAUTHORIZED