otpbox

API docs

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.

Try it now

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.

Get a key

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.

Plans

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.

PlanRequests / monthPriceHow to get it
Personal free key200 per key$0POST /api/v1/keys/free, no account
Organization — Free200, pooled$0Sign up, create an organization, mint keys per project
Organization — Pro5,000, pooled$9 / monthDashboard → your organization → Plan & usage → Upgrade to Pro
EnterpriseCustomCustomTell 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.

Authentication

Send your key as a bearer token on every request:

Authorization: Bearer <your-key>

Endpoints

MethodPathPurpose
POST/api/v1/inboxesCreate an inbox. Body: { domain?, local? }. Returns the address and an inbox-specific token.
GET/api/v1/inboxes/:idInbox metadata. Only readable by the key that created it.
GET/api/v1/inboxes/:id/messagesList messages, newest first, with extracted code/link hints.
GET/api/v1/messages/:idFull message: sanitized HTML, text, extracted code, attachments list.

Example: get a code in a Playwright test

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;

CI/CD: OTPs in GitHub Actions

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.

CI/CD: OTPs in GitLab CI

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.

CI/CD: OTPs in Jenkins

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.

CI/CD: OTPs in CircleCI

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.

MCP (for AI agents)

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.

Rate limits and quota

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

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.

Need higher volume or an SLA?

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