应用专属折扣任何每天租用 OTP Gmail 超过 2000 个 Gmail 用于任何服务(Facebook、Instagram 和 Apple 除外)并希望获得专属折扣的朋友,欢迎随时联系我!对于注册账号数量足够大的用户,最高可享受原价 50% 的折扣。
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

API 文档

公开 API /v1——使用 API 密钥认证,通过你自己的软件自动租用邮箱

你的 API 密钥
你尚未登录,请登录后获取你自己的 API 密钥。
请将此密钥放在 Authorization: Bearer <key> 请求头或 ?api_key=<key> 查询参数中发送。点击眼睛图标可显示完整密钥;需要更换时点击生成新密钥(旧密钥立即失效)。

概览

基础 URL
https://otpgmail.net
认证
请在每个请求中携带你的 API 密钥,放在 Authorization: Bearer og_xxx... 请求头中(推荐)或 ?api_key=og_xxx... 查询字符串中。
Idempotency-Key(防止重复下单)
每个 POST /v1/orders 请求都应包含 Idempotency-Key: <每个订单一个新 UUID> 请求头。如果请求超时或连接中断,请使用相同的密钥再次发送,服务器会返回原来的响应,而不会再创建一个订单。每个新订单请生成新的 UUID,仅在重试时才复用同一个 UUID。
一次处理多个订单:单次调用查询状态
要租用几十、几百个邮箱?请一次读取多个订单的状态:POST /v1/orders/status 每次调用最多返回 100 个订单(在速率限制中只算一次请求),GET /v1/orders 列出你的订单——重启后用 ?status=waiting_code 即可取回所有仍在等待验证码的订单。每个错误也会告诉你是否值得重试(error.retryable、error.retryAfterSec)。详情请见多订单状态一节。
邮箱类型:Gmail 或 iCloud
默认获得 @gmail.com 邮箱。如需租用 @icloud.com 邮箱,请在 POST /v1/orders 的请求体中加入 "domain": "icloud.com"。省略 domain 即表示 gmail.com,现有对接无需任何修改即可继续使用。iCloud 仅适用于在 GET /v1/services 中 icloud 字段不为 null 的服务;iCloud 的价格可能与 Gmail 不同(见 icloud.price)。每个订单响应都包含 domain 字段,说明该订单使用的邮箱类型。部分服务还提供 @gmx.com 邮箱(目前仅 Pokemon,代码 pkm,仓库 2):发送 "domain": "gmx.com"。服务是否提供该类型,取决于其在 GET /v1/services 的 offers[] 中是否有 domain = "gmx.com" 的条目;其他任何服务都会返回 DOMAIN_UNAVAILABLE。gmx.com 不适用于批量租用。
仓库:一个服务,多个邮箱来源
同一个服务可以从多个仓库出售,每个仓库有各自的价格、库存和验证码规则(GET /v1/services 中的 offers[] 数组)。创建订单时发送 "warehouse": 2 可指定仓库;"warehouse": 99 ⇒ 系统会选择有库存的最便宜仓库,缺货时自动换下一个(适合 Facebook、Instagram 等经常缺货的服务);0 ⇒ 有库存的最便宜仓库。现有客户端无需修改:省略 warehouse ⇒ 与以前一样使用仓库 1,price、stock 和 icloud 描述的都是仓库 1(没有仓库 1 的服务 ⇒ 有库存的最便宜仓库)。详情请见仓库。
批量租用(批次)
批次需在网页端购买(批量租用页面);API 可用于列出批次和读取验证码。Gmail(仓库 1):每个邮箱都有一个 proxyUrl,即无需 API 密钥的验证码链接。iCloud(仓库 3):私有邮箱;使用 GET /v1/batches/{id}/mails/{mailId}/codes 读取验证码,或在网页端打开 readUrl。详情请见批量租用一节。
购买短效 Gmail 账号(mail shop)
除了租用邮箱,你还可以从 2 个仓库买断完整的 Gmail 账号——仓库 1:邮箱、密码、Cookie + 收件箱链接(也可取 JSON);仓库 2:邮箱、密码 + 快速登录链接;仓库 99 会先买最便宜的(对应网页端的购买短效 Gmail页面)。该页面能做的事,API 全都能做:查看仓库、价格和库存,购买(必须携带 Idempotency-Key),跟踪交付进度,下载账号以及读取邮件。详情请见购买短效 Gmail一节。
让你的客户自己查看验证码(/otp 页面)
你是否在转售自己租用的邮箱(通过网页或 API)?请在账户页面开启分享码,然后把链接 https://otpgmail.net/zh/otp#k7m2q9:[email protected](k7m2q9 = 你的分享码)发给客户。客户无需登录即可看到该邮箱的最新验证码,看不到价格或你的余额;该页面只执行免费操作(绝不会从你的账户扣费)。
供工具 / 机器人使用的 JSON 链接(你的或你客户的):使用路径形式的链接 https://otpgmail.net/zh/otp/k7m2q9:[email protected](与 # 链接一样会打开 /otp 页面),在末尾加上 ?json ⇒ 服务器返回 JSON,内容与页面显示的数据完全相同:cards[]——该邮箱上每个租用中的服务一张卡片(email、serviceName、state、items[] = 最新的最多 5 个验证码 / 链接,新的在前),以及 serverTime。state:waiting 等待第一个验证码 · listening 等待新验证码 · received 已收到验证码 · needs_owner 需要卖家续期 · expired 已过期 · banned 邮箱被锁。多个邮箱:用逗号分隔(最多 50 个)。无需 API 密钥和 Cookie,CORS 开放;只读(“加载新 OTP”/“再次租用此邮箱”仍在 /otp 页面上)。防猜测限制与页面相同:分享码错误 / 该邮箱没有可显示的订单 ⇒ 404 NOT_FOUND;多次出错或同一邮箱每分钟读取超过 30 次 ⇒ 429 RATE_LIMITED,附带 retryAfter(秒)。在 /otp#… 链接后加 ?json 不会返回 JSON:浏览器从不把 # 之后的部分发送给服务器。
curl "https://otpgmail.net/zh/otp/k7m2q9:[email protected]?json"
{
  "success": true,
  "data": {
    "cards": [
      {
        "ref": "Q3vX9kL2mP0aB7cD",
        "serviceCode": "fb",
        "serviceName": "Facebook",
        "logo": "/logos/fb.png?v=20261007",
        "domain": "gmail.com",
        "email": "[email protected]",
        "state": "received",
        "until": null,
        "items": [
          {
            "kind": "code",
            "value": "529170",
            "label": null,
            "at": 1760000405
          },
          {
            "kind": "code",
            "value": "381904",
            "label": null,
            "at": 1759999030
          }
        ],
        "total": 2,
        "canLoad": true
      }
    ],
    "serverTime": 1760000530
  }
}
GitHub 上的完整示例代码
可直接运行的 Python、Node.js、PHP、C# (.NET) 和 curl 客户端:租用邮箱、等待验证码、自动取消并退款,缺货时在 iCloud 与 Gmail 之间切换:github.com/letoan0902/otpgmail-email-otp-api(英文)· thue-otp-gmail(越南语)。Python 注意事项:请使用 requests,不要使用裸 urllib(其默认 User-Agent 会被 CDN 返回 403)。
Webhook
无需不停轮询每个订单:注册一个 URL,一旦订单收到新验证码或被取消 / 退款,就会收到一条带签名的 POST——见 Webhook。

接口

