OTPBox

Fornece aos agentes de IA e suítes de teste uma caixa de entrada de e-mail descartável: crie identidades de teste, aguarde e-mails e extraia códigos OTP e links de verificação automaticamente via REST ou MCP.

Servidor MCP hospedado

npx add-mcp 'https://otpbox.io/mcp'

Instala no Claude Code, Codex, Cursor e outros

Documentação

Documentação da API para desenvolvedores OTPBox

Crie caixas de entrada descartáveis e leia códigos de uso único a partir de uma suíte de testes, script ou job de backend. Comece com uma chave gratuita — sem necessidade de conta — ou crie uma organização para compartilhar chaves com uma equipe e fazer upgrade.

Experimente agora

Ao vivo contra a API real, direto desta página — sem cadastro. Gera uma chave gratuita real (com limite de taxa), cria uma caixa de entrada real e verifica se há uma mensagem. Envie um e-mail real para o endereço da caixa de entrada a partir de outra aba para vê-lo aparecer.

Click "Mint a free key" to start.

Obtenha uma chave

Gratuita, 200 requisições/mês, autenticação simples por bearer token. Gere uma instantaneamente, sem pagamento ou conta:

curl -X POST https://otpbox.io/api/v1/keys/free \
  -H "content-type: application/json" \
  -d '{}'
→ { "id": "lic_...", "key": "...", "plan": "free", "quotaLimit": 200 }

Salve o key retornado — como um token de caixa de entrada, ele é exibido uma única vez e nunca é armazenado em nenhum lugar onde você possa lê-lo novamente. Limitado a 5 chaves gratuitas/dia por IP.

Chaves sandbox para CI. POST /api/v1/keys/sandbox gera uma chave da mesma forma (sem conta, mesmas 200 requisições/mês, seu próprio limite de 5/dia/IP) e retorna { "id": "lic_...", "key": "...", "plan": "free", "quotaLimit": 200, "sandbox": true }. Com uma chave sandbox, POST /api/v1/inboxes (e a ferramenta MCP create_test_inbox) ignora e-mails reais: a nova caixa de entrada já contém uma mensagem sintetizada de noreply@sandbox.otpbox.io com um code aleatório de 6 dígitos, então uma execução de teste é totalmente determinística e não precisa de entrega real. Caixas de entrada sandbox nunca recebem e-mails reais, portanto nunca disparam os webhooks message.received, otp.extracted ou link.detected.

Planos

As cotas são por mês calendário. Os planos de organização agrupam a cota entre todas as chaves que a organização gera; uma chave gratuita pessoal tem a sua própria. Detalhes completos na página de preços.

PlanoRequisições / mêsPreçoComo obter
Chave gratuita pessoal200 por chave$0POST /api/v1/keys/free, sem conta
Organização — Gratuito200, agrupadas$0Cadastre-se, crie uma organização, gere chaves por projeto
Organização — Pro5.000, agrupadas$9 / mêsPainel → sua organização → Plano e uso → Fazer upgrade para Pro
EnterprisePersonalizadoPersonalizadoConte-nos sobre seu caso de uso

Verifique a que uma chave tem direito a qualquer momento com GET /api/v1/usage — ele retorna o plano, o limite vigente e quanto dele foi usado neste mês.

Autenticação

Envie sua chave como bearer token em cada requisição:

Authorization: Bearer <your-key>

Endpoints

Todos os caminhos estão sob https://otpbox.io. Todas as rotas, exceto as duas rotas de geração de chave, precisam de Authorization: Bearer <key>, e cada requisição autenticada conta como uma requisição contra sua cota mensal. Os timestamps são em milissegundos Unix. Corpos e respostas são JSON.

