Skip to content

API reference

Two endpoints for the core flow — send and verify — plus one optional endpoint to pre-link Telegram, and TOTP's own enroll/verify/disable trio. All POST, all JSON, all authenticated.

Browse the full schema reference — every endpoint →

Recommended: link Telegram before you ever send a code

Telegram can't be pushed to like WhatsApp — a recipient has to open a link and tap Start once before Authevo can message them there. Do that the moment they sign up, not the moment WhatsApp fails: it's a calm setup step instead of an interruption mid-verification, and from then on the fallback resolves silently in the background — no link, no tap, nothing your user has to notice. See Link Telegram below

Send OTP

POST/v1/otp/send

Generates a one-time code and delivers it to the phone number over WhatsApp. The code expires after the number of seconds returned in expires_in.

ParameterTypeRequiredDescription
phonestringRequiredRecipient phone number in E.164 format, including the country code.
Request
cURL
curl -X POST https://api.authevo.dev/v1/otp/send \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+201234567890" }'
Response
200 OK
{
  "data": {
    "message_id": "msg_9k2m4n8x",
    "status": "sent",
    "expires_in": 600
  }
}

A successful call returns the message identifier and a sent status.

Handling the Telegram fallback

If WhatsApp can't reach this recipient and they haven't linked Telegram yet, /send returns 409 CHANNEL_NOT_LINKED instead of the usual 200 — show them error.telegram_bot_url (a link, a QR code, any UI you like). Once they tap Start in Telegram, Authevo delivers a fresh code there automatically — you never need to retry /send or poll for anything. Prefer to skip this round-trip entirely? Call the Telegram-link endpoint below proactively, right after signup, so the fallback is already linked before it's ever needed.

409 CHANNEL_NOT_LINKED
{
  "error": {
    "code": "CHANNEL_NOT_LINKED",
    "message": "Could not deliver via WhatsApp, and this recipient has not linked the Telegram fallback. Ask them to open the link below in Telegram and tap Start — no typing required.",
    "telegram_bot_url": "https://t.me/authevo_otp_bot?start=aBc123XyZ..."
  }
}
curl -X POST https://api.authevo.dev/v1/otp/send \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+201234567890" }'

# If WhatsApp can't reach this recipient and they haven't linked the
# Telegram fallback yet, you get a 409 instead of the usual 200:
#
#   {
#     "error": {
#       "code": "CHANNEL_NOT_LINKED",
#       "message": "...",
#       "telegram_bot_url": "https://t.me/authevo_otp_bot?start=..."
#     }
#   }
#
# Show error.telegram_bot_url to this recipient. Once they tap Start in
# Telegram, Authevo delivers a fresh code there automatically — no need to
# retry /send or poll for anything.

Verify OTP

POST/v1/otp/verify

Checks the code your user entered against the one that was sent to their phone. Returns whether the code is valid.

ParameterTypeRequiredDescription
phonestringRequiredThe same phone number the code was sent to, in E.164 format.
codestringRequiredThe 6-digit code the user received over WhatsApp.
Request
cURL
curl -X POST https://api.authevo.dev/v1/otp/verify \
  -H "Authorization: Bearer sk_…" \
  -d '{ "phone": "+201234567890", "code": "123456" }'
Response
200 OK
{ "data": { "verified": true } }

When the code matches and is still valid, verified is true. Otherwise the request fails with an error envelope.

Two-Factor (TOTP)

A second, independent verification method — any authenticator app (Google Authenticator, Authy, 1Password), no message ever sent, just $0.002 per verification. Enroll once, then verify a rotating 6-digit code forever after.

In sandbox mode, enroll returns a fixed, public demo secret — add it to any authenticator app to get a genuinely valid, rotating test code. Unlike OTP's sandbox, TOTP test codes are NOT always 123456 — they rotate every 30 seconds like the real thing. Full sandbox details →

Enroll

POST/v1/totp/enroll

Issues a shared secret for a phone number and returns a ready-to-display QR code alongside the raw otpauth:// URL and secret — show the QR to your user, or let them type the secret in manually.

ParameterTypeRequiredDescription
phonestringRequiredThe phone number to enroll, in E.164 format.
replacebooleanOptionalPass true to replace an existing confirmed enrollment (e.g. the user lost their device). A confirmed enrollment already in place returns a 409 rather than silently overwriting it.
Request
cURL
curl -X POST https://api.authevo.dev/v1/totp/enroll \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+201234567890" }'
Response
200 OK
{
  "data": {
    "secret": "JBSWY3DPEHPK3PXP",
    "otpauth_url": "otpauth://totp/Authevo:+201234567890?secret=JBSWY3DPEHPK3PXP&issuer=Authevo&algorithm=SHA1&digits=6&period=30",
    "qr_code": "data:image/png;base64,iVBORw0KGgo...",
    "already_enrolled": false
  }
}

qr_code is a ready-to-use PNG data URI — set it directly as an image source, no QR library needed on your end. already_enrolled is true only when a previous confirmed secret existed for this phone.

Verify

POST/v1/totp/verify

Checks a 6-digit code from the user's authenticator app against their enrolled secret.

ParameterTypeRequiredDescription
phonestringRequiredThe enrolled phone number, in E.164 format.
codestringRequiredThe 6-digit code currently shown in the user's authenticator app.
Request
cURL
curl -X POST https://api.authevo.dev/v1/totp/verify \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+201234567890", "code": "123456" }'
Response
200 OK
{
  "data": {
    "verified": true,
    "first_confirm": true
  }
}

first_confirm is true only on the exact call that confirms a brand-new enrollment — a good moment to show a one-time "you're all set" message.

Disable

POST/v1/totp/disable

Turns off TOTP for a phone number. Safe to call more than once — disabling an already-disabled (or never-enrolled) phone still returns disabled: true.

ParameterTypeRequiredDescription
phonestringRequiredThe phone number to disable, in E.164 format.
Request
cURL
curl -X POST https://api.authevo.dev/v1/totp/disable \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+201234567890" }'
Response
200 OK
{
  "data": {
    "disabled": true
  }
}

Re-enrolling the same phone afterward does not require replace: true — a disabled enrollment is treated as a fresh start.