GET/v1/balance
返回请求所用 API 密钥对应账户的余额(单位:VND)。
示例
curl "https://otpgmail.net/v1/balance" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "balance": 25000
  }
}
可能的错误: UNAUTHORIZED
GET/v1/services
列出已启用的服务:代码、名称、Gmail 价格(单位:VND)和当前 Gmail 库存;一个 icloud 块,包含 @icloud.com 邮箱的价格和库存;以及一个 offers[] 数组:出售该服务的每个仓库,每个“仓库 × 邮箱类型”一条记录(价格、库存、验证码数量、规则)。数据有缓存:库存约每 30–60 秒刷新一次,价格刷新频率更低。
icloud = null 表示该服务暂不支持 iCloud(服务商不提供,或已暂时停用),只能租用 Gmail。对于有 icloud 块的服务,创建订单时发送 "domain": "icloud.com",即可按 icloud.price 获得 @icloud.com 邮箱。price / stock / icloud.price 描述的是仓库 1,即未指定 warehouse 的订单所在的仓库(没有仓库 1 的服务 ⇒ 有库存的最便宜仓库);各仓库的价格和库存见 offers[](参见“仓库”)。 stock 是货源公布的数量(仓库 1:约每 30 秒刷新一次),并不保证一次调用能租到这么多:邮箱是逐个分配的,货源可能中途用完——这时 POST /v1/orders 会返回较少的订单并附带 partial 字段。仓库 2 和 4 显示的也是各自货源的数量。
示例
curl "https://otpgmail.net/v1/services" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": [
    {
      "code": "dr",
      "name": "OpenAI (ChatGPT)",
      "price": 350,
      "stock": 616525,
      "icloud": {
        "price": 630,
        "stock": 12464
      },
      "offers": [
        {
          "warehouse": 2,
          "domain": "gmail.com",
          "price": 350,
          "stock": 616525,
          "maxCodes": null,
          "rules": null,
          "fee": {
            "freeHours": 6,
            "paidPct": 50,
            "maxHours": 24
          },
          "isDefault": true
        },
        {
          "warehouse": 1,
          "domain": "gmail.com",
          "price": 630,
          "stock": 12464,
          "maxCodes": 3,
          "rules": null,
          "fee": null,
          "isDefault": false
        },
        {
          "warehouse": 1,
          "domain": "icloud.com",
          "price": 630,
          "stock": 12464,
          "maxCodes": 5,
          "rules": null,
          "fee": null,
          "isDefault": true
        }
      ],
      "note": null
    },
    {
      "code": "fb",
      "name": "Facebook",
      "price": 1590,
      "stock": 88,
      "icloud": null,
      "offers": [
        {
          "warehouse": 1,
          "domain": "gmail.com",
          "price": 1590,
          "stock": 88,
          "maxCodes": 10,
          "rules": null,
          "fee": null,
          "isDefault": false
        },
        {
          "warehouse": 4,
          "domain": "gmail.com",
          "price": 1580,
          "stock": 2310,
          "maxCodes": null,
          "rules": null,
          "fee": null,
          "isDefault": true
        }
      ],
      "note": null
    }
  ]
}
可能的错误: UNAUTHORIZED
POST/v1/orders
创建一个接收验证码的邮箱订单,默认是 Gmail,发送 domain: "icloud.com" 时则为 iCloud。服务商一分配邮箱,就会从你的余额扣费。返回订单列表(一个订单,quantity > 1 时为多个)。
部分交付:quantity 只是上限
  • 邮箱是逐个分配的。如果服务商中途缺货(例如此刻仓库 1 该服务已没有邮箱),调用会停止,并以 HTTP 200 返回已创建的订单——data 可能少于 quantity,且只对 data 中的订单扣费。GET /v1/services 中的 stock 并不保证一次调用能拿到多少。
  • 此时响应在 data 旁还会带一个顶层字段 partial(全部交付时不会出现,因此只读 data 的机器人无需任何修改):requested、delivered、missing 和 reason = {code, message, retryable, retryAfterSec},其中 code 是导致调用停止的错误(NO_MAILS_AVAILABLE、OUT_OF_STOCK、ALL_WAREHOUSES_EXHAUSTED、INSUFFICIENT_BALANCE、WAITING_LIMIT_REACHED…)。
  • 缺货通常只是暂时的——在仓库 1,服务商往往几秒后又会有邮箱。当 reason.retryable = true 时,等待 reason.retryAfterSec 秒(3–5 秒),然后用新的 Idempotency-Key 再请求缺少的 missing 个邮箱(旧密钥只会原样返回这次的响应);如此重复直到凑齐。
  • 部分交付后紧接着的调用可能返回 409 WAREHOUSE_UNAVAILABLE / OUT_OF_STOCK 或 503 NO_MAILS_AVAILABLE,这是正常现象:调用之间继续间隔 3–5 秒,只有连续多次调用都失败(例如 10 次、约一分钟)才放弃;之后可稍后再试,或发送 "warehouse": 99 让系统自动切换到其他仓库。retryable = false(INSUFFICIENT_BALANCE、WAITING_LIMIT_REACHED…)⇒ 请先解决原因(充值,或等待未完成的订单结束)。
  • 错误响应的 error 中也带有同样的两个字段:error.retryable 和 error.retryAfterSec(例如 409 OUT_OF_STOCK ⇒ true、5;402 INSUFFICIENT_BALANCE ⇒ false、null)——参见错误码。
