OTP

Send OTP

Send or resend an idempotent email code for an email and purpose.

Copy page
POST/v1/send

Send code

cURL
curl --request POST \
  --url https://api.sendotp.email/v1/send \
  --header "Authorization: Bearer $SENDOTP_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"email":"[email protected]","purpose":"report-access","language":"en"}'

200

The code was accepted for delivery.
{
  "ok": true,
  "id": "otp_01JZ4NQ8F2T7G2A9J6P0G5QX3K",
  "resent": false,
  "expiresAt": 1700000600
}

Overview

Creates a live challenge when none exists, or emails the same live code again for the same normalized email and purpose. The default live window is 10 minutes.

Authorization

Bearer API keyAuthorizationrequired

Send a server API key in the Authorization header. Browser-origin requests are rejected so keys stay on your backend.

Request

emailstringrequired

Recipient email address. It is trimmed, lowercased, and validated before lookup.

purposestringrequired

Stable flow key such as login, report-access, signup, or account-delete.

languagestring

Optional email template language. Supported: en (English), es (Spanish), fr (French), de (German), pt (Portuguese), it (Italian), and nl (Dutch). Defaults to en. Accepts base codes like es or locale tags like es-MX; unsupported languages fall back to en.

Responses

200

The code was accepted for delivery.

{
  "ok": true,
  "id": "otp_01JZ4NQ8F2T7G2A9J6P0G5QX3K",
  "resent": false,
  "expiresAt": 1700000600
}
429

The same live challenge was re-sent too recently.

{
  "error": "resend_cooldown",
  "id": "otp_01JZ4NQ8F2T7G2A9J6P0G5QX3K",
  "expiresAt": 1700000600,
  "retryAfter": 30
}
429

The email and purpose exceeded the account rate limit.

{
  "error": "rate_limited"
}
422

The email address uses a known temporary or disposable domain.

{
  "error": "temporary_email_not_allowed",
  "message": "Please use a permanent email address to continue."
}