Documentation

Everything you need to integrate the Nixify OTP API. Public — no sign-in required.

Quick Start
Make your first OTP request in minutes.
1

Create a test API key

Go to API Keys in the dashboard, click Create API Key, choose development environment, then copy the generated mg_test_… key. Test keys run in sandbox mode automatically — no real email is sent and the OTP code is returned in the response body.

2

Make your first request

curl
curl -X POST https://nixify.vercel.app/api/v1/otp/send \
  -H "Authorization: Bearer mg_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","purpose":"signup"}'

Use your mg_test_ key for the Quick Start. The response includes a code field with the plaintext OTP so you can call /verify immediately without checking an inbox.

3

Verify the code

JavaScript
const res = await fetch('https://nixify.vercel.app/api/v1/otp/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer mg_test_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ email: 'user@example.com', purpose: 'signup' }),
});
const data = await res.json();
console.log(data.code);        // sandbox: the plaintext OTP (mg_test_ only)
console.log(data.otp_request_id);

// Then verify:
const verify = await fetch('https://nixify.vercel.app/api/v1/otp/verify', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer mg_test_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'user@example.com',
    code: data.code,
    purpose: 'signup',
  }),
});
console.log((await verify.json()).verified); // true
Test vs live keys: mg_test_ keys run in sandbox mode (no real email, code returned in the response, per-email rate limits skipped). mg_live_ keys send real email via Nixify's managed delivery and enforce all rate limits. When you're ready to go live, create a production environment key and swap mg_test_xxx for mg_live_xxx in your code.
AI Prompt Helper
Copy this prompt, paste it into any AI model, and get a step-by-step integration guide for any programming language — written for beginners.

How this works

  1. Click Copy Prompt below — the prompt is pre-written and includes all the API details.
  2. Paste it into any AI model (ChatGPT, Claude, DeepSeek, or Z.ai).
  3. Replace [MY PROGRAMMING LANGUAGE] with your language (JavaScript, Python, PHP, Go, etc.).
  4. The AI will generate a complete, beginner-friendly step-by-step guide with full code, error handling, and comments.
The Prompt (copy this)
You are a helpful coding assistant. I'm a beginner and I want to use the Nixify email OTP verification service in my project.

