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 afetch
polyfill 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_image
later.
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-Authorization
and 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