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.
| Plano | Requisições / mês | Preço | Como obter |
|---|---|---|---|
| Chave gratuita pessoal | 200 por chave | $0 | POST /api/v1/keys/free, sem conta |
| Organização — Gratuito | 200, agrupadas | $0 | Cadastre-se, crie uma organização, gere chaves por projeto |
| Organização — Pro | 5.000, agrupadas | $9 / mês | Painel → sua organização → Plano e uso → Fazer upgrade para Pro |
| Enterprise | Personalizado | Personalizado | Conte-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étodo | Caminho | Finalidade |
|---|---|---|
| POST | /api/v1/keys/free | Gere uma chave gratuita (sem autenticação; 5/dia/IP). Retorna { id, key, plan, quotaLimit }. |
| POST | /api/v1/keys/sandbox | Gere uma chave sandbox (sem autenticação; 5/dia/IP). |
| POST | /api/v1/inboxes | Crie 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/:id | Metadados da caixa de entrada: { id, address, domain, createdAt, expiresAt }. Somente legível pela chave que a criou. Escopo inbox:read. |
| DELETE | /api/v1/inboxes/:id | Exclua a caixa de entrada e todas as mensagens nela imediatamente. Retorna { ok: true }. Escopo inbox:delete. |
| GET | /api/v1/inboxes/:id/messages | Liste 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/:id | Mensagem 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/usage | Plano, limite e uso deste mês — consulte Limites de taxa e cota. |
| POST | /api/v1/batches | Crie até 50 caixas de entrada em uma única chamada — consulte Lotes. Escopo bulk:create. |
| GET | /api/v1/batches/:id | Status de um lote e resultados por item. |
| POST | /api/v1/identities | Crie uma identidade de teste: uma persona sintética apoiada por uma caixa de entrada real. |
| POST | /api/v1/identities/bulk | Crie até 50 identidades de teste em uma única chamada. |
| GET | /api/v1/identities/:id | Busque uma identidade de teste (o token da caixa de entrada nunca é reexibido). |
| DELETE | /api/v1/identities/:id | Exclua uma identidade e sua caixa de entrada de apoio. |
| GET | /api/v1/webhooks | Liste os webhooks desta chave. Escopo webhook:manage. |
| POST | /api/v1/webhooks | Registre um webhook. Escopo webhook:manage. |
| DELETE | /api/v1/webhooks/:id | Exclua um webhook. Escopo webhook:manage. |
| GET | /api/v1/metrics | Volume 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 retorna400 { "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
statusdo lote écompleted(todos os itens tiveram sucesso),partial(alguns falharam) oufailed(nenhum teve sucesso). Oidde um item concluído é o id da caixa de entrada, então você pode lê-lo comGET /api/v1/inboxes/:id/messages. (O exemplo acima está abreviado para dois itens.) - O
tokende cada item é o bearer token da própria caixa de entrada. Ele só é retornado por esta chamada de criação. GET /api/v1/batches/:idretorna{ id, status, requestedCount, createdAt, completedAt, items: [{ id, status, address, expiresAt, error }] }(sem tokens);idénullpara um item com falha. Lote desconhecido ou de outra pessoa:404 not_found.- Requer o escopo
bulk:createe aceita um Idempotency-Key.503 no_domainsse 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
emailno 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/messagescomAuthorization: Bearer <token>) para ler o que chega. - O
profileda persona depende do template:business_customeradicionacompanyerole,studentadicionauniversityemajor,employeeadicionacompany,departmentejobTitle; os templates de cliente retornam um perfil vazio. ttlHoursé opcional e limitado ao máximo do servidor (atualmente 3 horas). Erros:400 bad_template(comvalidTemplates),409 address_unavailable,503 no_domains.POST /api/v1/identities/bulkcom{ "template", "count", "ttlHours"? }(countde 1 a 50, caso contrário400 bad_count) retorna201 { batchId, template, requestedCount, items: [...] };batchIdse parece comidbatch_.... Cada item é{ id, status: "active", name, email, country, profile, token, expiresAt }, ou{ id, status: "failed", error }.GET /api/v1/identities/:idretorna a identidade sem seu token, além debatchIdeerror.DELETE /api/v1/identities/:idexclui a identidade e sua caixa de entrada de apoio e retorna{ ok: true, id, inboxDeleted }. Ids desconhecidos retornam404 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 retorna400 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
/inboxese/batchessem 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
}
urldeve começar comhttps://, caso contrário400 { "error": "bad_url" }.eventsé uma lista do catálogo abaixo. Nomes desconhecidos são ignorados; se nenhum for válido, você recebe400 { "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/webhooksretorna{ webhooks: [{ id, url, events, status, createdAt }] }(nunca o segredo).statuséactiveoudisabled.DELETE /api/v1/webhooks/:idretorna{ ok: true }, ou404 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.
| Evento | Dispara quando |
|---|---|
inbox.created | Uma 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.deleted | Uma caixa de entrada é excluída com DELETE /api/v1/inboxes/:id ou a ferramenta MCP delete_inbox. |
inbox.expired | Uma de suas caixas de entrada atinge sua expiração e é removida pelo job de limpeza (que é executado a cada 15 minutos). |
message.received | Um e-mail chega em uma de suas caixas de entrada. |
otp.extracted | Um e-mail recebido continha um código de uso único. Mesmo payload que message.received. |
link.detected | Um e-mail recebido continha uma confirmação ou outro link. |
identity.created | Uma identidade de teste é criada. Uma criação em massa dispara um evento para todo o lote, com um payload em formato de lote. |
identity.deleted | Uma identidade de teste é excluída. |
usage.approaching | O uso atinge pela primeira vez 80% do limite mensal. Uma vez por período de cobrança. |
usage.exceeded | O 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 emGET /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 comPOST /api/v1/webhooks. O dashboard também envia um eventowebhook.testsob 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.
| Ferramenta | Argumentos | O que faz |
|---|---|---|
create_test_inbox | domain?, local? | Cria uma caixa de entrada descartável; retorna o id, o endereço e a expiração. |
wait_for_email | inboxId, 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_otp | inboxId | O código de uso único mais recentemente extraído na caixa de entrada, se houver. |
get_verification_link | inboxId | O link de confirmação/verificação mais recentemente extraído (url, host, type), se houver. |
get_latest_email | inboxId | A mensagem mais recente completa: remetente, assunto, texto/HTML, código, link e anexos. |
search_emails | inboxId, query? | Lista mensagens da mais nova para a mais antiga (até 50), opcionalmente filtradas por correspondência de substring no remetente ou assunto. |
delete_inbox | inboxId | Exclui permanentemente uma caixa de entrada e todas as mensagens nela. |
create_batch | count (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_identity | template, 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_identities | template, count (1–50), ttlHours? | Cria várias identidades de teste em uma única chamada. |
delete_test_identity | identityId | Exclui uma identidade de teste e sua caixa de entrada de apoio. |
register_webhook | url (https), events | Registra 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_usage | nenhum | Igual 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>" }.
| Escopo | Permite |
|---|---|
inbox:create | POST /api/v1/inboxes; MCP create_test_inbox |
inbox:read | GET /api/v1/inboxes/:id |
inbox:delete | DELETE /api/v1/inboxes/:id; MCP delete_inbox, delete_test_identity |
message:read | GET /api/v1/inboxes/:id/messages, GET /api/v1/messages/:id; MCP wait_for_email, get_latest_email, search_emails |
otp:read | MCP get_otp, get_verification_link |
identity:create | MCP create_test_identity, create_bulk_test_identities |
bulk:create | POST /api/v1/batches; MCP create_batch |
webhook:manage | GET / 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 }
| Campo | Significado |
|---|---|
plan | free, api_5k_monthly (Pro) ou enterprise. Para uma chave de organização, é o plano da organização. |
quotaLimit | Requisições permitidas neste período, ou null quando não há limite fixo. |
used | Requisições usadas até agora neste período. Para uma chave de organização, é a contagem agrupada de toda a organização. |
period | O mês calendário, como YYYY-MM (UTC). Os contadores são redefinidos quando ele vira. |
status | O status da chave (active para uma chave funcional). |
pooled | true 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.
| Status | Code | Significado |
|---|---|---|
| 400 | bad_count | count deve ser de 1 a 50 (max é retornado). |
| 400 | bad_template | Modelo de identidade desconhecido (validTemplates é retornado). |
| 400 | bad_domain | O domain solicitado não é um domínio ativo. |
| 400 | bad_url / bad_events | O url do webhook não é https://, ou nenhum nome de evento válido foi fornecido (validEvents é retornado). |
| 400 | invalid_idempotency_key | O cabeçalho Idempotency-Key está malformado — consulte Chaves de idempotência. |
| 401 | missing_key | Nenhum cabeçalho Authorization: Bearer. |
| 401 | invalid_key | A chave não é reconhecida. |
| 401 | key_expired | A chave tinha uma data de expiração e ela já passou. |
| 402 | license_inactive | A chave foi revogada ou não está ativa por outro motivo. |
| 403 | insufficient_scope | Os escopos da chave não incluem o que esta chamada precisa (required nomeia o escopo). |
| 404 | not_found | Não existe tal caixa de entrada, mensagem, lote, identidade ou webhook — ou ele pertence a uma chave diferente. |
| 409 | address_unavailable | A parte local solicitada já está em uso, ou nenhum endereço pôde ser alocado para uma identidade ou item de lote. |
| 409 | try_again | Um endereço aleatório não pôde ser alocado agora; tente novamente. |
| 409 | idempotency_key_in_progress | Uma solicitação com o mesmo Idempotency-Key ainda está em execução. |
| 429 | quota_exceeded | Cota mensal esgotada (used e limit são retornados) — consulte Limites de taxa e cota. |
| 429 | rate_limited | Muitas solicitações de criação de chave a partir do seu IP hoje (retryAfter, em segundos, é retornado). |
| 503 | no_domains | Nenhum 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.