Nadi API Documentation
Integrate Nadi's WhatsApp AI engine into your product. RESTful JSON API with Bearer token authentication, real-time webhooks, and multi-tenant AI chat processing.
https://getnadi.id
Overview
Nadi is a multi-tenant WhatsApp AI platform. Each tenant (customer) has their own AI agent configuration, contact list, templates, and usage quota. The API supports:
- Authentication — Bearer token, 8-hour TTL
- AI Chat — Enterprise-grade AI responses with template matching and caching
- Contact Management — Whitelist, pipeline, health profiles
- Broadcasting — Anti-ban compliant mass messaging
- Webhooks — WhatsApp Cloud API integration (Meta)
All requests and responses use Content-Type: application/json. Max request body size: 1 MB.
Authentication
Authenticate by including a Bearer token in the Authorization header. Obtain a token via POST /api/auth/login.
Authorization: Bearer {your-token-here}
Tokens expire after 8 hours. There are no refresh tokens — re-authenticate when expired.
Role-Based Access
| Role | Access | Description |
|---|---|---|
| admin | Admin | Full platform management — all customers, billing, analytics |
| customer | Customer | Own tenant only — agent config, contacts, logs, broadcasting |
| — | Public | Login, register, forgot password, webhook |
Error Handling
All errors return a JSON object with error.code and error.message:
{
"error": {
"code": "INVALID_LOGIN",
"message": "Nomor atau password salah."
}
}
HTTP Status Codes
| Status | Meaning | Common Codes |
|---|---|---|
| 200 | Success | — |
| 201 | Created | — |
| 400 | Validation error | VALIDATION_ERROR, MISSING_FIELDS, INVALID_PHONE |
| 401 | Unauthorized | UNAUTHORIZED, INVALID_LOGIN, SESSION_EXPIRED |
| 403 | Forbidden | FORBIDDEN, LOGIN_DISABLED, PLAN_GATE |
| 404 | Not found | NOT_FOUND |
| 409 | Conflict | DUPLICATE, PHONE_TAKEN, NAME_TAKEN |
| 429 | Rate limited | RATE_LIMITED, CHAT_LIMIT_REACHED, TOO_MANY_OTP |
| 500 | Server error | SERVER_ERROR |
Rate Limiting
| Endpoint | Limit | Window | Key |
|---|---|---|---|
| POST /api/auth/login | 10 requests | 15 min | IP address |
| POST /api/register | 10 requests | 1 hour | IP address |
| POST /api/auth/forgot | 3 OTPs | 15 min | Phone number |
| All write ops (/api/*) | 60 requests | 5 min | Auth token |
When rate limited, the API returns 429 with code RATE_LIMITED.
Auth Endpoints
Request Body
| Field | Type | Description |
|---|---|---|
| phone | string | Phone number (e.g., 628123456789) |
| password | string | Account password |
Response (200)
{
"data": {
"token": "a1b2c3d4-uuid",
"role": "customer",
"category": "Gym",
"categoryType": 1,
"profile": {
"id": "cust_abc12345",
"name": "Fitness Pro",
"phone": "628123456789"
}
}
}
Errors
| Status | Code | When |
|---|---|---|
| 401 | INVALID_LOGIN | Wrong phone or password |
| 403 | LOGIN_DISABLED | Account access revoked by admin |
| 429 | RATE_LIMITED | Too many attempts (10/15min) |
cURL Example
curl -X POST https://getnadi.id/api/auth/login \
-H "Content-Type: application/json" \
-d '{"phone":"628123456789","password":"mypassword"}'
MFA (Two-Factor Authentication)
Google Authenticator / Authy compatible TOTP. Mandatory for all users. Secrets encrypted at rest with AES-256-GCM.
Generates TOTP secret, QR code (data URL), and 10 one-time recovery codes. Requires a partial session token (obtained after login for users with mfaStatus: not_setup).
Response (200)
{
"data": {
"qrCode": "data:image/png;base64,iVBOR...",
"secret": "BWHS5EZNN6DEWFAHXIAU7UAV6W",
"recoveryCodes": ["FC2A-25BF", "7873-B7B1", ...],
"uri": "otpauth://totp/Nadi:628xxx?secret=...&issuer=Nadi"
}
}
Request Body
| Field | Type | Description |
|---|---|---|
| code | string | 6-digit TOTP from authenticator app |
Response (200)
{ "data": { "ok": true, "message": "MFA berhasil diaktifkan!", "mfaStatus": "enabled" } }
Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_CODE | Code not 6 digits or wrong |
Verify TOTP code or recovery code during login. On success, upgrades partial token to full session. Rate limited: 5 attempts per 15 minutes.
Request Body (TOTP)
{ "code": "123456" }
Request Body (Recovery)
{ "recoveryCode": "FC2A-25BF" }
Response (200)
{
"data": {
"ok": true,
"token": "uuid",
"role": "customer",
"profile": { "id": "cust_abc", "name": "Budi", "phone": "628xxx" },
"forceResetup": false
}
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | WRONG_CODE | Invalid TOTP or recovery code |
| 401 | MFA_EXPIRED | Partial token expired (5 min) |
| 423 | MFA_LOCKED | Too many attempts (15 min lockout) |
Request Body
{ "code": "123456" }
Response (200)
{ "data": { "ok": true, "message": "MFA berhasil dinonaktifkan." } }
AI / Chat Endpoints
Core AI endpoint. Processes a message through the tenant's AI agent — checks templates, cache, then Nadi AI engine. Enforces plan quota limits.
Request Body
| Field | Type | Description |
|---|---|---|
| customerId | string | Tenant ID (must match authenticated user) |
| message | string | User's message text |
| phone | string | Sender's phone for whitelist lookup |
| conversationHistory | array | Previous messages [{role, content}] |
| isPreview | boolean | Test mode — uses separate quota |
Response (200)
{
"data": {
"reply": "Halo Kak! Selamat datang di Fitness Pro...",
"source": "ai",
"model": "nadi-ai-v4",
"tokens": { "input": 850, "output": 120 },
"cost": "0.004350",
"usage": { "chats": 42, "chatLimit": 500 }
}
}
source values: ai (Nadi AI engine), template (matched template, zero cost), cache (cached response, zero cost)
Errors
| Status | Code | When |
|---|---|---|
| 403 | FORBIDDEN | customerId doesn't match session |
| 429 | CHAT_LIMIT_REACHED | Monthly chat quota exceeded |
| 429 | TOKEN_LIMIT_REACHED | Monthly token quota exceeded |
Send messages from external systems (CRM, website chat widget, etc.) to be processed by the tenant's AI agent. No authentication required — tenant is identified by the URL.
Request Body
| Field | Type | Description |
|---|---|---|
| phone | from | sender | string | Sender's phone number |
| message | text | body | string | Message content |
Response (200)
{
"data": {
"ok": true,
"reply": "Terima kasih sudah menghubungi...",
"source": "ai"
}
}
Integration Example
// Website chat widget integration
const response = await fetch('https://getnadi.id/api/webhook/cust_abc12345', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
phone: '628123456789',
message: 'Halo, saya mau tanya tentang membership'
})
});
const { data } = await response.json();
// data.reply = AI-generated response
Customer Endpoints
Returns all data needed to render the customer dashboard — profile, contacts, limits, templates, policies, activity, and plan gates.
Response (200)
{
"data": {
"customer": { "id", "name", "plan", "status", ... },
"bundle": {
"contacts": [...],
"limitVal": 25,
"activeCount": 12,
"unlimited": false
},
"limits": {
"chatLimit": 500,
"chatsUsed": 42,
"chatLimitReached": false
},
"gates": {
"salesPlaybook": false,
"escalation": false,
"advancedAnalytics": false,
"exportCsv": true
},
"templates": [...],
"policies": [...],
"activity": [...]
}
}
Response (200)
{
"data": {
"contacts": [
{
"id": "wl_a1b2c3",
"phone": "628123456789",
"name": "Budi",
"label": "Member Gold",
"leadStatus": "warm",
"chatCount": 15,
"isActive": true
}
],
"limitVal": 25,
"activeCount": 12,
"unlimited": false
}
}
Request Body
| Field | Type | Description |
|---|---|---|
| phone | string | Valid phone (min 10 digits) |
| name | string | Contact name (min 2 chars) |
| label | string | Custom label (e.g., "Member Gold") |
| notes | string | Notes about the contact |
Errors
| Status | Code | When |
|---|---|---|
| 400 | DUPLICATE | Phone already in contacts |
| 403 | WHITELIST_LIMIT_REACHED | Plan limit (25/50/unlimited) |
Response (200)
{
"data": {
"plan": "growth",
"billingCycle": "monthly",
"subscriptionStart": "2026-04-01T00:00:00Z",
"subscriptionEnd": "2026-05-01T00:00:00Z",
"daysRemaining": 14,
"billingAmount": 449000,
"subscriptionStatus": "active"
}
}
WhatsApp Connection
Initiates a WhatsApp Web session. Returns a QR code to scan from your phone's WhatsApp app.
Response (200)
{
"data": {
"status": "connecting",
"message": "Scan QR code dari WhatsApp di HP Anda."
}
}
Response (200)
{
"data": {
"status": "connecting",
"qr": "data:image/png;base64,iVBOR..."
}
}
status values: connected, connecting, disconnected. QR is only present during connecting state.
WhatsApp Webhook (Meta Cloud API)
Used by Meta to verify your webhook URL during setup. Configure in Meta Developer Console → WhatsApp → Configuration → Callback URL.
Query Parameters
| Param | Type | Description |
|---|---|---|
| hub.mode | string | Must be "subscribe" |
| hub.verify_token | string | Must match WA_VERIFY_TOKEN env |
| hub.challenge | string | Challenge to echo back |
Receives incoming WhatsApp messages from Meta Cloud API. Automatically resolves the tenant, processes through AI, and sends the reply. Returns 200 immediately — processing is async.
Security
Payload signature verified via X-Hub-Signature-256 header using META_APP_SECRET. Message deduplication prevents duplicate processing on Meta retries.
Processing Pipeline
Incoming Message → Deduplication check (skip if already processed) → Tenant resolution (by phone_number_id) → Guard stack: bot enabled? active? whitelisted? quota? → Template matching (zero cost) → Cache check → Nadi AI engine → Send reply via WhatsApp → Log usage
Health Check
{
"status": "ok",
"version": "3.0.0",
"uptime": 86400
}
© 2026 Nadi Platform — API v3.0 — getnadi.id