Add email OTP to any project
in under 10 minutes

Fast, Secure, Developer-Friendly and Self-Managed (No Admin).

Getting started Send OTP Verify OTP Code examples โšก AI integration Errors

1 ยท Getting started

Create an account at otply.xyz/signup.html and copy your API Key and API Secret from the dashboard. Every account starts on the Trial Plan โ€” 10 free OTP emails per month (refills monthly, no card required). Subscribe for unlimited emails at $9.99/month or $99.99/year.

Base URL

https://api.otply.xyz:8246/api/v1

Authentication โ€” two headers on every request

X-API-Key: otpm_your_key_here
X-API-Secret: your_secret_here
Server-side only. Never put these headers in browser JavaScript or mobile app code โ€” anyone could extract them and send OTPs on your account. Always call OTPLY from your backend.

POST2 ยท Send an OTP โ€” /send-otp

{
  "email": "user@example.com",
  "purpose": "login",          // optional, default "verification"
  "expiry_minutes": 10         // optional, 1-60, default 10
}

Response 200

{
  "success": true,
  "message": "OTP sent successfully",
  "expires_in": 600,
  "reference_id": "42"
}

On the Trial Plan the message also shows remaining quota, e.g. (Trial Plan: 9 of 10 remaining this month).

purpose namespaces your codes: a code sent with login can only be verified with login. Use separate purposes per flow โ€” signup, login, password-reset, confirm-action โ€” so they never collide.

POST3 ยท Verify an OTP โ€” /verify-otp

{
  "email": "user@example.com",
  "otp": "482913",
  "purpose": "login"           // must match the purpose used when sending
}

Response 200 (correct code)

{ "success": true, "message": "OTP verified successfully", "verified_at": "2026-07-29T18:45:00" }

Response 200 (wrong code)

{ "success": false, "message": "Invalid OTP. 2 attempt(s) remaining." }
A wrong OTP is not an HTTP error โ€” branch on the success field, not the status code.

Built-in rules

4 ยท Typical flows

Use caseFlow
Signup email verificationCreate account unverified โ†’ send-otp (purpose signup) โ†’ user enters code โ†’ verify-otp โ†’ mark verified
Two-factor login (2FA)Password correct โ†’ send-otp (purpose login) โ†’ verify-otp โ†’ issue session/JWT
Password resetsend-otp (purpose password-reset) โ†’ verify-otp โ†’ allow new password
Sensitive action confirmationsend-otp (purpose confirm-action) before deletes, payouts, transfers

5 ยท Code examples

cURL

curl -X POST https://api.otply.xyz:8246/api/v1/send-otp \
  -H "X-API-Key: $OTPLY_KEY" -H "X-API-Secret: $OTPLY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","purpose":"login"}'

Node.js

const OTPLY = 'https://api.otply.xyz:8246/api/v1';
const HEADERS = {
  'X-API-Key': process.env.OTPLY_KEY,
  'X-API-Secret': process.env.OTPLY_SECRET,
  'Content-Type': 'application/json'
};

const send = (email) => fetch(`${OTPLY}/send-otp`, {
  method: 'POST', headers: HEADERS,
  body: JSON.stringify({ email, purpose: 'login' })
}).then(r => r.json());

const verify = (email, otp) => fetch(`${OTPLY}/verify-otp`, {
  method: 'POST', headers: HEADERS,
  body: JSON.stringify({ email, otp, purpose: 'login' })
}).then(r => r.json());

Python

import os, requests

OTPLY = "https://api.otply.xyz:8246/api/v1"
HEADERS = {"X-API-Key": os.environ["OTPLY_KEY"],
           "X-API-Secret": os.environ["OTPLY_SECRET"]}

def send_otp(email, purpose="verification"):
    return requests.post(f"{OTPLY}/send-otp", headers=HEADERS,
        json={"email": email, "purpose": purpose}, timeout=30).json()

def verify_otp(email, otp, purpose="verification"):
    r = requests.post(f"{OTPLY}/verify-otp", headers=HEADERS,
        json={"email": email, "otp": otp, "purpose": purpose}, timeout=30)
    return r.json().get("success", False)

PHP