MétodoCaminhoFinalidade
POST/api/v1/keys/freeGere uma chave gratuita (sem autenticação; 5/dia/IP). Retorna { id, key, plan, quotaLimit }.
POST/api/v1/keys/sandboxGere uma chave sandbox (sem autenticação; 5/dia/IP).
POST/api/v1/inboxesCrie uma caixa de entrada. Corpo: { domain?, local? }. Retorna 201 { id, address, domain, createdAt, expiresAt, token }. Escopo inbox:create. Aceita um Idempotency-Key.
GET/api/v1/inboxes/:idMetadados da caixa de entrada: { id, address, domain, createdAt, expiresAt }. Somente legível pela chave que a criou. Escopo inbox:read.
DELETE/api/v1/inboxes/:idExclua a caixa de entrada e todas as mensagens nela imediatamente. Retorna { ok: true }. Escopo inbox:delete.
GET/api/v1/inboxes/:id/messagesListe mensagens, das mais recentes para as mais antigas (até 200), como { messages: [{ id, from, fromName, subject, code, linkHost, linkType, size, receivedAt, read }] }. Escopo message:read.
GET/api/v1/messages/:idMensagem completa: { id, from, fromName, subject, text, html, code, link, size, receivedAt, attachments, preview } onde link é { url, host, type } ou null. O HTML é sanitizado. Escopo message:read.
GET/api/v1/usagePlano, limite e uso deste mês — consulte Limites de taxa e cota.
POST/api/v1/batchesCrie até 50 caixas de entrada em uma única chamada — consulte Lotes. Escopo bulk:create.
GET/api/v1/batches/:idStatus de um lote e resultados por item.
POST/api/v1/identitiesCrie uma identidade de teste: uma persona sintética apoiada por uma caixa de entrada real.
POST/api/v1/identities/bulkCrie até 50 identidades de teste em uma única chamada.
GET/api/v1/identities/:idBusque uma identidade de teste (o token da caixa de entrada nunca é reexibido).
DELETE/api/v1/identities/:idExclua uma identidade e sua caixa de entrada de apoio.
GET/api/v1/webhooksListe os webhooks desta chave. Escopo webhook:manage.
POST/api/v1/webhooksRegistre um webhook. Escopo webhook:manage.
DELETE/api/v1/webhooks/:idExclua um webhook. Escopo webhook:manage.
GET/api/v1/metricsVolume de chamadas, taxa de erro e latência das chamadas de ferramentas MCP desta chave nas últimas 24 horas e 7 dias: { last24h, last7d }, cada um com totalCalls, errorRatePct, avgLatencyMs, p95LatencyMs e um detalhamento por ferramenta byTool.

As caixas de entrada são excluídas quando expiram — 1 hora após a criação por padrão — ou assim que você as excluir. A lista de mensagens apenas informa o que foi extraído (code, linkHost, linkType); busque a mensagem completa para obter a URL do link. linkType (e link.type) é um de verification, password_reset, magic_login, unsubscribe, tracking ou general.

Exemplo de resposta de mensagem:

{
  "id": "msg_a1b2c3d4e5f6",
  "from": "noreply@example.com",
  "fromName": "Example",
  "subject": "Your verification code",
  "text": "Your code is 482913",
  "html": null,
  "code": "482913",
  "link": { "url": "https://example.com/verify?t=...", "host": "example.com", "type": "verification" },
  "size": 2048,
  "receivedAt": 1790000000000,
  "attachments": [],
  "preview": "Your code is 482913"
}

Exemplo: obter um código em um teste Playwright

const key = process.env.OTPBOX_KEY;
const res = await fetch('https://otpbox.io/api/v1/inboxes', {
  method: 'POST',
  headers: { authorization: 'Bearer ' + key, 'content-type': 'application/json' },
  body: '{}',
});
const inbox = await res.json();
// use inbox.address to sign up, then poll:
const msgs = await fetch('https://otpbox.io/api/v1/inboxes/' + inbox.id + '/messages', {
  headers: { authorization: 'Bearer ' + key },
}).then((r) => r.json());
const code = msgs.messages[0]?.code;

Lotes

Crie até 50 caixas de entrada em uma única chamada — útil para provisionar uma matriz de testes inteira antecipadamente. O lote é executado de forma síncrona e retorna todos os endereços e tokens na resposta.

POST /api/v1/batches
{ "count": 3, "ttlHours": 2 }

→ 201
{
  "id": "batch_...",
  "status": "partial",
  "requestedCount": 3,
  "items": [
    { "id": "a1b2c3d4e5f6", "status": "completed", "address": "bold.quartz844@otpbox.io", "token": "...", "expiresAt": 1790007200000 },
    { "id": "bi_...", "status": "failed", "error": "address_unavailable" }
  ]
}
  • count é obrigatório, de 1 a 50; qualquer outro valor retorna 400 { "error": "bad_count", "max": 50 }. ttlHours é opcional e limitado ao máximo do servidor (atualmente 3 horas); sem ele, aplica-se o tempo de vida padrão da caixa de entrada.
  • O status do lote é completed (todos os itens tiveram sucesso), partial (alguns falharam) ou failed (nenhum teve sucesso). O id de um item concluído é o id da caixa de entrada, então você pode lê-lo com GET /api/v1/inboxes/:id/messages. (O exemplo acima está abreviado para dois itens.)
  • O token de cada item é o bearer token da própria caixa de entrada. Ele só é retornado por esta chamada de criação.
  • GET /api/v1/batches/:id retorna { id, status, requestedCount, createdAt, completedAt, items: [{ id, status, address, expiresAt, error }] } (sem tokens); id é null para um item com falha. Lote desconhecido ou de outra pessoa: 404 not_found.
  • Requer o escopo bulk:create e aceita um Idempotency-Key. 503 no_domains se nenhum domínio de caixa postal estiver disponível no momento.

