API Documentation

Overview

OTPBox provides a simple REST API for creating disposable email inboxes and retrieving verification codes programmatically.

All API responses are JSON. Free: 10 inbox creates/hr per IP (1h TTL). Pro: 120/hr with API key (6h TTL). Polling unrestricted on both.

✓ Polling is Unrestricted

Only POST /api/inbox (create inbox) counts toward the 10/hour limit. All retrieval endpoints are unrestricted:

  • GET /api/inbox/:token (get inbox state) — poll freely
  • GET /api/inbox/:token/latest (get latest OTP) — poll freely
  • Example: 1 create + 100 polls = only 1 request counts toward limit

CI Strategy: Poll as often as needed. Aggressive polling (e.g., every second) works fine since only inbox creation is rate limited.

Plans: Free vs Pro

Free — $0, no account

  • 10 inbox creates/hr per IP
  • 1h TTL
  • Poll /latest unrestricted
  • OTP + magic-link extraction

Pro — $9/mo

  • API key (Bearer or x-api-key)
  • 120 creates/hr
  • 6h TTL
  • 24h message history — debug failed tests anytime
  • Same API — more headroom for parallel CI

See /pricing. No seats/teams/SLA — you pay for create-rate + longer TTL + persistent debugging.

Pro auth example
curl -X POST https://otpbox.app/api/inbox   -H "Authorization: Bearer YOUR_API_KEY"

# or
curl -X POST https://otpbox.app/api/inbox   -H "x-api-key: YOUR_API_KEY"

Create Inbox

POST /api/inbox

Creates a new temporary inbox. Returns the inbox email address and access token.

Response
{
  "id": "abc123xyz789",
  "email": "abc123xyz789@box.otpbox.app",
  "expiresAt": 1704153600000,
  "token": "def456uvw012..."
}
Example
curl -X POST https://otpbox.app/api/inbox

Get Inbox State

GET /api/inbox/:token

Retrieves the inbox metadata and all messages. Messages are sorted newest first.

Response
{
  "inbox": {
    "id": "abc123xyz789",
    "email": "abc123xyz789@box.otpbox.app",
    "token": "def456uvw012...",
    "createdAt": 1704150000000,
    "expiresAt": 1704153600000
  },
  "messages": [
    {
      "id": "msg001",
      "inboxId": "abc123xyz789",
      "from": "noreply@service.com",
      "subject": "Your verification code",
      "body": "Your code is: 123456",
      "otp": "123456",
      "magicLink": null,
      "receivedAt": 1704150100000
    }
  ]
}

Get Latest Extraction

GET /api/inbox/:token/latest

Returns only the extracted OTP and/or magic link from the most recent message. Useful for CI/CD automation.

Response (200 OK)
{
  "otp": "123456",
  "link": "https://service.com/verify?token=...",
  "from": "noreply@service.com",
  "subject": "Your verification code",
  "receivedAt": 1704150100000
}
Response (404 Not Found)
{
  "error": "No messages found"
}

This endpoint returns 404 when the inbox exists but has no messages yet. Use a wait/retry loop in your automation (see CI examples below).

CI / Automation

OTPBox is designed for headless automation. Create a disposable inbox, trigger your app to send a verification email, then poll for the OTP.

Using Pro in CI

Store your Pro key as OTPBOX_API_KEY (local env or GitHub Actions secret). Send Authorization: Bearer $OTPBOX_API_KEY on POST /api/inbox only — polls stay unauthenticated.

Free: 10 creates/hr per IP, 1h TTL. Pro: 120/hr, 6h TTL, 24h message history — better for matrix jobs and debugging failed tests anytime. Get a key at /pricing.

Pro: 24-Hour Message History

✓ Never Lose an OTP

Pro keeps all your messages for 24 hours, even after the inbox expires. Debug failed tests from last night, check what emails your CI received, and never lose a verification code.

# Fetch all messages from the last 24h
curl https://otpbox.app/api/pro/history \
  -H "Authorization: Bearer YOUR_PRO_KEY"

# Filter by timestamp (Unix ms)
curl "https://otpbox.app/api/pro/history?since=1704150000000" \
  -H "Authorization: Bearer YOUR_PRO_KEY"

# Response includes all messages across all your inboxes
{
  "messages": [
    {
      "id": "abc123",
      "from": "noreply@app.com",
      "subject": "Verify your email",
      "otp": "123456",
      "magicLink": "https://app.com/verify?token=...",
      "receivedAt": 1704151234567,
      "inboxId": "xyz789"
    }
  ],
  "count": 1,
  "retention": "24 hours"
}

Free tier: messages deleted when inbox expires (1h). Pro tier: persistent 24h history for debugging.

Shell Script Example

#!/bin/bash
set -e

# 1. Create disposable inbox (optional Pro: export OTPBOX_API_KEY)
echo "Creating inbox..."
AUTH_HEADER=()
if [ -n "$OTPBOX_API_KEY" ]; then
  AUTH_HEADER=(-H "Authorization: Bearer $OTPBOX_API_KEY")