必须携带 Idempotency-Key 请求头(UUID)。每次新的租用请生成新的 UUID;超时后重试时请复用同一个密钥,以免创建重复订单。此调用会等待服务商分配邮箱,通常约需 0.3 秒,高峰期(少见)最长可达 10 秒,因此请将客户端超时设为约 15 秒,不要在 5 秒后就放弃并提前重试。
参数
名称类型必填说明
servicestring是服务代码(来自 GET /v1/services,例如 "ig"、"tg"、"fb")
quantitynumber否要租用的邮箱数量上限(默认 1;单次请求最多 200)——若仓库中途缺货,实际返回会更少:请以 data[] 的数量为准,并查看 partial
domainstring否邮箱类型:"gmail.com"(默认)、"icloud.com" 或 "gmx.com"。省略 = gmail.com,现有对接无需修改。仅当服务在 GET /v1/services 中的 icloud 字段不为 null 时才能租用 iCloud;"gmx.com" 仅适用于 offers[].domain = "gmx.com" 的服务(目前仅 Pokemon,代码 pkm),否则调用会返回 DOMAIN_UNAVAILABLE。其他任何值 ⇒ VALIDATION_ERROR。
warehousenumber否要租用的仓库。省略 = 仓库 1,与多仓库功能上线前完全一致(没有仓库 1 的服务回退到最便宜的仓库),现有客户端无需修改。0 = 有库存的最便宜仓库(只会尝试一个仓库)。1–4 = 指定仓库(来自 GET /v1/services 中的 offers[].warehouse);如果该仓库不出售此服务/邮箱类型、已暂停或缺货 ⇒ WAREHOUSE_UNAVAILABLE。99 = 自动:按价格从低到高依次尝试有库存的仓库,某个仓库缺货或失败时自动换下一个;全部用尽 ⇒ ALL_WAREHOUSES_EXHAUSTED。推荐用于经常缺货的服务(Facebook、Instagram 等)。参见“仓库”。
示例
# 为每个新订单生成一个随机的 Idempotency-Key
# 超时后重试时复用同一个密钥(防止重复下单)
# domain: "gmail.com"(默认,可选)| "icloud.com"(租用 @icloud.com 邮箱)| "gmx.com"(仅限提供该类型的服务,例如 Pokemon)
# warehouse: 0 | 1–4 | 99(可选,省略则与以前一样使用仓库 1;0 = 有库存的最便宜仓库;99 = 自动,仓库缺货时自动换下一个;见 GET /v1/services 中的 offers[])
curl -X POST "https://otpgmail.net/v1/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service":"ig","quantity":1,"domain":"gmail.com"}'
响应示例
{
  "success": true,
  "data": [
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ]
}
响应示例——部分交付(quantity 5,交付 2 个邮箱)
{
  "success": true,
  "data": [
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    {
      "orderId": "jkl012mno",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270001,
      "rentExpiresAt": 1753356401,
      "createdAt": 1753268201,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ],
  "partial": {
    "requested": 5,
    "delivered": 2,
    "missing": 3,
    "reason": {
      "code": "NO_MAILS_AVAILABLE",
      "message": "No inboxes available for this service right now, please try again later",
      "retryable": true,
      "retryAfterSec": 5
    }
  }
}
可能的错误: UNAUTHORIZED, VALIDATION_ERROR, SERVICE_DISABLED, INSUFFICIENT_BALANCE, OUT_OF_STOCK, DOMAIN_UNAVAILABLE, WAREHOUSE_UNAVAILABLE, ALL_WAREHOUSES_EXHAUSTED, NO_MAILS_AVAILABLE, QUANTITY_EXCEEDED, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCE
GET/v1/orders/{id}
按 ID 返回订单,包括目前已收到的验证码(otp[])。订单处于 waiting_code 状态时,请每 3–5 秒轮询此接口以获取验证码。
otp[] 数组按到达顺序排列:最早的在前,最新的在最后,请用 otp[otp.length - 1] 读取最新的验证码。每次调用时,系统都会按各仓库自己的节奏(每隔几秒)向服务商检查仍处于开启状态的订单,调用得更频繁只会返回已存储的数据。收到第一个验证码后请继续轮询:后续的验证码(如果服务发送了多个)会在订单有效期内追加到 otp[] 的末尾,不另收费;数量上限和时间窗口会在租用前显示(offers[] 中的 maxCodes 和 fee);如需确保系统正在等待新验证码,请调用 POST /v1/orders/{id}/next-code(适用于所有仓库;只有超出免费时段的仓库 2 才会收费)。如果服务每个邮箱只允许一个验证码,otp[] 会停留在该验证码,响应保持正常,不会报错。warehouse 字段说明由哪个仓库(1–4)提供了该邮箱;nextCodeFee 是获取更多验证码的费用(仓库 2 和仓库 3 的订单;仓库 1 和 4 为 null),参见“仓库”。 自 2026-10-02 起,订单还带有 nextCodeBlocked(true = 该邮箱无法再接收验证码)和 nextCodeBlockReason(原因):"email_banned"(邮箱已被对应服务锁定,请租用新邮箱)、"activation_gone"(服务商已关闭该邮箱)、"hard_expired"(验证码时段已结束)、"max_codes"(已收满验证码数量上限)、null(未被阻止 / 没有记录原因的旧订单)。这些都是新增字段,现有客户端无需修改。 要轮询很多订单?不要每个订单单独调用一次 GET,而是每次最多把 100 个 ID 发送到 POST /v1/orders/status——订单对象完全相同,速率限制只算一次请求。
参数
名称类型必填说明
idstring是订单 ID(创建订单时返回的 orderId)
示例
curl "https://otpgmail.net/v1/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "orderId": "abc123xyz",
    "service": "ig",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "completed",
    "price": 1060,
    "otp": [
      {
        "code": "123456",
        "receivedAt": 1753268500
      },
      {
        "code": "789012",
        "receivedAt": 1753269000
      }
    ],
    "codeDeadlineAt": 1753270000,
    "rentExpiresAt": 1753356400,
    "createdAt": 1753268200,
    "warehouse": 1,
    "nextCodeFee": null,
    "listening": true,
    "listenUntil": 1753270500,
    "nextCodeBlocked": false,
    "nextCodeBlockReason": null
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND
POST/v1/orders/{id}/cancel
取消订单并退还 100% 的价格。仅当订单处于 waiting_code 状态且尚未收到任何验证码时才允许取消。
已收到验证码的订单(哪怕只有一个)不能取消或退款,网站和 API 都一样。 rules.cancellable = false 的订单(自有库存的服务,如 Cointiply)无法取消:邮箱一经分配,就不能取消或退款,取消调用会返回 ORDER_NOT_CANCELLABLE。常规服务的仓库 3 订单(rules.cancellable = true)在验证码到达前可以像其他订单一样取消。
参数
名称类型必填说明
idstring是要取消的订单 ID
示例
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/cancel" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json"
响应示例
{
  "success": true,
  "data": {
    "orderId": "abc123xyz",
    "service": "ig",
    "domain": "gmail.com",
    "email": "[email protected]",
    "status": "cancelled",
    "price": 1060,
    "otp": [],
    "codeDeadlineAt": 1753270000,
    "rentExpiresAt": 1753356400,
    "createdAt": 1753268200,
    "warehouse": 1,
    "nextCodeFee": null,
    "listening": false,
    "listenUntil": null,
    "nextCodeBlocked": false,
    "nextCodeBlockReason": null
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR
POST/v1/orders/{id}/rerent
为自有库存服务(provider = "inventory",例如 Cointiply)的订单再次租用同一个邮箱:在该邮箱上创建一个全新接收时段的新订单,价格为旧订单的 rerentPriceVnd。
仅当旧订单的 canRerent = true 时有效(该邮箱的最新订单,且其上一个时段已结束或已收完所有邮件)。成功后立即扣费,请携带 Idempotency-Key 并在重试时复用,以免被重复扣费。响应为 { order, message };order 的结构与 GET /v1/orders/{id} 相同。 仓库 2(自 2026-10-09 起):已取消、已超时或已过期(邮箱已关闭)的订单,当 canRerent = true 时可调用(该邮箱的最新订单,且该邮箱没有其他进行中的订单)。系统会把原来的那个邮箱重新打开为一个新订单(status = waiting_code),价格为 rerentPriceVnd(目前为你价格的 100%),并像任何仓库 2 订单一样等待邮件 20 分钟,没有邮件则取消并 100% 退款。邮箱被对应服务锁定 ⇒ RERENT_EMAIL_BANNED;服务商已无法重新打开 ⇒ RERENT_MAIL_GONE;仓库 2 暂时不可用 ⇒ PROVIDER_ERROR,这三种情况都不会扣费。
参数
名称类型必填说明
idstring是该邮箱上一个订单的 ID(canRerent = true 的那个)
示例
# 超时后重试时复用同一个密钥(防止重复下单)
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/rerent" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Idempotency-Key: $(uuidgen)"
响应示例
{
  "success": true,
  "data": {
    "order": {
      "orderId": "def456uvw",
      "service": "ctp",
      "domain": "icloud.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 3500,
      "otp": [],
      "codeDeadlineAt": 1753273500,
      "rentExpiresAt": 1753273500,
      "createdAt": 1753269000,
      "provider": "inventory",
      "rules": {
        "windowMinutes": 75,
        "maxCodes": 4,
        "cancellable": false,
        "refundOnTimeout": false,
        "rerentRatio": 0.5
      },
      "windowEndsAt": 1753273500,
      "canRerent": false,
      "rerentPriceVnd": 3500,
      "warehouse": 3,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    "message": "Inbox rented again — a new 75-minute message window has started"
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, RERENT_NOT_AVAILABLE, RERENT_EMAIL_BANNED, RERENT_MAIL_GONE, INSUFFICIENT_BALANCE, WAITING_LIMIT_REACHED, RATE_LIMITED, DUPLICATE_REQUEST, PROVIDER_ERROR, MAINTENANCE
POST/v1/orders/{id}/next-code
请求在同一个邮箱上获取下一个验证码,适用于所有仓库(仓库 1–4)中已收到第一个验证码(status = completed)的订单。免费,但免费时段已结束的仓库 2 订单除外(nextCodeFee.feePriceVnd > 0):此时需发送 confirmFee: true 来支付费用并开启新的验证码时段(最长不超过硬性期限 nextCodeFee.hardUntil)。你的机器人无需知道是哪个仓库:继续轮询 GET /v1/orders/{id},新验证码会追加到 otp[] 中;想让系统等待新验证码时,再调用此接口。
此调用在各仓库中的作用
  • 仓库 1:在订单有效期(rentExpiresAt)内,每收到一个验证码后系统都会自动等待下一个,此调用返回 200,不做任何改动;一旦该邮箱无法再接收验证码(已达验证码上限、邮箱已关闭、已过期)⇒ 返回 409,并带有 nextCodeBlockReason。
  • 仓库 2:在服务商处重新打开邮箱(reordered = true),并等待邮件 20 分钟;免费时段内免费(nextCodeFee.feePriceVnd = 0),超出后必须发送 confirmFee = true(缺少 ⇒ 409 NEXT_CODE_FEE_CONFIRM,不扣费)。只有收到新验证码时才会保留这笔费用,20 分钟内没有验证码 ⇒ 自动退款。每收到一个验证码后,系统会再继续等待下一个 25 分钟(listening = true,listenUntil)。
  • 仓库 3(常规服务):重新开启一个短暂的监听时段,免费时段内免费(nextCodeFee.freeUntil);之后 ⇒ 409,请使用 POST /v1/orders/{id}/rerent。Cointiply 等自有库存服务(rules.cancellable = false)没有“下一个验证码”(409):每封邮件都会在时段内自动出现。
  • 仓库 4:在服务商处重新读取订单并等待新验证码,在 rentExpiresAt 之前免费。
200 = 系统正在为此订单等待新验证码(listening = true,listenUntil = 停止等待的时间;刚重新打开邮箱时 reordered = true;扣费时有 chargedVnd),或在你调用时恰好有新验证码到达(已在 order.otp 中)。409 = 无法开启新的等待时段,请查看 error.code;邮箱无法再接收验证码的原因见 order.nextCodeBlockReason(GET /v1/orders/{id})。仍在等待第一个验证码(waiting_code)的订单 ⇒ 409,暂时无需调用。付费调用请携带 Idempotency-Key(重试时复用,以免被重复扣费)。服务商无法连接 ⇒ 502 PROVIDER_ERROR,请稍后再试。 此调用的 409 错误码:NEXT_CODE_FEE_CONFIRM(仓库 2 需要付费,请带上 confirmFee: true 再次调用)、EMAIL_BANNED(邮箱已被对应服务锁定,请租用新邮箱;error.details.blockReason = "email_banned")、ORDER_EXPIRED(已过期、邮箱已关闭、已达验证码上限,或该服务没有“下一个验证码”,原因见 order.nextCodeBlockReason)、ORDER_NOT_CANCELLABLE(订单尚未收到第一个验证码)。把此调用返回的任何 409 都当作“没有更多验证码”的机器人无需修改即可继续使用。 refresh: true(仓库 2):先检查是否有新邮件(刚到的直接交付,不重新打开),再在服务商处重新打开邮箱——免费时段内等待重新计时 20 分钟;免费时段外等待截止时间(listenUntil)不变,也不扣费。响应额外包含 refreshed(true = 刚刚重新打开)和 refreshAvailableAt(unix 秒;在此之前再次刷新只会检查邮件;null = 现在即可刷新)。其他仓库:无效果(refreshed = false)。不发送 refresh 时,调用与以前完全一样。
参数
名称类型必填说明
idstring是订单 ID(orderId),任何仓库均可
confirmFeeboolean否true = 你同意支付 nextCodeFee.feePriceVnd 以开启新的验证码时段。feePriceVnd = 0 时无需此参数。
refreshboolean否true = 仓库 2:订单正在等待新验证码(listening = true)却迟迟不到时,立即在服务商处重新打开邮箱(刷新等待)——旧的等待在服务商那边可能已不再收信。免费;每个订单每 5 秒最多一次。订单不在等待中 ⇒ 与普通调用相同。
示例
# 免费(nextCodeFee 为 null 或 feePriceVnd = 0,即除免费时段结束后的仓库 2 之外的所有仓库):无需 confirmFee
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
  -H "Authorization: Bearer og_xxx..."

# 免费时段结束后的仓库 2:发送 confirmFee = true 以支付 nextCodeFee.feePriceVnd
curl -X POST "https://otpgmail.net/v1/orders/ORDER_ID/next-code" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"confirmFee":true}'
响应示例
{
  "success": true,
  "data": {
    "order": {
      "orderId": "ghi789rst",
      "service": "dr",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "completed",
      "price": 350,
      "otp": [
        {
          "code": "482913",
          "receivedAt": 1753268300
        }
      ],
      "codeDeadlineAt": 1753269700,
      "rentExpiresAt": 1753354200,
      "createdAt": 1753267800,
      "warehouse": 2,
      "nextCodeFee": {
        "feePriceVnd": 0,
        "freeUntil": 1753311000,
        "hardUntil": 1753354200,
        "paidPct": 50,
        "freeHours": 6
      },
      "listening": true,
      "listenUntil": 1753269500,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    "message": "Another code window is open — waiting for a new code",
    "listening": true,
    "listenUntil": 1753269500,
    "reordered": true
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, NEXT_CODE_FEE_CONFIRM, INSUFFICIENT_BALANCE, EMAIL_BANNED, ORDER_EXPIRED, ORDER_NOT_CANCELLABLE, PROVIDER_ERROR, DUPLICATE_REQUEST, MAINTENANCE

单次调用查询多个订单状态

租用大量邮箱的代理商和机器人不应该为每个订单单独发送 GET /v1/orders/{id}:POST /v1/orders/status 用相同的订单对象每次调用最多读取 100 个订单,GET /v1/orders 则从已存储的数据列出你的订单。
  • 用 POST /v1/orders/status 每 3–5 秒轮询一次,每次调用最多 100 个订单 ID——每次调用在速率限制中只算一次请求。
  • 每次调用,系统最多为列表中 20 个未结束的订单向服务商检查(先检查等待第一个验证码的订单,再检查最久未检查的订单),其余订单按已存储的数据返回;它们会在之后的调用中检查。一次调用约 3 秒内返回。
  • 订单一旦结束就从列表中移除:cancelled(已退款),或在不再需要验证码时的 completed。
  • 不存在或属于其他账号的 ID 会出现在 missing 中(不返回 404)——也把它们移除。
  • 重启后,用 GET /v1/orders?status=waiting_code 取回所有未结束的订单(沿 nextCursor 翻页),然后继续用 POST /v1/orders/status 轮询。
POST/v1/orders/status
一次调用最多返回你的 100 个订单。每个元素与该订单的 GET /v1/orders/{id} 返回内容完全一致(status、包含所有已收到验证码的 otp[]、warehouse…)。用于同时轮询多个未结束的订单。
data 保持 ids 的顺序;重复的 ID 会被忽略。不存在或属于其他账号的 ID 会列在 missing 中(不返回 404)。对于未结束的订单,系统会像 GET /v1/orders/{id} 一样向服务商检查,但每次调用最多检查 20 个订单——先检查等待第一个验证码的订单,再检查最久未检查的订单——其余订单按已存储的数据返回(会在之后的调用中检查);一次调用约 3 秒内返回。无论包含多少个 ID,一次调用在速率限制中都只算一次请求。超过 100 个 ID、空列表或非字符串 ID ⇒ 400 VALIDATION_ERROR。
参数
名称类型必填说明
idsstring[]是订单 ID(orderId)——每次调用 1 到 100 个
示例
# 你在等待的订单 ID(POST /v1/orders 返回的 orderId)——每次调用最多 100 个
curl -X POST "https://otpgmail.net/v1/orders/status" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -d '{"ids":["ORDER_ID_1","ORDER_ID_2"]}'
响应示例
{
  "success": true,
  "data": [
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "completed",
      "price": 1060,
      "otp": [
        {
          "code": "123456",
          "receivedAt": 1753268500
        }
      ],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    {
      "orderId": "jkl012mno",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270001,
      "rentExpiresAt": 1753356401,
      "createdAt": 1753268201,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ],
  "missing": [
    "zzz999qqq"
  ]
}
可能的错误: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED
GET/v1/orders
按从新到旧列出你的订单,只读取已存储的数据——不会向服务商获取新验证码(这由 POST /v1/orders/status 完成)。可按状态和创建时间筛选,用 nextCursor 翻页。
把 nextCursor 作为 cursor 传入即可获取下一页;nextCursor = null ⇒ 最后一页。即使同一秒内创建了很多订单,翻页也绝不会重复或遗漏订单。内部状态永远不会列出(只有 waiting_code、completed、cancelled 和 queued)。机器人重启后:GET /v1/orders?status=waiting_code&limit=200,沿 nextCursor 翻页,再用 POST /v1/orders/status 轮询这些订单。
参数
名称类型必填说明
statusstring否只返回该状态:waiting_code | completed | cancelled | queued(不传 ⇒ 全部)
sincenumber否只返回在此 unix 时间(秒)或之后创建的订单
limitnumber否每页订单数:1–200(默认 50)
cursorstring否上一页的 nextCursor,原样传入
示例
# 重启后:逐页收集所有仍在等待验证码的订单
curl "https://otpgmail.net/v1/orders?status=waiting_code&limit=200" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": [
    {
      "orderId": "jkl012mno",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270001,
      "rentExpiresAt": 1753356401,
      "createdAt": 1753268201,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    },
    {
      "orderId": "abc123xyz",
      "service": "ig",
      "domain": "gmail.com",
      "email": "[email protected]",
      "status": "waiting_code",
      "price": 1060,
      "otp": [],
      "codeDeadlineAt": 1753270000,
      "rentExpiresAt": 1753356400,
      "createdAt": 1753268200,
      "warehouse": 1,
      "nextCodeFee": null,
      "listening": false,
      "listenUntil": null,
      "nextCodeBlocked": false,
      "nextCodeBlockReason": null
    }
  ],
  "nextCursor": "1753268200:abc123xyz"
}
可能的错误: UNAUTHORIZED, VALIDATION_ERROR, RATE_LIMITED

仓库

每个 offers[] 条目是一个报价 =(服务、仓库、邮箱类型)。只列出当前在售的仓库;暂时缺货的仓库仍会留在列表中,stock = 0。仓库编号是固定的:1、2、3、4,创建订单时在 warehouse 参数中传入相同的编号即可。
现有客户端无需修改。省略 warehouse ⇒ 行为与以前相同(仓库 1:等待 25 分钟,如果服务发送多个验证码,额外的验证码会在 24 小时订单有效期内自动到达);发送 0 ⇒ 有库存的最便宜仓库;发送 99 ⇒ 自动选择仓库,某仓库缺货时自动换下一个(见下文);所有现有字段保持不变,只新增了字段(offers[]、warehouse、nextCodeFee、listening、nextCodeBlocked 等)。
自动仓库:warehouse = 99(推荐用于经常缺货的服务)
发送 "warehouse": 99,系统会从有库存的最便宜仓库租用;如果该仓库缺货或失败,会在同一次调用中自动换下一个。非常适合 Facebook 和 Instagram 等经常缺货的服务,你的机器人不必再读取 offers[] 并逐个仓库重试。
  • 尝试顺序:价格从低到高(例如 1,500 → 1,600 → 1,700 VND);价格相同 ⇒ 仓库编号小的优先。
  • 库存为零的仓库(offers[] 中 stock = 0)永远不会被调用,不浪费时间。
  • 当前仓库缺货或失败时换下一个(OUT_OF_STOCK、NO_MAILS_AVAILABLE、WAREHOUSE_UNAVAILABLE、PROVIDER_ERROR)。
  • 其他错误会立即返回,不会尝试下一个仓库:INSUFFICIENT_BALANCE、WAITING_LIMIT_REACHED、QUANTITY_EXCEEDED、MAINTENANCE、SERVICE_DISABLED、DOMAIN_UNAVAILABLE 等。
  • quantity > 1:如果第一个仓库数量不足,其余部分由下一个仓库补足,因此订单可能来自多个仓库;每个订单都带有发货仓库的 warehouse 和 price(并遵循该仓库的验证码规则)。
  • 如果所有仓库都尝试过仍无法租到邮箱 ⇒ ALL_WAREHOUSES_EXHAUSTED(HTTP 409,不扣费),请稍后再试。如果只租到了部分邮箱,仍会返回 200 和这些订单。
示例:用 warehouse = 99 租用 Facebook 的 Gmail
# warehouse: 99,选择有库存的最便宜仓库;缺货或失败 ⇒ 自动换下一个仓库
curl -X POST "https://otpgmail.net/v1/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service":"fb","quantity":1,"domain":"gmail.com","warehouse":99}'
offers[] 字段
字段类型说明
warehouse1 | 2 | 3 | 4仓库编号:1、2、3 或 4。
domainstring该报价的邮箱类型:gmail.com、icloud.com 或 gmx.com(附加域名,仅部分服务有,目前为 Pokemon)。
pricenumber从该仓库租用一个邮箱的价格(VND)。
stocknumber剩余邮箱数。0 = 缺货:从仓库 1 或 2 租用会返回 WAREHOUSE_UNAVAILABLE;仓库 4 仍会尝试(其来源常在报 0 时仍能发货),确实没有时返回 OUT_OF_STOCK。
maxCodesnumber | null一个邮箱最多可接收的验证码数量;null = 不限 / 未知(见 fee)。
rulesobject | null仓库 3 的时段规则(windowMinutes、maxCodes、cancellable、refundOnTimeout、rerentRatio);仓库 1、2 和 4 为 null。
feeobject | null额外验证码时段:自租用起 freeHours 小时内免费,之后每次延长收取租用价格的 paidPct%,最长到自租用起 maxHours 小时(null = 无限制)。fee = null:无费用,如果服务发送多个验证码,额外验证码在订单有效期内不收费(最多 maxCodes 个),与仓库 1 相同。
isDefaultbooleantrue 表示有库存的最便宜仓库(价格相同 ⇒ 仓库编号小的优先),即 warehouse = 0 时使用的仓库,也是 99 时第一个尝试的仓库。
各仓库规则
规则仓库 1仓库 2仓库 3仓库 4
等待第一个验证码25 分钟20 分钟25 分钟 ¹最多 25 分钟(部分来源更短,实际截止时间以订单的 codeDeadlineAt 为准)
未收到验证码自动取消,全额退款自动取消,全额退款自动取消,全额退款 ¹自动取消,全额退款
验证码到达前取消可以可以可以 ¹可以
每个邮箱的验证码数按 maxCodes不限不限 ¹按 maxCodes(通常为 null = 不限;部分报价为 1)
更多验证码如果服务发送更多验证码,在 24 小时订单有效期内不另收费;自动接收(无需额外调用)。邮箱通常在最近一个验证码之后保持开启约 25 分钟。fee.freeHours 小时内免费(目前 Gmail 6 小时,iCloud 12 小时);之后每次延长通过 next-code 收取价格的 fee.paidPct%,最长到 fee.maxHours(Gmail 24 小时,iCloud 10 天)24 小时内不另收费;之后按价格的 50% 再次租用同一个邮箱(rerent)与仓库 1 一样,在 24 小时订单有效期内不另收费,读取订单时会出现新验证码;next-code 会立即重新读取。
¹ 仅从自有库存出售的服务,例如 Cointiply(provider = "inventory",rules.cancellable = false),遵循 rules:在 rules.windowMinutes 分钟内最多接收 rules.maxCodes 封邮件,不可取消,时间用完仍未收到邮件也不退款。
nextCodeFee:获取更多验证码的费用(仓库 2 和仓库 3 的订单)
包含在 GET /v1/orders/{id} 和每个订单响应中;仓库 1 和 4 的订单为 null(更多验证码免费)。仓库 2:需要再来一个验证码时调用 POST /v1/orders/{id}/next-code,如果 feePriceVnd 大于 0,必须发送 confirmFee = true。仓库 3(常规服务):免费时段结束后,feePriceVnd 是再次租用同一个邮箱(rerent)的价格。
字段类型说明
feePriceVndnumber现在调用 next-code 将收取的金额;0 = 仍在免费时段内。
freeUntilnumber | null当前免费时段的结束时间(unix 秒)。
hardUntilnumber | null硬性期限(unix 秒),此时间之后不再有验证码。
paidPctnumber每个额外时段收取的租用价格百分比(%)。
freeHoursnumber一个验证码时段的免费小时数(仓库 2:Gmail 6,iCloud 12;仓库 3:24)。
示例:列出仓库,然后从其中一个租用
# warehouse: 0 | 1–4 | 99(可选,省略则与以前一样使用仓库 1;0 = 有库存的最便宜仓库;99 = 自动,仓库缺货时自动换下一个;见 GET /v1/services 中的 offers[])
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}'
仓库 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
  }
}

批量租用(批次)

批次需在网页端购买(批量租用页面),API 可用于列出批次和读取验证码。邮箱域名决定仓库:Gmail = 仓库 1,iCloud = 仓库 3(私有邮箱)。
  • 批次自购买起保留 24 小时,在此期间可免费读取验证码,验证码数量不限。
  • 没有收到验证码的邮箱不予退款;只有数量短缺的部分才会(自动)退款。
  • 仓库 1(Gmail):每个邮箱都有一个 proxyUrl,对其发起 GET 会返回 {"status":1,"code":"482913","all_codes":[...]},无需 API 密钥(链接本身就是密钥,请妥善保密)。下面的验证码接口同样可用。
  • 仓库 3(iCloud):proxyUrl = null,请用下面的接口读取验证码,或打开 readUrl(网页)。每个邮箱在该服务下绑定到你的账号;过期后你仍可在网页端再次租用。
  • 无法提取验证码的邮件(欢迎邮件、新闻邮件等)会原样放在 code 中返回,并带有 isRaw: true(HTML 或文本,最多 20,000 个字符)。
批次中邮箱的字段(mails[])
字段类型说明
idnumber邮箱 ID,即验证码接口的 {mailId} 参数
emailstring邮箱地址
urlstring仓库 1:= proxyUrl · 仓库 3:= readUrl(为兼容旧脚本 / 导出文件而保留)
proxyUrlstring | null仓库 1:无需登录的验证码链接 · 仓库 3:null
readUrlstring查看该批次验证码的网页
codeCountnumber已收到的验证码数量
lastCodestring | null最新的验证码(原始邮件:前 300 个字符,完整内容见验证码接口)
lastIsRawbooleantrue = 最新的验证码是一封原始邮件
checkedAtnumber | null系统最近一次就该邮箱向服务商查询的时间(unix 秒;null = 从未查询)
GET/v1/batches
你的批次,最新的在前(用 page / pageSize 分页,最多 100 条)。每个批次包含 warehouse(1 = Gmail,3 = iCloud)、status(active | expired | failed)、expiresAt、codeCount 和 readUrl。
参数
名称类型必填说明
pagenumber否页码(默认 1)
pageSizenumber否每页批次数(默认 20,最大 100)
示例
curl "https://otpgmail.net/v1/batches?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "k3v9m2x7q1w8e5r4",
        "serviceCode": "lf",
        "serviceName": "Tiktok",
        "domain": "icloud.com",
        "warehouse": 3,
        "status": "active",
        "countRequested": 3,
        "countReceived": 3,
        "countDuplicate": 0,
        "countCharged": 3,
        "unitPriceVnd": 600,
        "quotedVnd": 1800,
        "chargedVnd": 1800,
        "refundVnd": 0,
        "holdHours": 24,
        "expiresAt": 1753354200,
        "createdAt": 1753267800,
        "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
        "codeCount": 1,
        "rerentPriceVnd": null
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  }
}
可能的错误: UNAUTHORIZED
GET/v1/batches/{id}
单个批次及其 mails[],取每个邮箱的 id 即可读取其验证码。
Gmail 批次(仓库 1):每个邮箱的 proxyUrl 在你的脚本中与以前完全一样可用。iCloud 批次(仓库 3):proxyUrl = null,请用下面的接口读取验证码。已过期的 iCloud 批次带有 rerentPriceVnd,即单独再次租用一个邮箱的价格(在网页端操作)。
参数
名称类型必填说明
idstring是批次 ID(显示在批量租用页面和 readUrl 中)
示例
curl "https://otpgmail.net/v1/batches/BATCH_ID" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "id": "k3v9m2x7q1w8e5r4",
    "serviceCode": "lf",
    "serviceName": "Tiktok",
    "domain": "icloud.com",
    "warehouse": 3,
    "status": "active",
    "countRequested": 3,
    "countReceived": 3,
    "countDuplicate": 0,
    "countCharged": 3,
    "unitPriceVnd": 600,
    "quotedVnd": 1800,
    "chargedVnd": 1800,
    "refundVnd": 0,
    "holdHours": 24,
    "expiresAt": 1753354200,
    "createdAt": 1753267800,
    "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
    "codeCount": 1,
    "rerentPriceVnd": null,
    "mails": [
      {
        "id": 812,
        "email": "[email protected]",
        "url": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
        "proxyUrl": null,
        "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
        "fresh": true,
        "codeCount": 1,
        "lastCode": "482913",
        "lastCodeAt": 1753268500,
        "lastIsRaw": false,
        "checkedAt": 1753268507
      }
    ]
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND
GET/v1/batches/{id}/mails/{mailId}/codes
读取批次中某个邮箱的验证码:如果该邮箱到了检查时间,系统会向服务商查询,然后返回已收到的全部验证码,最早的在前,最新的验证码位于 codes[] 的末尾。
等待期间请每 5–10 秒轮询一次(调用得更频繁只会返回已存储的数据,polledAt 不会变化)。无法提取验证码的邮件会原样放在 code 中返回,并带有 isRaw: true。已过期的批次仍会返回已存储的验证码,但不再检查。
参数
名称类型必填说明
idstring是批次 ID
mailIdnumber是邮箱 ID(mails[].id)
poll0 | 1否0 = 只读取已存储的验证码,不向服务商检查(默认 1)
示例
# 等待期间每 5–10 秒再调用一次;最新的验证码位于 codes[] 的末尾
curl "https://otpgmail.net/v1/batches/BATCH_ID/mails/MAIL_ID/codes" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "mailId": 812,
    "codes": [
      {
        "code": "482913",
        "receivedAt": 1753268500,
        "isRaw": false
      }
    ],
    "polledAt": 1753268507,
    "mail": {
      "id": 812,
      "email": "[email protected]",
      "url": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
      "proxyUrl": null,
      "readUrl": "https://otpgmail.net/app/batch/k3v9m2x7q1w8e5r4",
      "fresh": true,
      "codeCount": 1,
      "lastCode": "482913",
      "lastCodeAt": 1753268500,
      "lastIsRaw": false,
      "checkedAt": 1753268507
    }
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, RATE_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
登录仓库 1 的账号(没有辅助邮箱)
  • 仓库 1(gm_cookie)的账号不带辅助邮箱——你拿到的是邮箱、密码、Cookie 和收件箱链接(inboxUrl / inboxJsonUrl),没有别的。
  • 推荐的登录方式:导入 Cookie(cookies,JSON)到浏览器——用 Cookie 导入扩展,或用指纹浏览器(antidetect)的 Cookie 导入功能——然后打开 Gmail:会话已处于登录状态,通常不会要求输入密码或验证。
  • 只需要验证码或邮件?通过 inboxUrl(网页)或 inboxJsonUrl(JSON)读取即可——不需要 API 密钥,也不需要登录 Google。
  • 在新设备或新 IP 上用密码登录,Google 可能要求验证(辅助邮箱、手机号或验证码)——这些账号无法通过该验证,所以请改用 Cookie 或收件箱链接。
  • 转售?请把 Cookie 和收件箱链接一起交给你的客户——.txt 下载(GET /v1/mail-shop/orders/{id}/download)每行就是 email|password|cookies|inboxUrl——并告诉他们用 Cookie 登录,不要用密码。
  • 仓库 2(gm_login)的账号则附带快速登录链接(loginUrl)——请在电脑浏览器中打开它登录。
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[])
字段类型说明
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
状态说明
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.
示例
curl "https://otpgmail.net/v1/mail-shop/products" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "products": [
      {
        "warehouse": 1,
        "code": "gm_cookie",
        "name": "Short-lived live Gmail — with cookies + inbox link",
        "description": "Short-lived personal Gmail account. You get the email · password · login cookies · an inbox link (add ?json to read the emails as JSON).",
        "terms": "• Warranty covers ONLY the first successful sign-in, within 2 hours of purchase.\n• These Gmail accounts typically last 6–24 hours: use them right after purchase.",
        "delivers": [
          "email",
          "password",
          "cookies",
          "inboxUrl",
          "inboxJsonUrl"
        ],
        "priceVnd": 3300,
        "listPriceVnd": 3300,
        "stock": 1840,
        "available": true,
        "maxQty": 500
      },
      {
        "warehouse": 2,
        "code": "gm_login",
        "name": "Short-lived live Gmail — with a quick login link",
        "description": "Short-lived personal Gmail account that has been used on a few services. You get the email · password · a quick login link.",
        "terms": "• Warranty covers ONLY the first successful sign-in, within 2 hours of purchase.\n• Sign in with the link in a DESKTOP BROWSER.",
        "delivers": [
          "email",
          "password",
          "loginUrl"
        ],
        "priceVnd": 6000,
        "listPriceVnd": 6000,
        "stock": 141,
        "available": true,
        "maxQty": 100
      }
    ],
    "auto": {
      "warehouse": 99,
      "priceVnd": 3300,
      "available": true,
      "order": [
        1,
        2
      ]
    }
  }
}
可能的错误: 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.
参数
名称类型必填说明
warehouse1 | 2 | 99否Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstring否Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumber否Number of accounts (default 1)
示例
curl "https://otpgmail.net/v1/mail-shop/quote?warehouse=99&quantity=10" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "warehouse": 99,
    "quantity": 10,
    "unitPriceVnd": 3300,
    "totalVnd": 38400,
    "available": true,
    "stock": null,
    "maxQty": 500,
    "route": [
      {
        "warehouse": 1,
        "quantity": 8,
        "unitPriceVnd": 3300
      },
      {
        "warehouse": 2,
        "quantity": 2,
        "unitPriceVnd": 6000
      }
    ]
  }
}
可能的错误: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_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.
参数
名称类型必填说明
warehouse1 | 2 | 99否Mail-shop warehouse: 1 = email + password + cookies + inbox link · 2 = email + password + login link · 99 = automatic, cheapest first. Send warehouse OR product
productstring否Instead of warehouse: gm_cookie (= warehouse 1) | gm_login (= warehouse 2)
quantitynumber否Number of accounts (default 1)
Idempotency-Keystring是Header: a new UUID for every new purchase — reuse it only to retry that same purchase
示例
# 必须携带 Idempotency-Key:每次购买都用新的 UUID——只有在超时后重试时才复用同一个密钥
# warehouse: 1 = cookies + inbox link · 2 = login link · 99 = cheapest first
curl -X POST "https://otpgmail.net/v1/mail-shop/orders" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --max-time 60 \
  -d '{"warehouse":99,"quantity":5}'