Identidades de teste

Uma identidade de teste é uma persona sintética — nome, país e um pequeno perfil — apoiada por uma caixa de entrada descartável real, para que um fluxo de cadastro em teste possa usar uma pessoa realista e ainda receber seu e-mail de verificação. Escolha um template: us_customer, indian_customer, european_customer, business_customer, student ou employee.

POST /api/v1/identities
{ "template": "business_customer", "ttlHours": 2 }

→ 201
{
  "id": "ident_...",
  "status": "active",
  "template": "business_customer",
  "name": "Mary Johnson",
  "email": "bold.quartz844@otpbox.io",
  "country": "US",
  "profile": { "company": "Acme Corp", "role": "Product Manager" },
  "token": "...",
  "createdAt": 1790000000000,
  "expiresAt": 1790007200000
}
  • Use email no formulário em teste. token é o bearer token da caixa de entrada de apoio, exibido apenas nesta resposta; use-o com a API pública de caixas de entrada (GET /api/inboxes/me/messages com Authorization: Bearer <token>) para ler o que chega.
  • O profile da persona depende do template: business_customer adiciona company e role, student adiciona university e major, employee adiciona company, department e jobTitle; os templates de cliente retornam um perfil vazio.
  • ttlHours é opcional e limitado ao máximo do servidor (atualmente 3 horas). Erros: 400 bad_template (com validTemplates), 409 address_unavailable, 503 no_domains.
  • POST /api/v1/identities/bulk com { "template", "count", "ttlHours"? } (count de 1 a 50, caso contrário 400 bad_count) retorna 201 { batchId, template, requestedCount, items: [...] }; batchId se parece com idbatch_.... Cada item é { id, status: "active", name, email, country, profile, token, expiresAt }, ou { id, status: "failed", error }.
  • GET /api/v1/identities/:id retorna a identidade sem seu token, além de batchId e error. DELETE /api/v1/identities/:id exclui a identidade e sua caixa de entrada de apoio e retorna { ok: true, id, inboxDeleted }. Ids desconhecidos retornam 404 not_found.

Chaves de idempotência

Se um job de CI repetir uma chamada de criação após um timeout de rede, ele pode acabar com duas caixas de entrada. Envie um cabeçalho Idempotency-Key em POST /api/v1/inboxes ou POST /api/v1/batches e uma repetição com a mesma chave retorna a resposta original em vez de criar outro recurso.

curl -X POST https://otpbox.io/api/v1/inboxes \
  -H "authorization: Bearer $OTPBOX_KEY" \
  -H "idempotency-key: signup-test-run-8841" \
  -H "content-type: application/json" -d '{}'
  • A chave tem de 1 a 255 caracteres de A-Z a-z 0-9 _ . : - (UUIDs funcionam bem); qualquer outro valor retorna 400 invalid_idempotency_key. O cabeçalho é opcional.
  • As chaves são limitadas à sua chave de API e ao endpoint, então o mesmo valor pode ser reutilizado contra /inboxes e /batches sem colisão.
  • Tanto respostas de sucesso quanto de erro são armazenadas e repetidas com o código de status original. Os registros são mantidos por 24 horas.
  • Se uma requisição com a mesma chave ainda estiver em andamento, a segunda recebe 409 idempotency_key_in_progress; repita após um momento.
  • Uma requisição repetida ainda conta como uma requisição contra sua cota. As rotas de identidades não leem este cabeçalho.

Webhooks

Receba um HTTPS POST quando algo acontecer em vez de fazer polling. Os webhooks pertencem à chave de API que os registrou e disparam para as caixas de entrada, identidades e uso dessa chave. Registrar, listar e excluí-los requer o escopo webhook:manage (ou uma chave sem restrições de escopo).

Registrar

