Quickstart
From an empty database to an authenticated API call. The examples use
nextneural_converse; the same five steps work on any of the four binaries — only the
last one differs.
A running binary and a reachable Postgres. See Running a binary if nothing is up yet.
1. Create an account
Signup is two steps: submit your details, then confirm the six-digit code that lands in your inbox. The account does not exist until the code is confirmed.
curl -X POST https://your-host/api/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "a-strong-password",
"first_name": "Priya",
"last_name": "Kumar"
}'
{ "detail": "We've emailed you a code — enter it to finish creating your account." }
Then confirm it:
curl -X POST https://your-host/api/auth/verify-signup-otp \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "code": "428913" }'
This returns 201 with your session token and user record:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"uuid": "dac0309e-d46d-4b48-a420-05d976e64c5c",
"email": "[email protected]",
"role": "admin",
"mobile_number": null,
"phone_number": null,
"profile_picture": null,
"organization": null,
"must_change_password": false,
"has_password": true,
"superadmin": false
}
}
"organization": null means setup is not finished. That is the next step.
2. Create your organization
curl -X POST https://your-host/api/organization \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Pragati AI",
"website": "https://pragati.example",
"employee_count": "11-50",
"company_type": "generic_sales"
}'
The account that creates an organization becomes its admin.
DEPLOYMENT_MODE defaults to sovereign, which permits exactly one
organization. Once it exists, this call fails for everyone else:
{ "detail": "this deployment is licensed for a single organization" } // 403
Set DEPLOYMENT_MODE=cloud to lift the cap — that exact value, nothing else.
A client can read the current setting without authenticating:
curl https://your-host/api/auth/config
{ "deployment_mode": "sovereign", "firebase_enabled": false }
An account that already belongs to an organization gets 409
{"detail": "this account already belongs to an organization"} instead, since
that check runs first.
3. Get past the waitlist gate
If your email domain was not on ALLOWED_EMAIL_DOMAINS when you signed up, your
organization is created with waitlist_status = "pending" and every org-scoped
route returns 403:
{ "detail": "your organization is awaiting approval", "needs_waitlist_approval": true }
There is no API for approving it — this is a deliberate out-of-band admin action:
UPDATE organizations SET waitlist_status = 'approved' WHERE id = 1;
Approval is a property of the organization, not the user, so one update clears every member. Details in Organizations.
4. Log in
Once approved, exchange your credentials for a session token whenever you need a fresh one:
curl -X POST https://your-host/api/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "password": "a-strong-password" }'
{ "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "..." : "..." } }
Use it as Authorization: Bearer <token> on every console endpoint.
5. Create an API key
Session tokens expire. For server-to-server integration, issue a scoped API key
instead — this requires the admin role:
curl -X POST https://your-host/api/api-keys \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "crm-integration",
"scopes": ["calls:read", "customers:read", "customers:write"]
}'
{
"uuid": "6f1a...",
"name": "crm-integration",
"key": "nn_k_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_prefix": "nn_k_xxxx",
"detail": "Copy this key now — it will not be shown again."
}
The plaintext key is returned once and never stored. Lose it and you issue a new one.
6. Call the developer API
curl https://your-host/v1/calls?limit=10 \
-H "Authorization: Bearer nn_k_your_key_here"
{
"data": [
{
"uuid": "c8f2a1b3-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"call_type": "outbound",
"from_number": "+915550010000",
"to_number": "+918888888888",
"duration": 113,
"outcome": "completed",
"started_at": "2026-09-18T14:32:01Z"
}
],
"total": 1
}
The developer API wraps list results in data. The console routes use a named
key instead — GET /api/voice/calls returns calls. See
the developer API reference.
Where each binary's developer API lives
| Binary | Developer API base | Notes |
|---|---|---|
nextneural_converse | /v1 | Calls, contacts, campaigns → |
nextneural_intel | /api/v1/sales | Analyses and field labels → |
nextneural_assist | — | Scoped keys work on the console routes themselves |
nextneural_coach | /api/nn-coach | Same routes as the console; role-gated ones refuse keys |
Next: Authentication in full →