响应示例
{
  "success": true,
  "data": {
    "id": "p3x9k2m7q1w8e5r4",
    "requestedWarehouse": 99,
    "warehouse": 1,
    "productCode": "gm_cookie",
    "quantity": 2,
    "deliveredCount": 2,
    "unitPriceVnd": 3300,
    "chargedVnd": 9300,
    "refundVnd": 0,
    "status": "completed",
    "channel": "api",
    "route": [
      {
        "warehouse": 1,
        "quantity": 1,
        "unitPriceVnd": 3300
      },
      {
        "warehouse": 2,
        "quantity": 1,
        "unitPriceVnd": 6000
      }
    ],
    "message": null,
    "createdAt": 1760000400,
    "completedAt": 1760000409,
    "items": [
      {
        "lineNo": 1,
        "warehouse": 1,
        "email": "[email protected]",
        "password": "Pw7Xq!k3d",
        "rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "inboxUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxJsonUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json",
        "twoFaUrl": "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxLinkExpiresAt": 1762592400
      },
      {
        "lineNo": 2,
        "warehouse": 2,
        "email": "[email protected]",
        "password": "Qp9!rT2",
        "rawLine": "[email protected]!rT2----no----https://login.example/google/login?uid=s2c38a3b",
        "loginUrl": "https://login.example/google/login?uid=s2c38a3b",
        "otpUrl": "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c",
        "otpService": "vinted"
      }
    ]
  }
}
可能的错误: UNAUTHORIZED, VALIDATION_ERROR, MAILSHOP_PRODUCT_NOT_FOUND, MAILSHOP_DISABLED, MAILSHOP_QTY_LIMIT, MAILSHOP_OUT_OF_STOCK, INSUFFICIENT_BALANCE, MAILSHOP_SUPPLIER_ERROR, RATE_LIMITED, DUPLICATE_REQUEST, 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).
参数
名称类型必填说明
pagenumber否Page (default 1)
pageSizenumber否Orders per page (default 20, max 100)
示例
curl "https://otpgmail.net/v1/mail-shop/orders?page=1&pageSize=20" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "p3x9k2m7q1w8e5r4",
        "requestedWarehouse": 99,
        "warehouse": 1,
        "productCode": "gm_cookie",
        "quantity": 2,
        "deliveredCount": 2,
        "unitPriceVnd": 3300,
        "chargedVnd": 9300,
        "refundVnd": 0,
        "status": "completed",
        "channel": "api",
        "route": [
          {
            "warehouse": 1,
            "quantity": 1,
            "unitPriceVnd": 3300
          },
          {
            "warehouse": 2,
            "quantity": 1,
            "unitPriceVnd": 6000
          }
        ],
        "message": null,
        "createdAt": 1760000400,
        "completedAt": 1760000409
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  }
}
可能的错误: 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.
参数
名称类型必填说明
idstring是Order ID (data.id returned by the purchase)
示例
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "id": "p3x9k2m7q1w8e5r4",
    "requestedWarehouse": 99,
    "warehouse": 1,
    "productCode": "gm_cookie",
    "quantity": 2,
    "deliveredCount": 2,
    "unitPriceVnd": 3300,
    "chargedVnd": 9300,
    "refundVnd": 0,
    "status": "completed",
    "channel": "api",
    "route": [
      {
        "warehouse": 1,
        "quantity": 1,
        "unitPriceVnd": 3300
      },
      {
        "warehouse": 2,
        "quantity": 1,
        "unitPriceVnd": 6000
      }
    ],
    "message": null,
    "createdAt": 1760000400,
    "completedAt": 1760000409,
    "items": [
      {
        "lineNo": 1,
        "warehouse": 1,
        "email": "[email protected]",
        "password": "Pw7Xq!k3d",
        "rawLine": "[email protected]|Pw7Xq!k3d||[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "cookies": "[{\"domain\":\".google.com\",\"name\":\"SID\",\"value\":\"g.a000…\"}]",
        "inboxUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxJsonUrl": "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json",
        "twoFaUrl": "https://otpgmail.net/2fa/Q7xK2mP9vL4tR8wN3cB6yH1d",
        "inboxLinkExpiresAt": 1762592400
      },
      {
        "lineNo": 2,
        "warehouse": 2,
        "email": "[email protected]",
        "password": "Qp9!rT2",
        "rawLine": "[email protected]!rT2----no----https://login.example/google/login?uid=s2c38a3b",
        "loginUrl": "https://login.example/google/login?uid=s2c38a3b",
        "otpUrl": "https://otpgmail.net/api/mail-shop/otp/p3x9k2m7q1w8e5r4.2.Zx8Kq2Lm4Np6Rt0Vw3Yb5c",
        "otpService": "vinted"
      }
    ]
  }
}
可能的错误: UNAUTHORIZED, NOT_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.
参数
名称类型必填说明
idstring是Order ID (data.id returned by the purchase)
示例
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/download" \
  -H "Authorization: Bearer og_xxx..." \
  -o mail-ORDER_ID.txt