POST /api/v1/webhooks
{ "url": "https://your-app.example.com/webhooks/otpbox", "events": ["otp.extracted", "usage.approaching"] }

→ 201
{
  "id": "wh_...",
  "url": "https://your-app.example.com/webhooks/otpbox",
  "events": ["otp.extracted", "usage.approaching"],
  "secret": "...",
  "createdAt": 1790000000000
}
  • url deve começar com https://, caso contrário 400 { "error": "bad_url" }.
  • events é uma lista do catálogo abaixo. Nomes desconhecidos são ignorados; se nenhum for válido, você recebe 400 { "error": "bad_events", "validEvents": [...] }.
  • O secret é retornado uma única vez, apenas nesta resposta. Armazene-o agora — você precisa dele para verificar assinaturas e ele não pode ser recuperado posteriormente.
  • GET /api/v1/webhooks retorna { webhooks: [{ id, url, events, status, createdAt }] } (nunca o segredo). status é active ou disabled.
  • DELETE /api/v1/webhooks/:id retorna { ok: true }, ou 404 not_found.

Catálogo de eventos

Cada entrega é um POST com content-type: application/json. O corpo é um objeto JSON cujo event nomeia o evento, seguido pelos campos mostrados abaixo. O nome do evento também é enviado no cabeçalho x-otpbox-event.

EventoDispara quando
inbox.createdUma caixa de entrada é criada com POST /api/v1/inboxes ou a ferramenta MCP create_test_inbox. Não é disparado para caixas de entrada de lote ou identidade.
inbox.deletedUma caixa de entrada é excluída com DELETE /api/v1/inboxes/:id ou a ferramenta MCP delete_inbox.
inbox.expiredUma de suas caixas de entrada atinge sua expiração e é removida pelo job de limpeza (que é executado a cada 15 minutos).
message.receivedUm e-mail chega em uma de suas caixas de entrada.
otp.extractedUm e-mail recebido continha um código de uso único. Mesmo payload que message.received.
link.detectedUm e-mail recebido continha uma confirmação ou outro link.
identity.createdUma identidade de teste é criada. Uma criação em massa dispara um evento para todo o lote, com um payload em formato de lote.
identity.deletedUma identidade de teste é excluída.
usage.approachingO uso atinge pela primeira vez 80% do limite mensal. Uma vez por período de cobrança.
usage.exceededO uso atinge 100% do limite mensal. Uma vez por período de cobrança.

Exemplos de payloads (timestamps em milissegundos Unix):

inbox.created

{ "event": "inbox.created", "inboxId": "a1b2c3d4e5f6", "address": "bold.quartz844@otpbox.io", "domain": "otpbox.io", "createdAt": 1790000000000, "expiresAt": 1790003600000 }

inbox.deleted

{ "event": "inbox.deleted", "inboxId": "a1b2c3d4e5f6", "address": "bold.quartz844@otpbox.io", "deletedAt": 1790000900000 }

inbox.expired

{ "event": "inbox.expired", "inboxId": "a1b2c3d4e5f6", "address": "bold.quartz844@otpbox.io", "expiresAt": 1790003600000, "expiredAt": 1790004200000 }

message.received

{ "event": "message.received", "inboxId": "a1b2c3d4e5f6", "messageId": "msg_...", "from": "noreply@example.com", "fromName": "Example", "subject": "Your verification code", "code": "482913", "link": null, "receivedAt": 1790000300000 }

otp.extracted

{ "event": "otp.extracted", "inboxId": "a1b2c3d4e5f6", "messageId": "msg_...", "from": "noreply@example.com", "fromName": "Example", "subject": "Your verification code", "code": "482913", "link": null, "receivedAt": 1790000300000 }

link.detected

{ "event": "link.detected", "inboxId": "a1b2c3d4e5f6", "messageId": "msg_...", "linkUrl": "https://example.com/verify?t=...", "linkHost": "example.com", "linkType": "verification", "receivedAt": 1790000300000 }

identity.created (identidade única)

{ "event": "identity.created", "identityId": "ident_...", "template": "us_customer", "email": "bold.quartz844@otpbox.io", "country": "US", "createdAt": 1790000000000, "expiresAt": 1790003600000 }

identity.created (de /identities/bulk)

{ "event": "identity.created", "batchId": "idbatch_...", "template": "student", "requestedCount": 10, "createdCount": 10, "failedCount": 0, "createdAt": 1790000000000 }

identity.deleted

