Create disposable inboxes and read one-time codes from a test suite, script, or backend job. Start with a free key — no account needed — or create an organization to share keys with a team and upgrade.
Live against the real API, right from this page — no signup. Mints a real (rate-limited) free key, creates a real inbox, then checks it for a message. Send the inbox address a real email from another tab to see it show up.
Click "Mint a free key" to start.
Free, 200 requests/month, simple bearer-token auth. Mint one instantly, no payment or account:
curl -X POST https://otpbox.io/api/v1/keys/free \
-H "content-type: application/json" \
-d '{}'
→ { "id": "lic_...", "key": "...", "plan": "free", "quotaLimit": 200 }
Save the returned key — like an inbox token, it's shown once and never stored anywhere you can read it back from. Limited to 5 free keys/day per IP.
Quotas are per calendar month. Organization plans pool the quota across every key the organization mints; a personal free key has its own. Full details on the pricing page.
| Plan | Requests / month | Price | How to get it |
|---|---|---|---|
| Personal free key | 200 per key | $0 | POST /api/v1/keys/free, no account |
| Organization — Free | 200, pooled | $0 | Sign up, create an organization, mint keys per project |
| Organization — Pro | 5,000, pooled | $9 / month | Dashboard → your organization → Plan & usage → Upgrade to Pro |
| Enterprise | Custom | Custom | Tell us about your use case |
Check what a key is entitled to at any time with GET /api/v1/usage — it returns the plan, the governing limit, and how much of it has been used this month.
Send your key as a bearer token on every request:
Authorization: Bearer <your-key>
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/inboxes | Create an inbox. Body: { domain?, local? }. Returns the address and an inbox-specific token. |
| GET | /api/v1/inboxes/:id | Inbox metadata. Only readable by the key that created it. |
| GET | /api/v1/inboxes/:id/messages | List messages, newest first, with extracted code/link hints. |
| GET | /api/v1/messages/:id | Full message: sanitized HTML, text, extracted code, attachments list. |
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;
The most common setup: a Playwright or Cypress E2E suite that signs up a real account and needs a real code to get past the OTP screen. The otpbox-sdk package (npm install otpbox-sdk) wraps the endpoints above in createInbox() / waitForOtp() / deleteInbox() so your workflow and test code don't hand-roll fetch calls.
# .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
Add OTPBOX_KEY under repo Settings → Secrets and variables → Actions so it's injected as an env var and never checked into the workflow file.
Inside the test, create a real inbox, fill the signup form with its address, then block on the code:
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);
});
A few CI-specific gotchas: never hardcode the key in the workflow YAML or commit it to the repo — always read it from a secret as above. Watch the free tier's 200 requests/month quota if the suite runs on every push or in a matrix — each inbox create/poll/delete counts against it, so a chatty suite on a busy repo can burn through it fast; upgrade or dedicate a key to CI if that happens. Call deleteInbox() in a finally/after-hook so failed runs don't leave orphaned inboxes around — though if you forget, the 15-minute expiry cron cleans them up automatically either way.
Same idea, run as a GitLab CI job: use the Playwright Docker image so playwright install isn't needed, inject the key as a masked CI/CD variable, and run the suite.
# .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
Add OTPBOX_KEY under your project's Settings → CI/CD → Variables, marked Masked and Protected so it never appears in job logs and only runs on protected branches.
The test code is identical to the Playwright example above: create an inbox with createInbox(), fill the signup form with inbox.address, block on waitForOtp(), then deleteInbox() in a finally block. The same gotchas apply — never print the key, watch the monthly quota on a busy pipeline, and let the 15-minute expiry cron clean up anything a failed job leaves behind.
A declarative Jenkinsfile stage that installs Playwright's browsers and runs the suite, with the key pulled from Jenkins' own credential store rather than an env var set in the job config:
// 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'
}
}
}
}
Add otpbox-key under Manage Jenkins → Credentials as a Secret text credential — that's what credentials('otpbox-key') above resolves and masks in the console log.
Same test code as the GitHub Actions example: create an inbox, drive the signup form with its address, wait for the code, delete the inbox when done. Same gotchas too — nothing Jenkins-specific changes about the quota or cleanup story.
A single job using the Playwright Docker image, with the key set as a project environment variable (CircleCI injects those into every job automatically, no explicit wiring needed):
# .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
Add OTPBOX_KEY under Project Settings → Environment Variables — it's then available as process.env.OTPBOX_KEY inside the test the same way it is locally, no environment: block in the config needed.
Test code, quota, and cleanup are exactly as described above: create the inbox, run the app's signup flow against its address, wait for the code, delete the inbox afterward.
The same key also works as an MCP server for agent/coding-assistant clients (Claude, Cursor, and others) that speak MCP:
{
"mcpServers": {
"otpbox": {
"url": "https://otpbox.io/mcp",
"headers": { "authorization": "Bearer <your-key>" }
}
}
}
Exposes 10 tools: create_test_inbox, wait_for_email, get_otp, get_verification_link, get_latest_email, search_emails, delete_inbox, get_usage, create_batch, register_webhook — the same underlying data as /api/v1, callable directly by an agent mid-task.
Metered by calendar month, not a rolling window. Organization keys share their organization's pooled quota (see Plans). Exceeding your quota returns 429 with { "error": "quota_exceeded", "used": …, "limit": … } — nothing is ever charged for overage; upgrade or wait for the month to roll over. There's no CAPTCHA on /api/v1 or /mcp — the quota itself is the abuse control.
Errors are JSON with an error code and an appropriate HTTP status: 401 invalid_key/missing_key, 402 license_inactive (key revoked), 404 not_found, 429 quota_exceeded.
Running a large test matrix, or want a dedicated plan with priority support? Tell us about your use case and we'll follow up. Current uptime: status page.
Questions: abuse@otpbox.io.
← Back to OTPBox