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.
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.
send_otp
Generate a code, store the hash with expiry and attempt counter, dispatch the SMS through your paired Android.
User reads it
The customer receives the SMS on their phone, types the code into your app's verify form.
verify_otp
Constant-time compare against the latest issued code. Returns verified plus remaining attempts on mismatch.
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
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.
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.
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.
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.