{ "event": "identity.deleted", "identityId": "ident_...", "inboxId": "a1b2c3d4e5f6", "email": "bold.quartz844@otpbox.io", "deletedAt": 1790000900000 }

usage.approaching e usage.exceeded

{ "event": "usage.approaching", "orgId": "org_...", "licenseId": "lic_...", "scope": "organization", "period": "2026-09", "used": 160, "limit": 200, "percent": 80 }

Para uma chave de organização, o limite é medido contra a cota agrupada da organização, e orgId e scope: "organization" são incluídos; toda chave na organização que tenha um webhook inscrito recebe o evento. Para uma chave pessoal, esses dois campos são omitidos e used / limit são os da própria chave. usage.exceeded tem o mesmo formato com um percent de 100 ou mais. Verifique a assinatura

Cada entrega carrega um cabeçalho x-otpbox-signature: o HMAC-SHA256 hexadecimal em minúsculas do corpo bruto da requisição, usando como chave o secret do webhook. Calcule-o sobre os bytes exatos que você recebeu (antes de qualquer parsing JSON) e compare em tempo constante. Rejeite qualquer coisa que não corresponda.

// Node (Express)
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();

app.post('/webhooks/otpbox', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = createHmac('sha256', process.env.OTPBOX_WEBHOOK_SECRET)
    .update(req.body) // Buffer: the raw body
    .digest('hex');
  const given = String(req.get('x-otpbox-signature') ?? '');

  const a = Buffer.from(expected);
  const b = Buffer.from(given);
  if (a.length !== b.length || !timingSafeEqual(a, b)) return res.status(401).end();

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.event === 'otp.extracted') console.log('code:', event.code);
  res.status(200).end();
});
# Python (Flask)
import hashlib, hmac, os
from flask import Flask, abort, request

app = Flask(__name__)

@app.post('/webhooks/otpbox')
def otpbox_webhook():
    raw = request.get_data()  # bytes: the raw body
    expected = hmac.new(os.environ['OTPBOX_WEBHOOK_SECRET'].encode(), raw, hashlib.sha256).hexdigest()
    given = request.headers.get('x-otpbox-signature', '')

    if not hmac.compare_digest(expected, given):
        abort(401)

    event = request.get_json(force=True)
    if event['event'] == 'otp.extracted':
        print('code:', event['code'])
    return '', 200

O pacote otpbox-sdk inclui a mesma verificação que o verifyWebhookSignature(secret, rawBody, signatureHeader).

Entrega, novas tentativas e desativação automática

  • Uma entrega é bem-sucedida quando seu endpoint responde com um status 2xx dentro de 8 segundos. Qualquer outra coisa — outro status, timeout, erro de conexão — é uma falha.
  • A primeira tentativa é enviada imediatamente. Uma entrega com falha é tentada novamente até 5 tentativas no total, aguardando 5 minutos, 15 minutos, 1 hora e 4 horas após as tentativas 1 a 4. As novas tentativas são processadas por um job que roda a cada 15 minutos, então uma nova tentativa pode chegar até 15 minutos depois desses atrasos nominais. Cada nova tentativa reenvia o corpo idêntico, então deduplique pela carga útil (por exemplo, messageId) se seu handler não for idempotente.
  • Após 10 tentativas consecutivas com falha, o webhook é marcado como disabled (visível em GET /api/v1/webhooks) e para de receber eventos; qualquer entrega bem-sucedida redefine o contador. Quando seu endpoint estiver saudável novamente, reative-o pelo dashboard (Projects & keys → a chave → Webhooks → Enable, depois Send test para confirmar) ou registre um novo com POST /api/v1/webhooks. O dashboard também envia um evento webhook.test sob demanda e permite reentregar qualquer entrega passada.
  • As entregas não são ordenadas. Responda rapidamente com um 2xx e faça o trabalho pesado depois.

CI/CD: OTPs no GitHub Actions

A configuração mais comum: uma suíte E2E com Playwright ou Cypress que registra uma conta real e precisa de um código real para passar pela tela de OTP. O pacote otpbox-sdk (npm install otpbox-sdk) encapsula os endpoints acima em createInbox() / waitForOtp() / deleteInbox() para que seu workflow e código de teste não precisem implementar manualmente chamadas fetch.