Please write a complete, step-by-step guide for integrating Nixify into my app using [MY PROGRAMMING LANGUAGE — e.g., JavaScript, Python, PHP, Go, Ruby, Java, C#]. Explain each step simply so a beginner can follow along.

Here's what you need to know about Nixify:

SERVICE OVERVIEW
- Nixify is an email OTP (one-time password) verification API.
- You send a user's email address to Nixify, Nixify emails them a 6-digit code, then you verify the code they entered.
- Three API endpoints: send OTP, verify OTP, resend OTP.
- Base URL: https://nixify.vercel.app/api/v1

AUTHENTICATION
- Create an API key in the Nixify dashboard. Use mg_test_ for development and CI, mg_live_ for production.
- Send the key as a Bearer token in the Authorization header:
  Authorization: Bearer mg_test_xxxxxxxxxxxxxxxxxxxxxxxx

SANDBOX MODE (mg_test_ keys only)
- Test keys run in sandbox mode automatically: OTPs are generated and stored but NO real email is sent. The plaintext 6-digit code is returned in the "code" field of the /send and /resend response so you can call /verify immediately without an inbox.
- Test keys skip the per-email rate limit (3/min, 10/hour) so CI can run fast. The per-IP limit still applies. User-owned test keys still consume the plan API_MESSAGES quota.
- Optionally force a simulated error with the X-Sandbox-Simulate header: rate_limited, locked, expired, mismatch, smtp_error.
- Live keys (mg_live_) CANNOT use sandbox mode — they always send real email.

STEP 1 — SEND OTP
POST /api/v1/otp/send
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

Body:
{
  "email": "user@example.com",
  "purpose": "signup"
}

Response (200):
{
  "otp_request_id": "uuid-here",
  "request_id": "trace-uuid",
  "expires_at": "2026-07-06T22:50:00.000Z",
  "message": "OTP sent",
  "code": "123456"
}
With a mg_live_ key, Nixify emails the user a 6-digit code and the "code" field is NOT present. The code expires in 10 minutes. With a mg_test_ key, no email is sent and "code" contains the plaintext OTP (sandbox mode).

STEP 2 — VERIFY OTP
POST /api/v1/otp/verify
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

Body:
{
  "email": "user@example.com",
  "code": "123456",
  "purpose": "signup"
}

Response (200):
{
  "verified": true,
  "otp_request_id": "uuid-here",
  "request_id": "trace-uuid"
}
If verified is true, the email is confirmed. Each code can only be used once.

STEP 3 — RESEND OTP (optional, if the user didn't get the email)
POST /api/v1/otp/resend
Body: { "email": "user@example.com", "purpose": "signup" }

RATE LIMITS
- Per email — /send: 3 per minute, 10 per hour (mg_live_ keys only; test keys skip these)
- Per IP — /send: 10 per minute, 60 per hour (all keys)
- Per IP — /verify: 30 per minute, 120 per hour (all keys)
- When rate limited, the API returns 429. IP-level and email-level 429s include a Retry-After header (seconds); email-level 429s also include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Plan-rate 429s (rate_limited from the per-minute plan rate) include X-RateLimit-Reset and X-Quota-Remaining instead of Retry-After.
- All responses include X-Request-Id (matches the body request_id) and X-Api-Version: 1. Successful (2xx) responses include X-Quota-Remaining.

ERROR HANDLING
The API returns JSON errors with this shape:
{ "error": { "code": "rate_limited", "message": "Too many OTP sends.", "doc_url": "/docs#error-rate_limited" }, "request_id": "uuid" }
The doc_url field points to a public docs anchor that explains the code.
Common error codes: validation_failed, unauthorized, key_revoked, key_expired, insufficient_scope, rate_limited, code_mismatch, expired, already_used, locked, not_found, ip_blocked, quota_exceeded, feature_not_available, internal_error.

WHAT I NEED FROM YOU
1. Write the complete integration in [MY LANGUAGE] — a single file I can run.
2. Include all three steps: send, verify, resend.
3. Show how to handle errors (try/catch, check response status, display the error message to the user).
4. Note that otp_request_id is for correlation/observability only — verify does NOT require it as input.
5. Add comments explaining each line for a beginner.
6. Show how to test it locally (what to install, how to run it).
7. Keep it simple — no frameworks, just plain [MY LANGUAGE] code using the standard library or a simple HTTP client.

Now paste it into an AI model

Hover over the button below, then click any AI model to open it in a new tab. Paste the prompt, replace the language placeholder, and you'll get a complete guide.

Authentication
All API requests require a Bearer token.

Send your API key in the Authorization header as a Bearer token:

Header
Authorization: Bearer mg_test_xxxxxxxxxxxxxxxxxxxxxxxx
test
mg_test_…

Development & CI. Sandbox mode is automatic — OTPs are generated and persisted exactly as in production, but no real email is sent; the plaintext code is returned in the code field of the/send and /resend response. Per-email rate limits are skipped so tests can run fast. Plan API_MESSAGES quota still applies to user-owned test keys.

Optionally force simulated errors with the X-Sandbox-Simulate header (one of rate_limited, locked,expired, mismatch,smtp_error). Live keys cannot use sandbox mode.

live
mg_live_…

Production only. Nixify sends real email through its managed delivery infrastructure (API customers do not provide SMTP credentials). All rate limits and quotas are enforced. Sandbox mode is not available.

API Reference
Three endpoints, one purpose: verify an email address.
POST/api/v1/otp/send

Issue + deliver a new OTP code to the given email.

Request body
email*stringRFC 5322 email address (lowercased, trimmed)
purposestringsignup | login | reset (defaults to signup)
Response body
otp_request_idstringOTP correlation ID (for webhook correlation)
request_idstringAPI request trace ID (matches X-Request-Id header)
expires_atstring (ISO)10-minute TTL
messagestring"OTP sent"
codestringSandbox only (mg_test_ keys): the plaintext 6-digit OTP. Never present for mg_live_ keys.
Example request
{
  "email": "user@example.com",
  "purpose": "signup"
}
Example response
{
  "otp_request_id": "f3a2b1c8-...",
  "request_id": "a1b2c3d4-...",
  "expires_at": "2026-07-06T22:50:00.000Z",
  "message": "OTP sent",
  "code": "123456"
}
Possible errors
validation_failedrate_limitedlockedip_blockedinternal_error
POST/api/v1/otp/verify

Verify the 6-digit code entered by the user.

Request body
email*stringSame email used in /send
code*stringExactly 6 numeric digits
purposestringsignup | login | reset (defaults to signup)
Response body
verifiedbooleantrue on success
otp_request_idstringOTP correlation ID of the consumed attempt
request_idstringAPI request trace ID (matches X-Request-Id header)
Example request
{
  "email": "user@example.com",
  "code": "123456",
  "purpose": "signup"
}
Example response
{
  "verified": true,
  "otp_request_id": "f3a2b1c8-...",
  "request_id": "a1b2c3d4-..."
}
Possible errors
validation_failedcode_mismatchexpiredalready_usedlockednot_foundrate_limitedip_blockedinternal_error
POST/api/v1/otp/resend

Send a fresh code if the user didn't receive the first one.

Request body
email*stringTarget email
purposestringsignup | login | reset (defaults to signup)
Response body
otp_request_idstringOTP correlation ID for the new attempt
request_idstringAPI request trace ID (matches X-Request-Id header)
expires_atstring (ISO)10-minute TTL
messagestring"OTP resent"
codestringSandbox only (mg_test_ keys): the plaintext 6-digit OTP. Never present for mg_live_ keys.
Example request
{
  "email": "user@example.com",
  "purpose": "signup"
}
Example response
{
  "otp_request_id": "9c1d7e44-...",
  "request_id": "e5f6g7h8-...",
  "expires_at": "2026-07-06T22:55:00.000Z",
  "message": "OTP resent",
  "code": "654321"
}
Possible errors
validation_failedrate_limitedlockedip_blockedinternal_error

All endpoints can also return authentication errors (unauthorized, key_revoked,key_expired, insufficient_scope) and plan-entitlement errors (quota_exceeded,feature_not_available). See the Error Codes section below for the full catalog.

API Client
Use the REST API from any HTTP client.
JavaScript (fetch)
const res = await fetch('https://nixify.vercel.app/api/v1/otp/send', { method: 'POST', headers: { 'Authorization': 'Bearer mg_test_xxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'user@example.com', purpose: 'signup' }) });
Python (requests)
import requests; res = requests.post('https://nixify.vercel.app/api/v1/otp/send', headers={'Authorization': 'Bearer mg_test_xxx'}, json={'email': 'user@example.com', 'purpose': 'signup'})
Webhooks
Receive signed event deliveries on your own endpoints.

Register endpoint URLs in the Webhooks dashboard. Each delivery is signed with HMAC-SHA256 and includes the Nixify-Signature and Nixify-Event headers:

Delivery headers
Nixify-Signature: t=1720000000000,v1=8c2f1e9a7b3d4f5e6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
Nixify-Event: otp.sent
Content-Type: application/json

The t component is a millisecond timestamp; v1 is the HMAC-SHA256 of `${t}.${payload}` using your endpoint secret. Reject any delivery older than 5 minutes to prevent replay attacks.

Verify the signature

Node.js
import crypto from 'crypto';

function verify(secret, payload, signatureHeader) {
  const { t, v1 } = Object.fromEntries(
    signatureHeader.split(',').map(p => p.split('='))
  );
  const signed = `${t}.${payload}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signed)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(v1)
  );
}

Events

  • otp.sent — code was generated + delivered
  • otp.verified — user successfully verified
  • otp.failed — verification failed (wrong code)
  • otp.expired — 10-minute TTL elapsed without verification
Rate Limits
Per-email and per-IP throttles to prevent abuse.
ScopeLimitWindow
Per email — /send31 minute
Per email — /send101 hour
Per IP — /send10 / 601 min / 1 hr
Per IP — /verify30 / 1201 min / 1 hr

Per-email limits apply to mg_live_ keys only; test keys skip them so CI can run fast. Per-IP limits apply to all keys.

Response headers

  • All responses include X-Request-Id (matches the body's request_id) and X-Api-Version: 1.
  • Successful (2xx) responses include X-Quota-Remaining for plan quota tracking.
  • Rate-limited responses (429): IP-level and email-level 429s include a Retry-After header (seconds); email-level 429s additionally include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.
  • Plan-rate 429s (the per-minute plan rate limit, returned as rate_limited from the entitlement engine) include X-RateLimit-Reset and X-Quota-Remaining — they do not include Retry-After.
Error Codes
The API uses a consistent error envelope with stable codes. The full catalog is below — no dashboard login required.
Error envelope (every error response)
{
  "error": {
    "code": "rate_limited",
    "message": "Too many OTP sends. Retry in 47s.",
    "doc_url": "/docs#error-rate_limited"
  },
  "request_id": "a1b2c3d4-..."
}

The doc_url field always points to a public anchor on this page — every code below has its own #error-<code> jump link.

validation_failedHTTP 400Validation Failed

The request body or parameters failed validation.

Causes
  • Missing required field (email, code, password)
  • Invalid email format
  • OTP code is not exactly 6 digits
  • Password is shorter than 8 characters
Fixes
  • Check the `message` field for the specific field that failed
  • Ensure email is a valid RFC 5322 address
  • OTP codes must be exactly 6 numeric digits
unauthorizedHTTP 401Unauthorized

No valid API key was provided.

Causes
  • Missing Authorization header
  • API key has an invalid format (must start with mg_live_ or mg_test_)
  • API key does not exist
Fixes
  • Create an API key in the dashboard at /dashboard/api-keys
  • Send it as: Authorization: Bearer mg_live_xxx
key_revokedHTTP 401API Key Revoked

The API key has been revoked and can no longer be used.

Causes
  • An admin revoked the key
  • The key was rotated and the old one revoked
Fixes
  • Generate a new API key
  • Update your application's environment variables
key_expiredHTTP 401API Key Expired

The API key has passed its expiration date.

Causes
  • The key was created with an expiry that has now passed
Fixes
  • Generate a new API key
  • For long-lived keys, omit the expiration
insufficient_scopeHTTP 403Insufficient Scope

The API key does not have permission for this action.

Causes
  • A read-only key was used for a write operation
  • The key's scopes don't include the required action
Fixes
  • Use a key with `full` scope or the specific required scope
  • Update the key's scopes in the dashboard
rate_limitedHTTP 429Rate Limited

Too many requests in the time window. The rate_limited code covers three independent limiters: per-email (3/min, 10/hour), per-IP (send + verify), and the plan's per-minute API request rate. Not every 429 of this code includes a Retry-After header — see the fixes for the two header patterns.

Causes
  • Exceeded 3 OTP sends per email per minute
  • Exceeded 10 OTP sends per email per hour
  • Exceeded an IP-level rate limit (send or verify)
  • Exceeded the plan's per-minute API request rate (entitlement engine)
Fixes
  • IP/email 429s: wait for the Retry-After header (seconds) before retrying; email-level 429s additionally include X-RateLimit-Limit/Remaining/Reset
  • Plan-rate 429s (no Retry-After): wait for X-RateLimit-Reset and check X-Quota-Remaining; implement exponential backoff
  • Reduce request frequency or upgrade to a plan with a higher per-minute rate
lockedHTTP 423Locked

Too many failed verification attempts.

Causes
  • 5 incorrect OTP attempts on a single code
  • 10 cumulative failed verifies (brute-force lockout)
Fixes
  • Wait 15 minutes for the per-code lockout to expire
  • Wait 30 minutes for the account lockout to expire
  • If the lock persists after the cooldown, contact support with the request ID
code_mismatchHTTP 400Code Mismatch

The OTP code did not match the stored code.

Causes
  • User typed the wrong code
  • Code was for a different email or purpose
Fixes
  • Ask the user to re-enter the code
  • Request a new code via the resend endpoint
expiredHTTP 410OTP Expired

The OTP code has expired (10-minute TTL).

Causes
  • More than 10 minutes passed since the code was issued
Fixes
  • Request a new code via POST /api/v1/otp/resend
already_usedHTTP 409OTP Already Used

This OTP code has already been consumed (single-use).

Causes
  • The code was already verified successfully
  • A concurrent request consumed it first
Fixes
  • Request a new code if you need to verify again
disposable_emailHTTP 422Disposable Email Rejected

The email domain is on the disposable-email blocklist.

Causes
  • The domain (e.g. mailinator.com) is blocked
Fixes
  • Use a real email address
ip_blockedHTTP 403IP Blocked

The client IP has been temporarily suspended.

Causes
  • Too many rate-limit violations from this IP
Fixes
  • Wait for the block to expire
  • Contact support if you believe this is an error
not_foundHTTP 404Not Found

The requested resource was not found.

Causes
  • No active OTP found for this email
  • Account does not exist
Fixes
  • Request a new OTP first
  • Check the email address spelling
quota_exceededHTTP 402Monthly API Quota Exceeded

Your plan's monthly API_MESSAGES quota has been exhausted. This quota is consumed by every authenticated v1 API request — not just the OTP endpoints (broadcasts, suppressions, groups, events, deliveries, and all other v1 routes also consume it). It is separate from the per-email and per-IP rate limits.

Causes
  • The API key owner's plan has used all of its monthly API_MESSAGES allotment
  • Note: mg_test_ (sandbox) keys owned by a user ALSO consume this quota — sandbox mode skips real email delivery and the per-email rate limit, but not the plan quota
Fixes
  • Wait for the quota to reset on the next billing cycle
  • Upgrade to a higher plan for a larger monthly API_MESSAGES quota
  • Reduce request volume by batching or caching where possible
feature_not_availableHTTP 402Feature Not Available

Your current plan does not include access to this feature.

Causes
  • The API key owner's plan does not grant the required feature entitlement
Fixes
  • Upgrade to a plan that includes this feature
  • Use a different API key associated with an eligible plan
internal_errorHTTP 500Internal Server Error

An unexpected error occurred.

Causes
  • SMTP connection failure
  • Database error
  • Unexpected server bug
Fixes
  • Retry with exponential backoff
  • Contact support with the request ID from the response body or the X-Request-Id header
Changelog
Notable changes to the v1 API.
v1.0.02026-07-06
  • Initial public release.
  • Endpoints: /api/v1/otp/send, /api/v1/otp/verify, /api/v1/otp/resend.
  • API keys (mg_test_ / mg_live_) with full + read_only scopes.
  • Webhooks with HMAC-SHA256 signed deliveries (Nixify-Signature + Nixify-Event headers).
  • Sandbox mode is automatic for mg_test_ keys: OTPs are persisted but not emailed; the plaintext code is returned in the response. The optional X-Sandbox-Simulate header forces simulated errors (rate_limited, locked, expired, mismatch, smtp_error) for testing.