fi
RESPONSE=$(curl -s -X POST https://otpbox.app/api/inbox "${AUTH_HEADER[@]}")
TOKEN=$(echo "$RESPONSE" | jq -r '.token')
EMAIL=$(echo "$RESPONSE" | jq -r '.email')

echo "Inbox: $EMAIL"
echo "Token: $TOKEN"

# 2. Trigger your app to send verification email to $EMAIL
# Example: curl -X POST https://yourapp.com/signup -d "email=$EMAIL"
echo "Triggering verification email..."
# ... your app logic here ...

# 3. Poll for OTP (unrestricted - poll as needed)
echo "Waiting for OTP..."
MAX_ATTEMPTS=10
SLEEP_SECONDS=3

for i in $(seq 1 $MAX_ATTEMPTS); do
  RESPONSE=$(curl -s https://otpbox.app/api/inbox/$TOKEN/latest)
  OTP=$(echo "$RESPONSE" | jq -r '.otp // empty')
  
  if [ -n "$OTP" ] && [ "$OTP" != "null" ]; then
    echo "✓ Got OTP: $OTP"
    
    # 4. Use the OTP in your test
    # Example: curl -X POST https://yourapp.com/verify -d "code=$OTP"
    exit 0
  fi
  
  echo "  Attempt $i/$MAX_ATTEMPTS - no message yet, waiting..."
  sleep $SLEEP_SECONDS
done

echo "✗ Timeout: No OTP received after $MAX_ATTEMPTS attempts"
exit 1

GitHub Actions Example

name: E2E Verification Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Create OTPBox inbox
        id: inbox
        # Free still works without the secret. Optional: set OTPBOX_API_KEY for Pro
        # (matrix runners that hit Free 429 / need 120/hr + 6h TTL).
        env:
          OTPBOX_API_KEY: ${{ secrets.OTPBOX_API_KEY }}
        run: |
          AUTH=()
          if [ -n "$OTPBOX_API_KEY" ]; then
            AUTH=(-H "Authorization: Bearer $OTPBOX_API_KEY")
          fi
          RESPONSE=$(curl -s -X POST https://otpbox.app/api/inbox "${AUTH[@]}")
          echo "token=$(echo $RESPONSE | jq -r '.token')" >> $GITHUB_OUTPUT
          echo "email=$(echo $RESPONSE | jq -r '.email')" >> $GITHUB_OUTPUT
      
      - name: Trigger signup with OTPBox email
        run: |
          curl -X POST https://yourapp.com/api/signup \
            -H "Content-Type: application/json" \
            -d '{"email": "${{ steps.inbox.outputs.email }}"}'
      
      - name: Wait for OTP
        id: otp
        run: |
          for i in {1..10}; do
            RESPONSE=$(curl -s https://otpbox.app/api/inbox/${{ steps.inbox.outputs.token }}/latest)
            OTP=$(echo "$RESPONSE" | jq -r '.otp // empty')
            
            if [ -n "$OTP" ] && [ "$OTP" != "null" ]; then
              echo "otp=$OTP" >> $GITHUB_OUTPUT
              echo "✓ Received OTP: $OTP"
              exit 0
            fi
            
            echo "Waiting for OTP (attempt $i/10)..."
            sleep 3
          done
          
          echo "✗ Timeout waiting for OTP"
          exit 1
      
      - name: Verify OTP
        run: |
          curl -X POST https://yourapp.com/api/verify \
            -H "Content-Type: application/json" \
            -d '{"code": "${{ steps.otp.outputs.otp }}"}'

Rate Limits & Best Practices

Rate Limit:Free 10 creates/hr per IP · Pro 120/hr with API key; polling unrestricted on both
Inbox Lifetime:Free 1h TTL · Pro 6h TTL
Polling Strategy:Poll as aggressively as needed — retrieval endpoints are not rate limited
Error Handling:/latest returns 404 when inbox is empty. Check response.otp for null or empty.
Free vs Pro:Free is the lead magnet for light CI. Hit the create wall? Pro $9/mo.

Wait/Retry Pattern

Email delivery typically takes 1-5 seconds. The /latest endpoint returns 404 with { "error": "No messages found" } when the inbox exists but has no messages yet.

Recommended pattern: Poll every 3 seconds for up to 30 seconds (10 attempts). Since polling is unrestricted, feel free to poll more aggressively if needed.

# Minimal wait/retry in curl (polling is unrestricted)
for i in {1..10}; do
  OTP=$(curl -s https://otpbox.app/api/inbox/$TOKEN/latest | jq -r '.otp // empty')
  [ -n "$OTP" ] && [ "$OTP" != "null" ] && break
  sleep 3
done

Error Responses

404 Not Found

Inbox does not exist

410 Gone

Inbox has expired

429 Too Many Requests

Rate limit exceeded on POST /api/inbox. Free-tier wall body (live):

{
  "error": "Free tier: 10 inbox creates/hr per IP. Need more for CI? See Pro → https://otpbox.app/pricing"
}

Notes

  • Inboxes expire after 1 hour (Free) or 6 hours (Pro)
  • Tokens are opaque and random - do not parse or predict them
  • Messages appear typically within seconds of being sent
  • For real-time updates, poll every 3-5 seconds or use the web UI
  • OTP extraction uses regex patterns for 4-8 digit codes
  • Magic link detection looks for common verification URL patterns