Saudi Arabia identity verification APIs — OTP via SMS/WhatsApp/Email, Face & Voice biometrics, and transactional SMS. Built for developers: REST/JSON, minimal examples, and fast integration.
Free to start
- Email OTP is free — send & verify by email at no cost.
- 100 free credits on signup for SMS, WhatsApp, Face, and Voice.
- 👉 Create your account »
A single page you can read on GitHub and immediately understand how to integrate Authentica:
- OTP via SMS / WhatsApp / Email
- Biometrics: Face & Voice verification
- Custom SMS and Balance checks
This README includes minimal, commented Node.js & Python snippets. Full, runnable scripts live in
/examples/(also readable inline on GitHub). Cloning is optional.
Authentica is a Saudi identity verification platform that provides simple, secure JSON APIs for OTP, biometrics, and voice-based authentication. Teams use Authentica to verify users, prevent fraud, and streamline onboarding with fast, developer-friendly integration.
What Authentica offers
- Multi-channel OTP: Send and verify one-time codes via SMS, WhatsApp, or Email.
- Biometrics: Face and Voice matching to step-up trust when needed.
- Outbound messaging: Custom SMS using approved sender IDs.
- Developer-friendly: Clean REST endpoints, JSON payloads, and clear error semantics.
Common use cases
- Sign-up / login (passwordless or 2FA)
- Risk-based step-up for sensitive actions
- Account recovery and user re-verification
- Transactional notifications and alerts (SMS)
What this repo provides
- A docs-first README you can read on GitHub to learn flows end-to-end.
- Short deep-dive pages in
/docs/for OTP, Face, Voice, and SMS. - Tiny, runnable examples in
/examples/for Node.js and Python—kept minimal so you can copy/paste quickly.
- Skim Before you start (headers, formats, optional env names).
- Pick a flow (OTP, Face, Voice, SMS, Balance).
- Copy a Node or Python snippet as your starting point.
- If you want a runnable script, click the file under Examples Index.
- About Authentica
- Before you start
- OTP — Send & Verify
- Face Verification
- Voice Verification
- Custom SMS
- Balance
- Examples Index
- Docs quick links
- Troubleshooting
- Security & Best Practices
- Useful Links & Contact
authentica-documentation/
├─ README.md # You are here
├─ docs/ # Short, focused deep dives (optional reading)
│ ├─ otp.md
│ ├─ face.md
│ ├─ voice.md
│ └─ custom-sms.md
│
└─ examples/
├─ node/
│ ├─ otp_send.js # tiny, commented scripts
│ ├─ otp_verify.js
│ ├─ face_verify.js
│ ├─ voice_verify.js
│ ├─ custom_sms.js
│ └─ balance.js
└─ python/
├─ otp_send.py
├─ otp_verify.py
├─ face_verify.py
├─ voice_verify.py
├─ custom_sms.py
└─ balance.py
| Feature | Node.js file | Python file |
|---|---|---|
| OTP — Send | examples/node/otp_send.js | examples/python/otp_send.py |
| OTP — Verify | examples/node/otp_verify.js | examples/python/otp_verify.py |
| Face verify | examples/node/face_verify.js | examples/python/face_verify.py |
| Voice verify | examples/node/voice_verify.js | examples/python/voice_verify.py |
| Custom SMS | examples/node/custom_sms.js | examples/python/custom_sms.py |
| Balance | examples/node/balance.js | examples/python/balance.py |
Every example is intentionally short and commented. Use them as runnable references; no boilerplate frameworks beyond
express(Node) andfastapi/requests(Python) where needed.
Base URL
https://api.authentica.sa
Auth header
X-Authorization: YOUR_API_KEY
JSON headers
Accept: application/json
Content-Type: application/json
Runtime requirements
- Node.js 18+ (global
fetch) or add afetchpolyfill for older versions - Python 3.9+
Formats & notes
- Phone numbers: E.164 (e.g.,
+9665XXXXXXXX). - Face/Voice media: base64 strings (avoid logging in production).
- Timeouts/retries: treat all calls as network I/O; add retries with jitter in your app if needed.
Optional environment variables (used by examples; not required to understand the README)
AUTHENTICA_API_KEY=your_api_key
BASE_URL=https://api.authentica.sa
Use for: Login, 2FA, passwordless, recovery.
sequenceDiagram
autonumber
participant App as Your App
participant Auth as Authentica
App->>Auth: POST /api/v2/send-otp (sms/whatsapp/email)
Auth-->>App: 200 (queued/sent)
Note over App: User receives OTP
App->>Auth: POST /api/v2/verify-otp (phone/email + otp)
Auth-->>App: 200 (verified: true/false)
// sendOtp('sms', '+9665XXXXXXXX') or sendOtp('email', 'user@example.com') async function sendOtp(method, recipient) { const BASE_URL = process.env.BASE_URL || 'https://api.authentica.sa'; const API_KEY = process.env.AUTHENTICA_API_KEY || 'YOUR_API_KEY'; const body = method === 'email' ? { method, email: recipient } : { method, phone: recipient }; const res = await fetch(`${BASE_URL}/api/v2/send-otp`, { method: 'POST', headers: { 'Accept':'application/json','Content-Type':'application/json','X-Authorization': API_KEY }, body: JSON.stringify(body) }); const json = await res.json(); if (!res.ok) throw new Error(JSON.stringify(json)); return json; // { success: true, ... } }
# send_otp('sms', '+9665XXXXXXXX') or send_otp('email', 'user@example.com') import os, json, requests def send_otp(method: str, recipient: str): BASE_URL = os.getenv('BASE_URL','https://api.authentica.sa') API_KEY = os.getenv('AUTHENTICA_API_KEY','YOUR_API_KEY') body = {'method': method, ('email' if method=='email' else 'phone'): recipient} r = requests.post(f"{BASE_URL}/api/v2/send-otp", headers={'Accept':'application/json','Content-Type':'application/json','X-Authorization':API_KEY}, data=json.dumps(body)) j = r.json() if not r.ok: raise Exception(j) return j
// verifyOtp('+9665XXXXXXXX', '123456') or verifyOtp('user@example.com','123456') async function verifyOtp(recipient, otp) { const BASE_URL = process.env.BASE_URL || 'https://api.authentica.sa'; const API_KEY = process.env.AUTHENTICA_API_KEY || 'YOUR_API_KEY'; const body = recipient.includes('@') ? { email: recipient, otp } : { phone: recipient, otp }; const res = await fetch(`${BASE_URL}/api/v2/verify-otp`, { method: 'POST', headers: { 'Accept':'application/json','Content-Type':'application/json','X-Authorization': API_KEY }, body: JSON.stringify(body) }); const json = await res.json(); if (!res.ok) throw new Error(JSON.stringify(json)); return json; // { verified: true } }
# verify_otp('+9665XXXXXXXX', '123456') or verify_otp('user@example.com','123456') import os, json, requests def verify_otp(recipient: str, otp: str): BASE_URL = os.getenv('BASE_URL','https://api.authentica.sa') API_KEY = os.getenv('AUTHENTICA_API_KEY','YOUR_API_KEY') body = {'otp': otp} body['email' if '@' in recipient else 'phone'] = recipient r = requests.post(f"{BASE_URL}/api/v2/verify-otp", headers={'Accept':'application/json','Content-Type':'application/json','X-Authorization':API_KEY}, data=json.dumps(body)) j = r.json() if not r.ok: raise Exception(j) return j
Tips
- For SMS/WhatsApp use
phone; for Email useemail. - If delivery is inconsistent, try another channel (WhatsApp ↔ SMS).
- Check E.164 formatting.
Use for: Step-up checks (account recovery, high-risk actions).
sequenceDiagram
autonumber
participant App as Your App
participant Auth as Authentica
App->>Auth: POST /api/v2/verify-by-face
Auth-->>App: 200 { match, score }
async function verifyByFace(refBase64, queryBase64) { const BASE_URL = process.env.BASE_URL || 'https://api.authentica.sa'; const API_KEY = process.env.AUTHENTICA_API_KEY || 'YOUR_API_KEY'; const res = await fetch(`${BASE_URL}/api/v2/verify-by-face`, { method: 'POST', headers: { 'Accept':'application/json','Content-Type':'application/json','X-Authorization': API_KEY }, body: JSON.stringify({ user_id: 'demo', registered_face_image: refBase64, query_face_image: queryBase64 }) }); const json = await res.json(); if (!res.ok) throw new Error(JSON.stringify(json)); return json; // expect match info/score }
import os, json, requests def verify_by_face(ref_b64: str, query_b64: str): BASE_URL = os.getenv('BASE_URL','https://api.authentica.sa') API_KEY = os.getenv('AUTHENTICA_API_KEY','YOUR_API_KEY') r = requests.post(f"{BASE_URL}/api/v2/verify-by-face", headers={'Accept':'application/json','Content-Type':'application/json','X-Authorization':API_KEY}, data=json.dumps({'user_id':'demo','registered_face_image':ref_b64,'query_face_image':query_b64})) j = r.json() if not r.ok: raise Exception(j) return j
Tips
- Clear, well-lit images; avoid heavy compression.
- You can store a reference image once, then omit
registered_face_imagelater.
Use for: High-assurance checks, hands-free flows.
sequenceDiagram
autonumber
participant App as Your App
participant Auth as Authentica
App->>Auth: POST /api/v2/verify-by-voice
Auth-->>App: 200 { match, score }
async function verifyByVoice(refBase64, queryBase64) { const BASE_URL = process.env.BASE_URL || 'https://api.authentica.sa'; const API_KEY = process.env.AUTHENTICA_API_KEY || 'YOUR_API_KEY'; const res = await fetch(`${BASE_URL}/api/v2/verify-by-voice`, { method: 'POST', headers: { 'Accept':'application/json','Content-Type':'application/json','X-Authorization': API_KEY }, body: JSON.stringify({ user_id: 'demo', registered_audio: refBase64, query_audio: queryBase64 }) }); const json = await res.json(); if (!res.ok) throw new Error(JSON.stringify(json)); return json; // expect match info/score }
import os, json, requests def verify_by_voice(ref_b64: str, query_b64: str): BASE_URL = os.getenv('BASE_URL','https://api.authentica.sa') API_KEY = os.getenv('AUTHENTICA_API_KEY','YOUR_API_KEY') r = requests.post(f"{BASE_URL}/api/v2/verify-by-voice", headers={'Accept':'application/json','Content-Type':'application/json','X-Authorization':API_KEY}, data=json.dumps({'user_id':'demo','registered_audio':ref_b64,'query_audio':query_b64})) j = r.json() if not r.ok: raise Exception(j) return j
Tips
- Use similar sampling/quality for reference & query audio.
- Do not log base64 audio.
Use for: Non-OTP notifications with a registered sender name.
sequenceDiagram
autonumber
participant App as Your App
participant Auth as Authentica
App->>Auth: POST /api/v2/send-sms
Auth-->>App: 200 (queued)
Note over App,Auth: Carrier delivers SMS to user
async function sendCustomSms(phone, message, senderName) { const BASE_URL = process.env.BASE_URL || 'https://api.authentica.sa'; const API_KEY = process.env.AUTHENTICA_API_KEY || 'YOUR_API_KEY'; const res = await fetch(`${BASE_URL}/api/v2/send-sms`, { method: 'POST', headers: { 'Accept':'application/json','Content-Type':'application/json','X-Authorization': API_KEY }, body: JSON.stringify({ phone, message, sender_name: senderName }) }); const json = await res.json(); if (!res.ok) throw new Error(JSON.stringify(json)); return json; // queued }
import os, json, requests def send_custom_sms(phone: str, message: str, sender_name: str): BASE_URL = os.getenv('BASE_URL','https://api.authentica.sa') API_KEY = os.getenv('AUTHENTICA_API_KEY','YOUR_API_KEY') r = requests.post(f"{BASE_URL}/api/v2/send-sms", headers={'Accept':'application/json','Content-Type':'application/json','X-Authorization':API_KEY}, data=json.dumps({'phone':phone,'message':message,'sender_name':sender_name})) j = r.json() if not r.ok: raise Exception(j) return j
Tip: Ensure your sender name is registered/approved in Authentica.
sequenceDiagram
autonumber
participant App as Your App
participant Auth as Authentica
App->>Auth: GET /api/v2/balance
Auth-->>App: 200 { balance }
async function checkBalance() { const BASE_URL = process.env.BASE_URL || 'https://api.authentica.sa'; const API_KEY = process.env.AUTHENTICA_API_KEY || 'YOUR_API_KEY'; const res = await fetch(`${BASE_URL}/api/v2/balance`, { headers: { 'Accept': 'application/json','X-Authorization': API_KEY } }); const json = await res.json(); if (!res.ok) throw new Error(JSON.stringify(json)); return json; // e.g., { data: { balance: 21934 }} }
import os, requests def check_balance(): BASE_URL = os.getenv('BASE_URL','https://api.authentica.sa') API_KEY = os.getenv('AUTHENTICA_API_KEY','YOUR_API_KEY') r = requests.get(f"{BASE_URL}/api/v2/balance", headers={'Accept':'application/json','X-Authorization':API_KEY}) j = r.json() if not r.ok: raise Exception(j) return j
- Never log PII (full phone/email) or base64 media.
- Store reference face/voice media encrypted at rest; obtain explicit user consent.
- Enforce E.164 phone formatting.
- Map common error codes (400 invalid params, 401 bad key, 429 rate limits) to actionable messages.
- 401 Unauthorized → Check
X-Authorizationand account status in the portal. - OTP not delivered → Confirm E.164, correct channel, registered sender (SMS), and carrier coverage.
- Face/Voice mismatch → Improve sample quality; ensure correct base64 and consistent formats.
- Website: https://authentica.sa
- API Docs: https://authenticasa.docs.apiary.io/#reference
- Portal/Dashboard: https://portal.authentica.sa/
- Support: support@authentica.sa