响应示例
email|password|cookies|inboxUrl
[email protected]|Pw7Xq!k3d|[{"domain":".google.com","name":"SID","value":"g.a000…"}]|https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d
可能的错误: 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.
参数
名称类型必填说明
idstring是Order ID (data.id returned by the purchase)
lineNonumber是Account number within the order (items[].lineNo)
maxnumber否Number of latest emails: 10 (default), 20, 50, 100 or 200
示例
# Warehouse 1 accounts only; max = 10 | 20 | 50 | 100 | 200
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/inbox?max=20" \
  -H "Authorization: Bearer og_xxx..." \
  --max-time 70

# Same emails without an API key: GET the account's inboxJsonUrl
curl "https://otpgmail.net/mail/Q7xK2mP9vL4tR8wN3cB6yH1d?json&max=20"
响应示例
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "letters": [
      {
        "key": "h:9f2c4e1a7b3d5c80",
        "from": "Instagram [email protected]",
        "subject": "350038 is your Instagram code",
        "receivedAt": 1760000520,
        "code": "350038",
        "content": "<html><body><p>Enter this confirmation code in the app: 350038</p></body></html>"
      }
    ],
    "max": 10,
    "total": 8,
    "fetchedAt": 1760000530,
    "cached": false
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_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.
参数
名称类型必填说明
idstring是Order ID (data.id returned by the purchase)
lineNonumber是Account number within the order (items[].lineNo)
示例
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/status" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "live": true,
    "reason": null,
    "checkedAt": 1760000600
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_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).
参数
名称类型必填说明
idstring是Order ID (data.id returned by the purchase)
lineNonumber是Account number within the order (items[].lineNo)
示例
curl "https://otpgmail.net/v1/mail-shop/orders/ORDER_ID/items/1/2fa" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "none": false,
    "code": "424819",
    "expiresIn": 24
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PROVIDER_ERROR

