Verification (OTP)

The Verify endpoints handle one-time codes end to end: generation, expiry, attempt limits and validation. You never store the code.

Start and check

POST /verify/start sends a one-time code; POST /verify/check validates the code the user typed. The code lives on our side only: you send a destination and get back a masked status, never the code itself. A wrong code is still a 200, with valid: false and the attempts you have left. Codes go out by SMS or email (the channel field), and both calls require the sms:send scope. Start accepts an Idempotency-Key, like every write that costs money: a network retry returns the same challenge instead of sending a second code.

cURL
curl -X POST https://api.mailer.quathos.com/api/v1/verify/start \
  -H "Authorization: Bearer $QSENDYX_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "to": "+14155550100", "channel": "sms" }'
Start a verification.
cURL
curl -X POST https://api.mailer.quathos.com/api/v1/verify/check \
  -H "Authorization: Bearer $QSENDYX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+14155550100", "code": "481902" }'
Check the code the user typed.

SMS codes are delivered under the platform's own sending identity. That means you do not need 10DLC onboarding or a dedicated number to verify users: Verify reaches Brazil, the United States and Canada out of the box. Destinations beyond those are refused with 403 destination_not_allowed.

OTP endpoints are a favourite abuse target: a script can trigger thousands of verifications. Rate-limit by user and by destination on your side too, not only by IP. The platform already enforces its own ceilings (at most 3 code sends per destination per hour, 10 SMS per day to the same number, plus a daily spend limit), but your product knows its users better than any global cap: pumping fraud routes codes to premium numbers to collect a share of the termination fee.

Custom services

/verify/start uses a default verification service: a six-digit numeric code, five-minute expiry, five attempts, at most three sends per destination per hour. To change any of that, create your own service with POST /verify/services: code length (4–10), expiry (60–3600 seconds), attempt limit, default channel, and use its id: POST /verify/services/{id}/challenges sends the code, POST /verify/services/{id}/checks validates it.

cURL
curl -X POST https://api.mailer.quathos.com/api/v1/verify/services/{service_id}/challenges \
  -H "Authorization: Bearer $QSENDYX_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "to": "+5511999990000", "channel": "sms" }'
Start a verification on a custom service.
cURL
curl -X POST https://api.mailer.quathos.com/api/v1/verify/services/{service_id}/checks \
  -H "Authorization: Bearer $QSENDYX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+5511999990000", "code": "481902" }'
Check against the same service.

What a verification costs

A verification bills in two parts. The message that carries the code is a normal send: SMS codes are billed per segment at the destination rate, and email codes follow the same quota rules as any transactional email. On top of that, a successful SMS verification carries a US$ 0.05 success fee: reserved when the code goes out, consumed only when the check confirms it, and released back if the code expires or runs out of attempts. Email verifications have no success fee.

The reservation happens up front: a start with insufficient balance is refused with 402 before any code is sent. In the test environment nothing is charged.