<?php
function otply(string $endpoint, array $body): array {
    $ch = curl_init("https://api.otply.xyz:8246/api/v1/$endpoint");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'X-API-Key: ' . getenv('OTPLY_KEY'),
            'X-API-Secret: ' . getenv('OTPLY_SECRET'),
            'Content-Type: application/json'
        ],
        CURLOPT_POSTFIELDS => json_encode($body),
        CURLOPT_TIMEOUT => 30
    ]);
    $out = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $out ?: ['success' => false, 'message' => 'Connection error'];
}
// otply('send-otp',   ['email' => $email, 'purpose' => 'login']);
// otply('verify-otp', ['email' => $email, 'otp' => $code, 'purpose' => 'login']);

โšก 6 ยท Integrate with AI in one prompt

Don't want to write the integration yourself? Paste the prompt below into Claude (claude.ai โ€” the free tier works) or, best of all, Claude Code, which edits your repository directly. Any current Claude model handles it. Just fill in the three bracketed lines at the top with your project details first.

I want to add email OTP authentication to my project using OTPLY, an OTP-as-a-Service API. Please integrate it for me.

MY PROJECT:
- Language/framework: [e.g. Node.js + Express / PHP / Python + Django / Laravel / Next.js]
- Flow I need: [signup email verification / 2FA login / password reset / action confirmation]
- Where to add it: [describe your existing auth files, or paste them]

OTPLY API SPEC:
- Base URL: https://api.otply.xyz:8246/api/v1
- Auth: every request needs headers X-API-Key and X-API-Secret (I have these; load them from environment variables OTPLY_KEY and OTPLY_SECRET - never hardcode them, never expose them in frontend/browser code; all OTPLY calls must go through my backend)
- Send a code: POST /send-otp with JSON {"email": "...", "purpose": "...", "expiry_minutes": 10}
  -> 200 {"success": true, "message": "...", "expires_in": 600}
- Verify a code: POST /verify-otp with JSON {"email": "...", "otp": "123456", "purpose": "..."}
  -> 200 {"success": true} if correct, or 200 {"success": false, "message": "Invalid OTP. 2 attempt(s) remaining."} if wrong - branch on the "success" field, NOT the HTTP status
- Rules: codes are 6 digits, single-use, max 3 wrong attempts, default 10-minute expiry; the same recipient can only be sent a new code every 30 seconds (HTTP 429 with wait time - show a resend countdown in the UI); use a distinct "purpose" string per flow (e.g. "signup", "login", "password-reset") - verification must use the same purpose as the send
- Errors: 401 = bad credentials, 403 = monthly trial quota exhausted (10 free/month; subscribers unlimited), 422 = invalid input, 429 = resend cooldown, 500 = retry later
- Full docs: https://otply.xyz/docs.html

WHAT I WANT YOU TO BUILD:
1. Backend endpoints in my framework: one to request an OTP, one to verify it, wired into my chosen flow (e.g. only mark the user verified / issue the session after successful verification)
2. A simple frontend piece: email entry -> 6-digit code entry screen with a resend button that respects the 30-second cooldown, and clear error messages for wrong code / attempts exceeded / cooldown
3. Proper handling of every error case listed above
4. Tell me exactly which files you created/changed and what environment variables I must set
๐Ÿ’ก Get your free API keys first at signup โ€” the Trial Plan (10 OTP emails/month) is enough to build and test the whole integration before subscribing.

7 ยท Other endpoints

EndpointMethodPurpose
/accountGETPlan, trial quota remaining, usage stats
/checkoutPOSTSubscribe โ€” body {"billing_cycle":"monthly"} or "yearly", returns payment URL
/billing-portalGETManage payment method, invoices, cancellation
/subscription/cancelPOSTStop auto-renewal (access until period end)
/healthGETService status โ€” no auth, use for monitoring

8 ยท Errors

HTTPMeaningWhat to do
401Bad API key/secretCheck headers against your dashboard
403Trial quota exhaustedSubscribe for unlimited, or wait for next month's refill
422Invalid inputFix email format / OTP must be 6 digits / expiry 1โ€“60
429Resend cooldown (30 s per recipient)Wait the indicated seconds; show a countdown in your UI
500Delivery or internal failureRetry shortly โ€” trial quota is auto-refunded on failed sends

9 ยท Best practices