订单生命周期

状态说明
等待验证码新创建的订单,等待第一个验证码。请每 3–5 秒轮询 GET /v1/orders/{id}。
已完成第一个验证码已到达,状态立即变更。如果服务发送多个验证码,之后仍可能收到更多(订单有效期内不另收费;数量上限和时间窗口因仓库而异);请继续轮询,每个新验证码会出现在 otp[] 的末尾。
已取消已 100% 退款。在你调用 /cancel(验证码到达前)时,或到 codeDeadlineAt 仍未收到验证码时(最多 25 分钟)发生。
重要说明:
  • 订单处于 waiting_code 时,请每 3–5 秒轮询一次 GET /v1/orders/{id},以便及时拿到验证码。
  • 第一个验证码一到达,状态就会立即变为 completed。
  • 到 codeDeadlineAt 仍无验证码(最多 25 分钟;仓库 2:20 分钟;部分仓库 4 的来源更短)⇒ 系统取消订单并 100% 退款(status = cancelled)。
  • otp[] 数组最早的在前,最新的在最后。请用 otp[otp.length - 1] 读取最新的验证码(Python:otp[-1])。
  • completed 订单会自动继续接收验证码:继续轮询 GET /v1/orders/{id},每个新验证码都会出现在 otp[] 的末尾。如果服务发送多个验证码,订单有效期内额外验证码不收费;数量上限和时间窗口会在租用前显示(offers[] 中的 maxCodes 和 fee;在仓库 1,邮箱通常在最近一个验证码之后保持开启约 25 分钟)。如需明确请求再来一个验证码(例如注册后登录):POST /v1/orders/{id}/next-code,适用于所有仓库,只有超出免费时段的仓库 2 才会收费。
  • 如果服务每个邮箱只允许一个验证码,otp[] 会停留在该验证码。响应保持正常,不会报错,由你决定何时停止轮询。
  • otp[].code 通常是一个短验证码(数字 / 字母)或一个链接。无法提取验证码的邮件(欢迎邮件、新闻邮件、特殊格式的验证码等)会原样(HTML 或文本)放在 otp[].code 中。当它超过 80 个字符、包含换行或是 HTML 时,请把它当作完整邮件处理,并自行在其中查找验证码或链接。
  • POST /v1/orders 会等待服务商分配邮箱,通常约需 0.3 秒,高峰期(少见)最长可达 10 秒。请将客户端超时设为约 15 秒,不要在 5 秒后就放弃并提前重试;重试时请复用同一个 Idempotency-Key。
  • 已收到验证码的订单无法取消或退款,取消调用会返回 ORDER_NOT_CANCELLABLE。
  • 如需租用 @icloud.com 邮箱,请在创建订单时发送 "domain": "icloud.com"(Gmail 则省略;现有机器人无需修改)。如果服务不支持 iCloud,或 iCloud 已暂时停用,会返回 DOMAIN_UNAVAILABLE,改用 Gmail 即可立即租用成功。订单生命周期、取消与退款规则以及读取验证码的方式与 Gmail 完全相同。
  • 自有库存服务(provider = "inventory",例如 Cointiply,见 GET /v1/services)仅限 iCloud;每封邮件以链接形式出现在 otp[].code 中,同时带有 kind、label 和完整的邮件正文 text。一个订单在 rules.windowMinutes 分钟内(截至 windowEndsAt)最多接收 rules.maxCodes 封邮件,不可取消(ORDER_NOT_CANCELLABLE),如果时间用完仍未收到邮件,订单会变为 cancelled,且不退款。如需在同一个邮箱上继续接收,请在 canRerent 为 true 时调用 POST /v1/orders/{id}/rerent(价格:rerentPriceVnd)。其他所有服务 / 订单的 provider 均为 "standard"——自 2026-10-24 起 provider 只有两个值:"standard" | "inventory"(实际仓库见 warehouse);请只与 "inventory" 比较。
  • 仓库 2 订单(warehouse = 2):验证码在 fee.freeHours 小时内免费;免费时段结束后不再有新验证码到达,请调用 POST /v1/orders/{id}/next-code 并带上 confirmFee = true(支付 nextCodeFee.feePriceVnd)来开启新的时段,最长到 nextCodeFee.hardUntil。常规服务的仓库 3 订单:24 小时内验证码免费,验证码到达前可取消,之后请使用 rerent。仓库 4 订单:读取订单时新验证码会进入 otp[],在 rentExpiresAt 之前免费;对于只发一个验证码的来源,收到第一个验证码后 nextCodeBlockReason 会变为 "max_codes"。
  • 同时有很多订单?用 POST /v1/orders/status 每次最多轮询 100 个——请见单次调用查询多个订单状态一节。

