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 freelyGET /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.
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/inboxCreates a new temporary inbox. Returns the inbox email address and access token.
{
"id": "abc123xyz789",
"email": "abc123xyz789@box.otpbox.app",
"expiresAt": 1704153600000,
"token": "def456uvw012..."
}curl -X POST https://otpbox.app/api/inbox
Get Inbox State
GET /api/inbox/:tokenRetrieves the inbox metadata and all messages. Messages are sorted newest first.
{
"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/latestReturns only the extracted OTP and/or magic link from the most recent message. Useful for CI/CD automation.
{
"otp": "123456",
"link": "https://service.com/verify?token=...",
"from": "noreply@service.com",
"subject": "Your verification code",
"receivedAt": 1704150100000
}{
"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 1GitHub 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
/latest returns 404 when inbox is empty. Check response.otp for null or empty.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
doneError Responses
404 Not FoundInbox does not exist
410 GoneInbox has expired
429 Too Many RequestsRate 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