# .github/workflows/e2e.yml
name: E2E
on: [push]
jobs:
  e2e:
    runs-on: ubuntu-latest
    env:
      OTPBOX_KEY: ${{ secrets.OTPBOX_KEY }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test

Adicione OTPBOX_KEY em Settings → Secrets and variables → Actions do repositório para que seja injetado como variável de ambiente e nunca seja commitado no arquivo do workflow.

Dentro do teste, crie uma caixa de entrada real, preencha o formulário de cadastro com o endereço dela e então aguarde o código:

import { test, expect } from '@playwright/test';
import { OTPBox } from 'otpbox-sdk';

test('sign up with a real OTP', async ({ page }) => {
  const client = new OTPBox({ apiKey: process.env.OTPBOX_KEY! });
  const inbox = await client.createInbox();

  await page.goto('https://your-app.example.com/signup');
  await page.fill('[name="email"]', inbox.address);
  await page.click('button[type="submit"]');

  const code = await client.waitForOtp(inbox.id, { timeoutMs: 20_000 });
  await page.fill('[name="otp"]', code);
  await page.click('button[type="submit"]');
  await expect(page.locator('text=Welcome')).toBeVisible();

  await client.deleteInbox(inbox.id);
});

Alguns cuidados específicos de CI: nunca coloque a chave diretamente no YAML do workflow nem a commite no repositório — sempre leia de um secret como acima. Fique de olho na cota de 200 requisições/mês do plano gratuito quota se a suíte rodar a cada push ou em uma matriz — cada criação/consulta/exclusão de caixa de entrada conta contra ela, então uma suíte verbosa em um repositório movimentado pode esgotá-la rápido; faça upgrade ou dedique uma chave ao CI se isso acontecer. Chame deleteInbox() em um hook finally /after-hook para que execuções com falha não deixem caixas de entrada órfãs — mas, se você esquecer, o cron de expiração de 15 minutos limpa tudo automaticamente de qualquer forma.

CI/CD: OTPs no GitLab CI

Mesma ideia, executada como um job do GitLab CI: use a imagem Docker do Playwright para que playwright install não seja necessário, injete a chave como uma variável de CI/CD mascarada e execute a suíte.

# .gitlab-ci.yml
e2e:
  stage: test
  image: mcr.microsoft.com/playwright:v1.48.0-jammy
  variables:
    OTPBOX_KEY: $OTPBOX_KEY
  script:
    - npm ci
    - npx playwright test

Adicione OTPBOX_KEY em Settings → CI/CD → Variables do projeto, marcada como Masked e Protected para que nunca apareça nos logs dos jobs e só rode em branches protegidas.

O código de teste é idêntico ao exemplo do Playwright acima: crie uma caixa de entrada com createInbox(), preencha o formulário de cadastro com inbox.address, aguarde waitForOtp() e então chame deleteInbox() em um bloco finally. Os mesmos cuidados se aplicam — nunca imprima a chave, fique de olho na cota mensal em um pipeline movimentado e deixe o cron de expiração de 15 minutos limpar o que um job com falha deixar para trás.

CI/CD: OTPs no Jenkins

Um estágio declarativo Jenkinsfile que instala os navegadores do Playwright e executa a suíte, com a chave obtida do próprio armazenamento de credenciais do Jenkins em vez de uma variável de ambiente definida na configuração do job:

// Jenkinsfile
pipeline {
  agent any
  environment {
    OTPBOX_KEY = credentials('otpbox-key')
  }
  stages {
    stage('E2E') {
      steps {
        sh 'npm ci'
        sh 'npx playwright install --with-deps chromium'
        sh 'npx playwright test'
      }
    }
  }
}

Adicione otpbox-key em Manage Jenkins → Credentials como uma credencial Secret text — é isso que credentials('otpbox-key') acima resolve e mascara no console log.

Mesmo código de teste do exemplo do GitHub Actions: crie uma caixa de entrada, conduza o formulário de cadastro com o endereço dela, aguarde o código e exclua a caixa de entrada ao final. Mesmos cuidados também — nada específico do Jenkins muda em relação à cota ou à limpeza.

CI/CD: OTPs no CircleCI

Um único job usando a imagem Docker do Playwright, com a chave definida como uma variável de ambiente do projeto (o CircleCI injeta essas variáveis em todos os jobs automaticamente, sem necessidade de configuração explícita):

# .circleci/config.yml
version: 2.1
jobs:
  e2e:
    docker:
      - image: mcr.microsoft.com/playwright:v1.48.0-jammy
    steps:
      - checkout
      - run: npm ci
      - run: npx playwright test
workflows:
  test:
    jobs:
      - e2e

Adicione OTPBOX_KEY em Project Settings → Environment Variables — ela fica disponível como process.env.OTPBOX_KEY dentro do teste da mesma forma que localmente, sem precisar de bloco environment: na configuração.

Código de teste, cota e limpeza são exatamente como descrito acima: crie a caixa de entrada, execute o fluxo de cadastro do app com o endereço dela, aguarde o código e exclua a caixa de entrada depois.

MCP (para agentes de IA)

A mesma chave também funciona como um servidor MCP para clientes de agente/assistente de código (Claude, Cursor e outros) que falam MCP. É um servidor remoto (streamable HTTP) em https://otpbox.io/mcp; autentique com o mesmo cabeçalho Authorization: Bearer da API REST:

{
  "mcpServers": {
    "otpbox": {
      "url": "https://otpbox.io/mcp",
      "headers": { "Authorization": "Bearer <your-key>" }
    }
  }
}

Expõe 13 ferramentas — os mesmos dados subjacentes do /api/v1, chamáveis diretamente por um agente no meio de uma tarefa. As ferramentas de caixa de entrada e mensagens usam o id da caixa de entrada retornado por create_test_inbox.

FerramentaArgumentosO que faz
create_test_inboxdomain?, local?Cria uma caixa de entrada descartável; retorna o id, o endereço e a expiração.
wait_for_emailinboxId, timeoutSeconds? (1–25, padrão 20)Bloqueia até que uma nova mensagem chegue na caixa de entrada ou o timeout expire ({ timedOut, message }). Prefira isso a polling.
get_otpinboxIdO código de uso único mais recentemente extraído na caixa de entrada, se houver.
get_verification_linkinboxIdO link de confirmação/verificação mais recentemente extraído (url, host, type), se houver.
get_latest_emailinboxIdA mensagem mais recente completa: remetente, assunto, texto/HTML, código, link e anexos.
search_emailsinboxId, query?Lista mensagens da mais nova para a mais antiga (até 50), opcionalmente filtradas por correspondência de substring no remetente ou assunto.
delete_inboxinboxIdExclui permanentemente uma caixa de entrada e todas as mensagens nela.
create_batchcount (1–50), ttlHours?Cria várias caixas de entrada em uma única chamada; cada item tem seu próprio endereço, token e expiração.
create_test_identitytemplate, ttlHours?Cria uma persona sintética (nome, email, país, perfil) apoiada por uma caixa de entrada real. Modelos: us_customer, indian_customer, european_customer, business_customer, student, employee.
create_bulk_test_identitiestemplate, count (1–50), ttlHours?Cria várias identidades de teste em uma única chamada.
delete_test_identityidentityIdExclui uma identidade de teste e sua caixa de entrada de apoio.
register_webhookurl (https), eventsRegistra um webhook. Retorna o segredo de assinatura, mostrado uma única vez. Aceita os eventos de caixa de entrada, mensagem, link e identidade (não os dois eventos usage.* — registre-os via REST).
get_usagenenhumIgual ao GET /api/v1/usage: plan, quotaLimit, used neste mês, period, status e pooled (true quando a cota é compartilhada em uma organização).

Cada chamada de ferramenta conta como uma requisição contra sua cota mensal, e as ferramentas aplicam os mesmos escopos de chave que o REST. Falhas retornam como erros de ferramenta MCP (por exemplo, uma chamada acima da cota ou limitada por rate limit) em vez de códigos de status HTTP, e uma chave ausente, inválida, expirada ou inativa é rejeitada com 401 / 402 antes de qualquer ferramenta ser executada. Separadamente da cota mensal, um agente é limitado a 60 chamadas de ferramenta por minuto por chave.

Escopos de chave

Uma chave pode ser restrita a um conjunto de escopos quando criada no dashboard da organização (junto com um tipo de chave opcional, rótulo e data de expiração). Uma chave criada sem escopos — incluindo toda chave pessoal gratuita — pode usar tudo. Uma chave com escopos que chama algo fora deles recebe 403 { "error": "insufficient_scope", "required": "<scope>" }.

EscopoPermite
inbox:createPOST /api/v1/inboxes; MCP create_test_inbox
inbox:readGET /api/v1/inboxes/:id
inbox:deleteDELETE /api/v1/inboxes/:id; MCP delete_inbox, delete_test_identity
message:readGET /api/v1/inboxes/:id/messages, GET /api/v1/messages/:id; MCP wait_for_email, get_latest_email, search_emails
otp:readMCP get_otp, get_verification_link
identity:createMCP create_test_identity, create_bulk_test_identities
bulk:createPOST /api/v1/batches; MCP create_batch
webhook:manageGET / POST / DELETE /api/v1/webhooks; MCP register_webhook

GET /api/v1/usage, GET /api/v1/metrics, GET /api/v1/batches/:id e as rotas /api/v1/identities não são limitadas por escopo. Chaves também podem ter expiração: uma chave expirada recebe 401 key_expired.

Limites de taxa e cota

Medidos por mês calendário (UTC), não por janela contínua. Toda requisição /api/v1 autenticada e toda chamada de ferramenta MCP conta como uma requisição — incluindo leituras, o próprio GET /api/v1/usage e chamadas que terminam em erro, como 404. Chaves de organização compartilham a cota agrupada da organização (veja Plans); uma chave pessoal gratuita tem a própria. Exceder sua cota retorna 429 com { "error": "quota_exceeded", "used": …, "limit": … } — nada é cobrado por excesso; upgrade ou aguarde o mês virar. Não há CAPTCHA em /api/v1 ou /mcp — a própria cota é o controle de abuso.

Verifique sua situação a qualquer momento:

GET /api/v1/usage
→ { "plan": "api_5k_monthly", "quotaLimit": 5000, "used": 812, "period": "2026-09", "status": "active", "pooled": true }
CampoSignificado
planfree, api_5k_monthly (Pro) ou enterprise. Para uma chave de organização, é o plano da organização.
quotaLimitRequisições permitidas neste período, ou null quando não há limite fixo.
usedRequisições usadas até agora neste período. Para uma chave de organização, é a contagem agrupada de toda a organização.
periodO mês calendário, como YYYY-MM (UTC). Os contadores são redefinidos quando ele vira.
statusO status da chave (active para uma chave funcional).
pooledtrue quando a cota é compartilhada entre as chaves de uma organização.

Para ser avisado antes de bater no limite, assine um webhook para usage.approaching (80%) e usage.exceeded (100%). Dois outros limites são separados da cota mensal: a criação de chaves é limitada a 5 por dia por IP (429 rate_limited), e o MCP é limitado a 60 chamadas de ferramenta por minuto por chave.

Erros

Erros são JSON com um código error e um status HTTP apropriado; alguns carregam campos extras. Erros de autenticação (401/402) são retornados antes que qualquer coisa seja contada contra sua cota.

StatusCodeSignificado
400bad_countcount deve ser de 1 a 50 (max é retornado).
400bad_templateModelo de identidade desconhecido (validTemplates é retornado).
400bad_domainO domain solicitado não é um domínio ativo.
400bad_url / bad_eventsO url do webhook não é https://, ou nenhum nome de evento válido foi fornecido (validEvents é retornado).
400invalid_idempotency_keyO cabeçalho Idempotency-Key está malformado — consulte Chaves de idempotência.
401missing_keyNenhum cabeçalho Authorization: Bearer.
401invalid_keyA chave não é reconhecida.
401key_expiredA chave tinha uma data de expiração e ela já passou.
402license_inactiveA chave foi revogada ou não está ativa por outro motivo.
403insufficient_scopeOs escopos da chave não incluem o que esta chamada precisa (required nomeia o escopo).
404not_foundNão existe tal caixa de entrada, mensagem, lote, identidade ou webhook — ou ele pertence a uma chave diferente.
409address_unavailableA parte local solicitada já está em uso, ou nenhum endereço pôde ser alocado para uma identidade ou item de lote.
409try_againUm endereço aleatório não pôde ser alocado agora; tente novamente.
409idempotency_key_in_progressUma solicitação com o mesmo Idempotency-Key ainda está em execução.
429quota_exceededCota mensal esgotada (used e limit são retornados) — consulte Limites de taxa e cota.
429rate_limitedMuitas solicitações de criação de chave a partir do seu IP hoje (retryAfter, em segundos, é retornado).
503no_domainsNenhum domínio de caixa de correio está disponível no momento; tente novamente em breve.

Precisa de maior volume ou um SLA?

Executando uma grande matriz de testes, ou quer um plano dedicado com suporte prioritário? Conte-nos sobre seu caso de uso e entraremos em contato. Uptime atual: página de status.

Perguntas: abuse@otpbox.io.

Veja também: guias (Playwright, Cypress, agentes de IA), preços e o changelog.

← Voltar para OTPBox