OTP reference

OTP and phone verification through SMS8

Send and verify one-time passwords from Claude Code, Cursor, Windsurf or any HTTP client. Configurable code length, expiry, and attempts. Hard per-phone abuse cap.

How it works

Two endpoints, one round-trip

Same logic whether the caller is an AI assistant via MCP, an external client via HTTPS, or your own backend.

STEP 01

send_otp

Generate a code, store the hash with expiry and attempt counter, dispatch the SMS through your paired Android.

STEP 02

User reads it

The customer receives the SMS on their phone, types the code into your app's verify form.

STEP 03

verify_otp

Constant-time compare against the latest issued code. Returns verified plus remaining attempts on mismatch.

Endpoints

POST only, Bearer auth

GET returns 405. Cookies ignored. The API key never lands in URLs or browser history.

Send endpoint

POST https://app.sms8.io/ajax/otp-send.php

  • phone — required, E.164
  • length — 4 to 8 digits, default 6
  • expires_in — 60 to 900 seconds, default 300
  • max_attempts — 1 to 10, default 5
  • template — body with {code} placeholder
  • option / devices / useRandomDevice — device picker

Verify endpoint

POST https://app.sms8.io/ajax/otp-verify.php

  • phone — required, E.164
  • code — the code the user typed

Responses:

  • {verified: true} on success
  • {verified: false, reason: "code_mismatch", attempts_left: 4}
  • Other reasons: expired, not_found, max_attempts
curl examples

Working calls

Send an OTP

curl -X POST https://app.sms8.io/ajax/otp-send.php \
  -H "Authorization: Bearer YOUR_SMS8_API_KEY" \
  -d "phone=+1234567890"

# Override defaults:
#   -d "length=6"  -d "expires_in=300"
#   -d "max_attempts=5"
#   -d "template=Your code is {code}, expires soon."

# Pick a sender device or SIM:
#   -d "option=0" --data-urlencode 'devices=["DEVICE_ID"]'
#   -d "option=1"               # broadcast across all devices
#   -d "option=2"               # broadcast across all SIMs
#   -d "useRandomDevice=1"      # one random sender

Verify a code

curl -X POST https://app.sms8.io/ajax/otp-verify.php \
  -H "Authorization: Bearer YOUR_SMS8_API_KEY" \
  -d "phone=+1234567890" \
  -d "code=123456"

# Success: {"verified": true}
# Failure: {"verified": false, "reason": "code_mismatch", "attempts_left": 4}

From an AI assistant (MCP)

If Claude Code, Cursor, or Windsurf is connected to mcp.sms8.io, ask: "Add SMS phone verification to my signup flow using the sms8 MCP". The assistant calls send_otp and verify_otp with your defaults and wraps the existing signup or login routes.

Defaults you control

Per-user defaults via the dashboard

Set fallbacks once. They apply whenever a caller leaves the field blank. Hard limits stay in place regardless.

Code length

4 to 8 digits. Default 6. Tune for memorability vs brute-force resistance.

Expiry

60 to 900 seconds. Default 300 (5 min). Short enough to limit replay, long enough for users to type.

Verify attempts

1 to 10 wrong guesses before the code is locked. Default 5.

Resend cooldown

30 to 600 seconds between sends to the same phone. Default 60.

SMS template

Customize the body. Must contain the literal {code} placeholder.

Edit your defaults

Live preview reflects every setting. Send a real OTP to your phone and verify the round-trip.

Open OTP settings →

Security model

Hard caps that protect your customers

5 OTPs per phone per 24h

The hardest cap. Not user-configurable. Even if your API key leaks, no single phone can be spammed beyond 5 codes per rolling 24-hour window.

Transaction-locked checks

Cooldown and the 24h cap run under SELECT ... FOR UPDATE. Parallel callers cannot race past the limit.

Constant-time verify

verify_otp uses hash_equals for the compare. No timing leaks.

POST only, no cookies

GET on either endpoint returns 405. Cookies are ignored on auth resolution. CSRF via image tags is not possible.

Per-OTP attempt cap

Default 5 wrong attempts per code. After that, the code is locked and a new send_otp is required.

Generic error messages

Internal exceptions return Internal error. Stack traces never leak to clients.

FAQ

OTP questions

How do I send an OTP through SMS8?

POST to https://app.sms8.io/ajax/otp-send.php with your phone number. The endpoint accepts an Authorization: Bearer header. Optional fields: length (4 to 8), expires_in (60 to 900s), max_attempts (1 to 10), template, plus a device picker.

How does verify_otp work?

POST the phone and the code the user typed to https://app.sms8.io/ajax/otp-verify.php. The server runs a constant-time compare against the latest OTP and returns verified: true or verified: false. On failure, reason and attempts_left are returned.

What is the OTP abuse cap?

Any single phone can receive at most 5 OTPs in a rolling 24-hour window. This hard cap is not user-configurable and protects recipients even if your API key leaks.

Do I need A2P 10DLC for SMS OTPs through SMS8?

No. SMS8 routes OTPs through your paired Android phone and SIM, so A2P 10DLC registration is not required.

Can the AI assistant add OTP verification automatically?

Yes. The bundled Claude Code Skill teaches the assistant when to call send_otp and verify_otp. A prompt like Add phone verification to /signup is enough.

Add phone verification in 60 seconds

Sign up free, set your OTP defaults, and let your AI assistant wire the verification into your app.