速率限制

限制按 API 密钥计算(账号可能有单独的限制;没有时按网站默认限制):
  • 100 次请求/秒:几秒内的短时突发
  • 3000 次请求/分钟:每分钟的总流量
超出任一限制会返回 HTTP 429,带有 Retry-After: <seconds> 响应头和响应体 {"success":false,"error":{"code":"RATE_LIMITED",...}}。请先等待响应头中给出的秒数,再重试。 错误响应体中的 error.retryAfterSec 也给出相同的秒数(同时 error.retryable = true)。

错误码

所有错误的结构相同:{"success":false,"error":{"code":"...","message":"...","retryable":false,"retryAfterSec":null}}
自 2026-10-17 起,每个 /v1 错误还包含 error.retryable 和 error.retryAfterSec(新增字段——现有客户端无需任何修改;details 保持不变)。retryable = true ⇒ 同一调用稍后可能成功:等待 retryAfterSec 秒(≥ 1 的整数)后再次发送——缺货 / 没有邮箱 / 仓库暂停 / 服务商错误 ⇒ 5 秒;429 RATE_LIMITED ⇒ 与 Retry-After 响应头相同;使用同一 Idempotency-Key 的第一个请求仍在处理时的 409 DUPLICATE_REQUEST ⇒ 2 秒。retryable = false(retryAfterSec = null)⇒ 在解决原因之前(余额、等待中订单上限、参数、API 密钥…)重试也没有用。POST /v1/orders 的 partial.reason 遵循同样的规则。 遇到表示缺货的 409 响应(OUT_OF_STOCK、WAREHOUSE_UNAVAILABLE、ALL_WAREHOUSES_EXHAUSTED)时,请等满 retryAfterSec 秒再重试——提前重试通常只会得到同样的回答——并在再次租用前查看 GET /v1/services 中的 stock / offers[].stock;已租的订单请用 webhook 跟踪,而不是频繁轮询。
错误响应示例
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
  }
}
错误码HTTPretryable · retryAfterSec含义
UNAUTHORIZED401false · nullAPI 密钥无效或缺失。
ACCOUNT_LOCKED403false · null账号已被锁定,使用其密钥发出的所有请求都会被拒绝。请联系客服。
FORBIDDEN403false · null处理租用时,账号已不再处于启用状态(刚刚被锁定)。
NOT_FOUND404false · null你的账号下没有此 ID 的订单 / 批次 / 邮箱(其他账号的订单也会返回 404)。
VALIDATION_ERROR400false · null请求参数无效(类型错误、缺少必填字段等)。
INSUFFICIENT_BALANCE402false · null账号余额不足,无法支付订单。
SERVICE_DISABLED409false · null服务代码不存在,或该服务已暂时停用。请从 GET /v1/services 获取准确的代码。
OUT_OF_STOCK409true · 5请求时,该服务(所选邮箱类型)没有可用邮箱。
DOMAIN_UNAVAILABLE409false · null该服务不提供所选的邮箱类型:尚不支持 iCloud / 已暂时停用,或该服务没有 gmx.com 邮箱。发送 domain "gmail.com"(或省略该字段)即可租用 Gmail。
WAREHOUSE_UNAVAILABLE409true · 5 / false所选仓库不出售此服务/邮箱类型、已暂停或刚刚售罄。请从 offers[] 中选择其他仓库,或发送 warehouse = 99 让系统自动选择有库存的仓库。 如果该仓库(或销售此服务的所有仓库)对您的账户不可用,retryable = false —— 重试没有用,请使用其他仓库。
ALL_WAREHOUSES_EXHAUSTED409true · 5仅在 warehouse = 99 时出现:所有有库存的仓库都已尝试(从最便宜的开始),均缺货或失败,或所有仓库库存均为零。未扣费,请稍后再试。
NO_MAILS_AVAILABLE503true · 5服务商已没有该服务的可用邮箱(暂时缺货)。
QUANTITY_EXCEEDED400false · nullquantity 超过单次请求的上限(目前为 200)。
WAITING_LIMIT_REACHED429false · null同时等待验证码的订单过多(超过你账号的 waiting_code 并发订单上限),请等部分订单完成后再租用。
ORDER_NOT_CANCELLABLE409false · null订单无法取消:可能已收到验证码,或已不再处于 waiting_code 状态。订单尚未收到第一个验证码时,next-code 也会返回此错误码。
RERENT_NOT_AVAILABLE409false · null订单无法再次租用。仓库 3:它不是该邮箱的最新订单、该邮箱已不再分配给你的账号,或上一个接收时段仍未结束。仓库 2:订单尚未关闭(仍可接收验证码,请使用 next-code)、订单太久远无法再次租用、该邮箱有其他进行中的订单,或服务已暂停。仓库 1 和 4 没有“再次租用”。
RERENT_EMAIL_BANNED409false · null仓库 2 再次租用:服务商报告该邮箱已被对应服务锁定(email banned),无法再次租用;不会扣费。
RERENT_MAIL_GONE409false · null仓库 2 再次租用:服务商已无法重新打开此邮箱,请租用新邮箱;不会扣费。
NEXT_CODE_FEE_CONFIRM409false · null仓库 2 订单的免费时段已结束,请带上 confirmFee: true 再次调用 next-code 以支付费用(nextCodeFee.feePriceVnd)。尚未扣费。
EMAIL_BANNED409false · null该邮箱已被对应服务锁定(服务商报告 email banned),无法再接收验证码,请租用新邮箱。自 2026-10-02 起(此前为 ORDER_EXPIRED,HTTP 状态同为 409)。
ORDER_EXPIRED409false · null订单无法再接收验证码:已超过时限(rentExpiresAt / nextCodeFee.hardUntil)、服务商已关闭邮箱、已达验证码上限(maxCodes),或该服务没有“下一个验证码”。原因见 order.nextCodeBlockReason。
RATE_LIMITED429true · = Retry-After超过 100 次请求/秒或 3000 次请求/分钟(按 API 密钥计算;账号可能有单独的限制)。Retry-After 响应头会告诉你需要等待多少秒。在仓库 2,如果账号在最近 60 分钟内取消了大量没有验证码的仓库 2 订单,而收到验证码的很少,POST /v1/orders 和 /rerent 也会返回此错误码,在取消率降下来之前暂停在仓库 2 租用("warehouse": 99 也会带着这个错误停在仓库 2,期间请改选其他仓库)。Facebook / Instagram(fb、fbb、ig)不受此限制。
DUPLICATE_REQUEST409true · 2 / false该 Idempotency-Key 已用于不同的请求内容或接口,或使用该密钥的第一个请求仍在处理中(请稍后重试)。如需再创建一个订单,请生成新的密钥。
PROVIDER_ERROR502true · 5邮箱服务商出错(暂时性,几秒后重试)。
MAINTENANCE503false · null系统维护中,暂停创建和取消订单。
MAILSHOP_DISABLED503false · null短效 Gmail 购买已暂停,或该商品已停售——请稍后再试。
MAILSHOP_PRODUCT_NOT_FOUND404false · null没有该代码对应的商品——准确的代码请查看 GET /v1/mail-shop/products.
MAILSHOP_OUT_OF_STOCK409false · null按当前价格,库存不足以满足这一数量——error.details.stock = 现在可购买的账号数。
MAILSHOP_QTY_LIMIT400false · nullquantity 超过了单笔订单上限(目前为 500)——见 error.details.max。
MAILSHOP_SUPPLIER_ERROR502false · null货源当场拒绝了这次购买:系统已记录一个 failed 订单并退款(error.details.orderId)。请稍后使用新的 Idempotency-Key 重试。
INTERNAL500false · null服务器出现意外错误,请稍后再试;如果问题持续出现,请联系客服。

Webhook(推送通知)

每个账户注册一个 URL;一旦订单收到新验证码(order.code)或被取消 / 退款(order.cancelled),我们立即向该 URL POST 一条带签名的 JSON 事件。Webhook 为可选功能,覆盖账户的所有订单(网页和 API)。投递为尽力而为——请保留低频轮询作为兜底(见下文)。
3 步完成设置
  1. 调用 PUT /v1/webhook 并传入你的 URL(可选 events)——保存 data.secret(64 位十六进制字符):验证每次投递都需要它。再次调用 PUT 会保留同一个 secret。
  2. POST /v1/webhook/test——我们立即发送一条 ping 事件,并返回你的服务器如何响应(ok、httpStatus、error)。
  3. 你的端点必须在 5 秒内返回任意 2xx——先响应,再处理事件。其他任何结果(3xx、4xx、5xx、超时、连接错误)都算失败并会重试。
