Email & SMS verification
Prove that someone controls an email address or a phone number. Send a one-time code, then check the code they typed. Both calls answer synchronously, so there is nothing to poll and no webhook to wait for.
This is the server-to-server form of the contact step the SDK runs inside a verification flow. Use it when you want the possession check on its own: at sign-up, before a payout, when a customer changes their number.
Authentication: Authorization: Bearer sk_… (secret keys only). The response describes a real person’s contact details and the call spends your credits on a message, so it never belongs in client-side code.
Production note: sk_live_ keys require an approved business.
Send a code
destination comes back normalised: an email is lowercased, a phone number is E.164. Store that form, because it is what the check and the entity record use.
Codes are valid for five minutes.
Check the code
A wrong code answers 400 invalid_code with attemptsRemaining, so you can tell the person how many tries are left. When the budget runs out the challenge is dead and you send a new code.
Re-checking a verified challenge is safe. It returns the same record and does not charge again.
Read a challenge
The same record, at any point in its life. status is pending, verified or expired. Useful for reconciling your own state after a dropped response.
What the signals tell you
Every challenge carries the intelligence computed when the code was sent, at no extra cost. Branch on it however your risk policy needs.
Phone
Pricing
A check bills the same component your workflows already bill for the same work: email_verification or phone_verification on the sdk_verification module. One rate card governs both doors, and Billing in your dashboard shows the rate.
Three rules follow from that:
- A code that is never checked costs nothing. Billing happens when the check completes and returns an answer, not when the message goes out.
- We refuse before we send. If your balance cannot cover the check the send answers
402 insufficient_creditswith the required amount, rather than spending a message you cannot pay for. - Sandbox and development are free, and
chargedAmountisnullthere.
chargedAmount on a completed check is what it cost, in USD, as a decimal string.
Sandbox
On sk_test_ and sk_dev_ keys nothing is sent to anyone. The code is all zeros at the length you asked for, so 000000 by default, 0000 at codeLength: 4. deliveryChannel comes back as test. Everything else behaves exactly as it does live, so you can build and test the whole flow at no cost.
Rate limits
Two caps protect your account and ours, both counted per rolling hour:
- Five codes per destination. Hammering one number is stopped before it starts.
- A per-organisation ceiling across all destinations, 500 by default. This is what bounds a leaked key rotating through numbers. Contact support if your volume needs more.
Over either cap, the send answers 429 send_rate_limited.
Using a verified contact in a KYC flow
A contact proved here is deliberately not carried into a POST /verify submission. That submission prices its own contact steps, so honouring a proof from this API would bill the same check twice. If you want the contact verified as part of a verification, use the SDK’s contact step, which is configured per workflow.
What this API does record, when you send an externalUserId and an entity already exists under it, is the proved contact on that entity’s compliance record as otp_verified evidence, which is the strongest source there is. It never creates an entity that does not exist.