Udeyou OTP - Node.js Integration Guide ====================================== Base URL https://udeyou.com/api/v1 Docs https://udeyou.com/docs Format JSON requests and responses (Content-Type: application/json) Auth Authorization: Bearer or X-API-Key: This English guide is the reference version. The code is the same code shown on https://udeyou.com/docs, tested against the live API. 1. Before you start 1) Create an account: https://udeyou.com/register New accounts start a free 2-month trial: 2000 messages in the first month and 2000 more in the second. 2) Create an API key in the dashboard: https://udeyou.com/dashboard#keys The key is shown once. Keep it in the UDEYOU_API_KEY environment variable on your server. 3) Key types udy_test_... No email and no charge, works with a zero balance. The code comes back in the response ("code") together with "test_mode": true. Use it for development and tests. udy_live_... Sends the email and uses 1 credit per code. The credit is refunded automatically if delivery fails. Each key only sees codes sent with its own type. 4) Keep the key on your server. Never put it in browser JavaScript, a mobile app or a code repository. 2. Requirements and run Node.js 18 or later (built-in fetch). No packages. Save the file below as udeyou.mjs, then: UDEYOU_API_KEY=udy_test_... node udeyou.mjs user@example.com With a test key the example verifies automatically. With a live key it asks for the code from the email. 3. How the example works - sendOtp(to, options), verifyOtp(otpId, code) and getBalance() return the "data" object of the response. - Failures throw UdeyouError with status, code, retryAfter, attemptsRemaining and fields. Timeouts and connection errors use status 0 and the code network_error. - In an Express or Next.js backend, store otp_id in the user session and use data.to from the verify response as the verified address. 4. Full example ----- BEGIN udeyou.mjs ----- // Udeyou OTP - Node.js 18+ (built-in fetch, no dependencies) // Usage: UDEYOU_API_KEY=udy_test_... node udeyou.mjs user@example.com import { createInterface } from 'node:readline/promises'; const BASE_URL = (process.env.UDEYOU_BASE_URL ?? 'https://udeyou.com/api/v1').replace(/\/+$/, ''); const API_KEY = process.env.UDEYOU_API_KEY; class UdeyouError extends Error { constructor(status, error = {}) { super(error.message ?? `HTTP ${status}`); this.status = status; this.code = error.code ?? `http_${status}`; this.retryAfter = error.retry_after; // 429 responses this.attemptsRemaining = error.attempts_remaining; // invalid_code this.fields = error.fields; // validation_error } } async function udeyou(method, path, body) { let res; try { res = await fetch(BASE_URL + path, { method, headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(20_000), }); } catch (err) { // Timeout, DNS or connection failure throw new UdeyouError(0, { code: 'network_error', message: err.message }); } const json = await res.json().catch(() => ({})); if (!res.ok || json.success !== true) throw new UdeyouError(res.status, json.error ?? {}); return json.data; } const sendOtp = (to, options = {}) => udeyou('POST', '/otp/send', { to, ...options }); const verifyOtp = (otpId, code) => udeyou('POST', '/otp/verify', { otp_id: otpId, code }); const getBalance = () => udeyou('GET', '/balance'); // 1) Send the code const to = process.argv[2] ?? 'user@example.com'; try { const sent = await sendOtp(to, { lang: 'ar' }); console.log(`Sent ${sent.otp_id} to ${sent.to} (expires in ${sent.expires_in} s)`); // 2) Verify the code the user typed. Test keys (udy_test_) return it in the response. let code = sent.code; if (!code) { const rl = createInterface({ input: process.stdin, output: process.stdout }); code = (await rl.question('Code: ')).trim(); rl.close(); } const result = await verifyOtp(sent.otp_id, code); console.log(`Verified ${result.to} at ${result.verified_at}`); } catch (err) { if (!(err instanceof UdeyouError)) throw err; console.error(`Error ${err.status} ${err.code}: ${err.message}`); if (err.retryAfter) console.error(`Retry after ${err.retryAfter} s`); if (err.attemptsRemaining !== undefined) console.error(`Attempts remaining: ${err.attemptsRemaining}`); if (err.fields) console.error(err.fields); process.exitCode = 1; } ----- END udeyou.mjs ----- 5. API reference POST /otp/send -> 201 Created to string required Recipient email, up to 190 characters. sender_name string optional Name shown in the inbox, 2-60 characters. Default: your account name. lang string optional "ar" (default) or "en": language of the default message and footer. code_length integer optional 4-10 digits. Default 6. expires_in integer optional Lifetime in seconds, 60-3600. Default 300. code string optional Your own code, 4-10 letters or digits. Otherwise Udeyou generates a numeric code. template string optional Plain text up to 1000 characters. Must contain {{code}}; {{sender_name}} and {{minutes}} are also available. subject string optional Up to 120 characters, same placeholders. reply_to string optional Address that receives the customer's replies. Send code_length and expires_in as JSON numbers: "6" as a string is rejected with 422. Sending a new code to the same address cancels the previous ones. Every email ends with a line saying it was sent on behalf of sender_name through Udeyou OTP. data: otp_id, to, status ("sent"), mode ("live" or "test"), expires_in, expires_at (ISO 8601, UTC), credits_remaining, and test_mode + code with a test key (keep the code as a string: it may start with 0). POST /otp/verify -> 200 OK code string required The code the customer typed (4-10 letters or digits). otp_id string recommended The id returned by /otp/send. to string alternative Recipient email instead of otp_id; checks the latest code sent to it. data: verified (true), otp_id, to, verified_at. Use data.to as the verified address. A code works once, expires after expires_in seconds and is locked after 5 wrong attempts. GET /balance -> 200 OK data: company, mode, credits. GET /health -> 200 OK (no key needed) {"success": true, "status": "ok"} Success: {"success": true, "data": {...}} Failure: {"success": false, "error": {"code": "...", "message": "...", ...}} 6. Errors (use error.code in your logic; message is English help text) 400 invalid_json Body is not a JSON object. 400 invalid_code Wrong code. attempts_remaining = tries left. 401 invalid_api_key Key missing, malformed, unknown or revoked. 402 insufficient_credits Live key and a zero balance. 403 account_suspended The account is suspended. 404 otp_not_found No code matches otp_id or to for this key type. 404 not_found Unknown endpoint. 405 method_not_allowed HTTP method not supported for this endpoint. 409 otp_already_used The code was already used. 410 otp_expired Expired, or a newer code was sent to the same address. 422 validation_error Invalid fields (details in error.fields on /otp/send). 429 rate_limited More than 60 send requests per minute (retry_after). 429 resend_too_soon Less than 60 s since the last code to this address (retry_after). 429 recipient_limit 5 codes to this address in the last hour (retry_after). 429 too_many_attempts All 5 attempts used: request a new code. 500 server_error Unexpected server error. 502 delivery_failed The email could not be delivered; the credit was refunded. 7. Limits (per account, shared by live and test keys) - Send requests: 60 per minute (rate_limited, retry_after 60). - Gap between two codes to the same address: 60 seconds (resend_too_soon, retry_after 60). - Codes to the same address: 5 per hour (recipient_limit, retry_after 3600). - Wrong attempts: 5 per code (too_many_attempts, then request a new code). - Code lifetime: 300 seconds by default, 60-3600 with expires_in (otp_expired). Only /otp/send counts toward the per-minute limit. retry_after is the window length in seconds, not the time left: wait that long, then retry. 8. Best practices - Keep the key in an environment variable on the server only. - Use a test key in development and automated tests, a live key in production. - Store otp_id in the user session on the server and verify with it. - Trust data.to from the verify response, not an address sent by the app. - Treat the code as a string; it may start with 0. - Show attempts_remaining, and disable "resend" for 60 seconds after each send. - Set a timeout on every request and hide network errors from users. - Revoke a leaked key in the dashboard, then create a new one. - Never log codes or keys. Support: https://udeyou.com/contact (include the otp_id and the error code)