URL 必须以 http:// 或 https:// 开头(证书有效),且可从互联网访问:localhost、私有、链路本地、组播和保留地址(10.x、172.16–31.x、192.168.x、169.254.x、fc00::/7、fe80::/10…)以及 OTPGmail 自身的服务器都会以 VALIDATION_ERROR 拒绝。每次投递前都会重新解析主机名。不跟随重定向——请注册最终 URL。
请求格式
每次投递都是向你的 URL 发出的 HTTP POST,正文为 JSON(UTF-8),并带有以下请求头:
请求头值
Content-Typeapplication/json
User-AgentOTPGmail-Webhook/1
X-OTPGmail-Event事件名:order.code、order.cancelled 或 ping
X-OTPGmail-Delivery事件的唯一 ID(UUID)——与正文中的 deliveryId 相同,每次重试都不变
X-OTPGmail-Timestamp本次尝试签名时的 Unix 时间(秒)——与正文中的 sentAt 相同
X-OTPGmail-Signaturesha256= + 以你的 secret 为密钥、对 timestamp + "." + 原始正文 计算的 HMAC-SHA256 小写十六进制
验证签名
以你的 secret 为密钥,对 X-OTPGmail-Timestamp + . + 原始请求正文计算 HMAC-SHA256(小写十六进制),并与 sha256= 之后的值做常量时间比较。密钥就是这串 64 个字符的 secret 本身(其 UTF-8 字节——不要按十六进制解码)。务必使用原始字节:解析后再序列化 JSON 会改变字节。拒绝与你的时钟相差超过 5 分钟的时间戳(防重放)。
接收端示例(Python 和 Node 只用标准库;curl 命令向你自己的接收端发送一次签名正确的测试投递)
# 本地测试:向你的接收端发送一条签名方式与我们完全相同的投递
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"
事件
事件发送时机
order.code订单收到新验证码——每个验证码一条事件,包括同一邮箱之后收到的验证码。code 是新验证码;otp 按从旧到新列出订单到此为止的所有验证码(与 GET /v1/orders/{id} 相同;正文超过 64 KB 时只含新验证码)。
order.cancelled订单被取消或退款——每个订单一次:截止前未收到验证码(timeout)、你自己取消(user)、管理员取消(admin),或因仓库故障而退款(provider)。refundVnd = 退回余额的 VND(服务规则不退款时为 0)。
ping仅由 POST /v1/webhook/test 发送。
事件字段
字段类型说明
eventstring事件名(也在请求头 X-OTPGmail-Event 中)
deliveryIdstring事件的唯一 ID(UUID,= X-OTPGmail-Delivery)——保存它以跳过重复
sentAtnumber本次尝试的 Unix 时间(= X-OTPGmail-Timestamp)
orderIdstring订单 ID——与 GET /v1/orders/{id} 中的相同
servicestring服务代码
warehouse1 | 2 | 3 | 4 | null提供邮箱的仓库 1–4;订单从未分配到邮箱时为 null
domainstring邮箱域名:gmail.com、icloud.com 或 gmx.com
emailstring | null所租邮箱的地址
codestring仅 order.code:新验证码——也可能是链接或整封邮件,与 otp[].code 相同
receivedAtnumber仅 order.code:验证码到达的 Unix 时间
statusstring当时的订单状态:order.code 为 completed,order.cancelled 为 cancelled(管理员为已有验证码的订单退款时保持 completed)
otparray仅 order.code:到此为止的所有验证码,[{code, receivedAt}],从旧到新
reasonstring仅 order.cancelled:timeout | user | admin | provider
refundVndnumber仅 order.cancelled:退回余额的 VND(0 = 未退款)
正文示例——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
    }
  ]
}
正文示例——order.cancelled
{
  "event": "order.cancelled",
  "deliveryId": "0b7e4c2a-5d1f-4e8b-9a3c-2f6d8e1b4a7c",
  "sentAt": 1760701630,
  "orderId": "p8v2n5c1x7z3m9k4",
  "service": "ig",
  "warehouse": 2,
  "domain": "gmail.com",
  "email": "[email protected]",
  "reason": "timeout",
  "refundVnd": 1500,
  "status": "cancelled"
}
正文示例——ping
{
  "event": "ping",
  "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f",
  "sentAt": 1760700000
}
重试、暂停与历史
  • 投递失败后会在 10 秒、1 分钟、5 分钟、30 分钟和 2 小时后重试(共 5 次);最后一次仍失败则丢弃该事件(failed)。
  • 连续 50 次失败(所有事件合计)会暂停 webhook:GET /v1/webhook 显示 pausedAt,不再发送任何内容,新事件也不会入队。
  • 恢复方法:再次调用 PUT /v1/webhook(URL 不变也可以),或成功完成一次 POST /v1/webhook/test——之后队列中等待的事件会被发送。
  • GET /v1/webhook/deliveries 列出最近 100 次投递及其状态、尝试次数和最后一次错误;历史保留 7 天。
重复与顺序
同一事件可能到达多次(例如你的 2xx 响应丢失时),事件也可能乱序到达(会并行发送多条)。保存已处理的 deliveryId 并跳过重复;对每个订单,以其最新数据(otp、status)为准,而不是到达顺序。
保留轮询作为兜底
Webhook 可能延迟(重试)或丢失(webhook 已暂停、你的服务器宕机)。请保留低频轮询——例如对仍处于 waiting_code 的订单每 30–60 秒调用一次 GET /v1/orders/{id}——并以 API 返回的订单对象为准。
PUT/v1/webhook
注册或更新账户的 webhook。首次调用会创建并返回新的 secret;之后的调用保留 secret、重新启用 webhook,若已暂停则恢复。不传 events:新 webhook 接收两种事件,已有 webhook 保持不变。URL 必须是公网地址——否则返回 400 VALIDATION_ERROR(原因见 error.details.problem)。
参数
名称类型必填说明
urlstring是你的端点(http:// 或 https://,公网地址,最多 2048 个字符)
eventsstring[]否要发送的事件:["order.code", "order.cancelled"](默认两者都发)
示例
# 保存 data.secret——验证每次投递都需要它
curl -X PUT "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-server.example/otpgmail-webhook","events":["order.code","order.cancelled"]}'
响应示例
{
  "success": true,
  "data": {
    "url": "https://your-server.example/otpgmail-webhook",
    "events": [
      "order.code",
      "order.cancelled"
    ],
    "enabled": true,
    "secret": "9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e",
    "pausedAt": null,
    "consecutiveFailures": 0,
    "createdAt": 1760699000,
    "updatedAt": 1760699000
  }
}
可能的错误: UNAUTHORIZED, VALIDATION_ERROR
GET/v1/webhook
账户的 webhook,包括 secret、pausedAt 和 consecutiveFailures。未注册 webhook 时 data 为 null。
示例
curl "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "url": "https://your-server.example/otpgmail-webhook",
    "events": [
      "order.code",
      "order.cancelled"
    ],
    "enabled": true,
    "secret": "9f2c6d1e8b7a4f3c5e0d2b1a6c9e8f7d3b5a1c2e4f6a8b0d9c7e5f3a1b2c4d6e",
    "pausedAt": 1760705400,
    "consecutiveFailures": 50,
    "createdAt": 1760699000,
    "updatedAt": 1760705400
  }
}
可能的错误: UNAUTHORIZED
DELETE/v1/webhook
删除 webhook;队列中仍在等待的事件会被丢弃。始终返回 200——没有可删除的内容时 data.deleted 为 false。
示例
curl -X DELETE "https://otpgmail.net/v1/webhook" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "deleted": true
  }
}
可能的错误: UNAUTHORIZED
POST/v1/webhook/test
立即向你的 URL 发送一条 ping 事件并报告结果:ok(你的服务器返回 2xx)、httpStatus、durationMs、error。测试成功会恢复已暂停的 webhook。每分钟最多 10 次测试;webhook 在全系统关闭期间返回 503 MAINTENANCE。
示例
curl -X POST "https://otpgmail.net/v1/webhook/test" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": {
    "ok": true,
    "httpStatus": 200,
    "durationMs": 143,
    "error": null,
    "deliveryId": "3c9a1e7f-2b4d-4c6e-8f0a-5d7b9c1e3a2f"
  }
}
你的服务器未返回 2xx 时的 200 响应
{
  "success": true,
  "data": {
    "ok": false,
    "httpStatus": 500,
    "durationMs": 88,
    "error": "HTTP 500",
    "deliveryId": "7d2b9f4e-1c6a-4e3b-8d5f-0a9c2e7b1f4d"
  }
}
可能的错误: UNAUTHORIZED, NOT_FOUND, RATE_LIMITED, MAINTENANCE
GET/v1/webhook/deliveries
最近的投递,最新的在前(保留 7 天):status = queued(下次尝试时间为 nextAt)| delivered | failed | paused,并附 attempts、lastHttpStatus 和 lastError。不返回事件正文。
参数
名称类型必填说明
limitnumber否投递条数,1–100(默认 100)
示例
curl "https://otpgmail.net/v1/webhook/deliveries?limit=20" \
  -H "Authorization: Bearer og_xxx..."
响应示例
{
  "success": true,
  "data": [
    {
      "id": "0b7e4c2a-5d1f-4e8b-9a3c-2f6d8e1b4a7c",
      "event": "order.cancelled",
      "orderId": "p8v2n5c1x7z3m9k4",
      "status": "queued",
      "attempts": 1,
      "lastHttpStatus": 503,
      "lastError": "HTTP 503",
      "nextAt": 1760701640,
      "createdAt": 1760701630,
      "deliveredAt": null
    },
    {
      "id": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8",
      "event": "order.code",
      "orderId": "k3m9x2p7q1w8e5r4",
      "status": "delivered",
      "attempts": 1,
      "lastHttpStatus": 200,
      "lastError": null,
      "nextAt": null,
      "createdAt": 1760700124,
      "deliveredAt": 1760700125
    }
  ]
}
可能的错误: UNAUTHORIZED