API keys
Scoped credentials for server-to-server integration. Issuing and revoking them requires the admin role and a signed-in session — a key can never mint another key, which is what keeps revocation meaningful.
| Binary | Path | Revoke identifier |
|---|---|---|
nextneural_converse | /api/api-keys | {uuid} |
nextneural_intel | /api/organization/api-keys | {id} — integer |
nextneural_assist | /api/organization/api-keys | {id} — integer |
nextneural_coach | /api/nn-coach/auth/api-keys | {uuid} |
Paths below use nextneural_converse's.
nextneural_converse and nextneural_coach revoke by the key's UUID; nextneural_intel and
nextneural_assist revoke by its integer id. Sending a UUID to the latter two
returns 400.
Create a key
POST /api/api-keys
{
"name": "crm-integration",
"scopes": ["calls:read", "customers:read", "customers:write"]
}
201 Created. The response has three different shapes. Only the top-level
key is common to all of them — read the plaintext from there and nothing else.
nextneural_converse returns the record flat, keyed by uuid:
{
"uuid": "6f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"name": "crm-integration",
"key": "nn_k_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_prefix": "nn_k_...xxxx",
"detail": "Copy this key now — it will not be shown again."
}
nextneural_intel and nextneural_assist nest the record under api_key and
identify it by an integer id:
{
"api_key": {
"id": 3,
"name": "crm-integration",
"key_prefix": "nn_k_xxxxxxx",
"scopes": ["sales:analyses:read"],
"last_used_at": null,
"created_at": "2026-09-23T10:29:20Z",
"revoked_at": null
},
"key": "nn_k_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
nextneural_coach is flat like nextneural_converse but renames two fields —
prefix rather than key_prefix, and warning rather than detail:
{
"uuid": "6f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"name": "crm-integration",
"prefix": "nn_k_xxxxxxx",
"key": "nn_k_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"warning": "This is the only time the key is shown. Store it now."
}
Only the hash is stored. If the key is lost, issue a new one and revoke the old — there is no way to recover it.
| Field | nextneural_intel | nextneural_converse / nextneural_assist / nextneural_coach |
|---|---|---|
name | required | required |
scopes | required, at least one | optional |
An unrecognised scope is rejected at creation rather than becoming a key that
mysteriously returns 403 in production. The grantable scopes for each binary
are listed in Authentication.
List keys
GET /api/api-keys
{
"api_keys": [
{
"uuid": "6f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"name": "crm-integration",
"key_prefix": "nn_k_...xxxx",
"scopes": ["calls:read", "customers:read", "customers:write"],
"last_used_at": "2026-09-20T09:15:44Z",
"created_at": "2026-09-18T11:02:10Z",
"revoked_at": null
}
],
"available_scopes": ["calls:read", "calls:write", "customers:read", "customers:write",
"flows:read", "flows:write", "webhooks:read", "webhooks:write"]
}
nextneural_coach has no list endpointIt registers POST and DELETE /api/nn-coach/auth/api-keys only. Keys issued
there cannot be listed back over the API — record the uuid from the creation
response, because it is the value the revoke call takes and there is no other
way to recover it.
Each row identifies the key the same way its creation response did — by uuid
in nextneural_converse, by an integer id in nextneural_intel and
nextneural_assist. That is the value the revoke call takes.
available_scopes is what this binary is willing to grant, so a UI can render
the picker without hardcoding the list. nextneural_intel does not return it —
take its scope list from
Authentication instead. In nextneural_assist the
list is available on its own at GET /api/organization/api-key-scopes.
key_prefix is a display hint, and its format differs
Only key_prefix is stored in the clear — enough to recognise a key in a list,
not enough to use it. The two families build it differently, so a UI that
matches on it has to know which binary it is talking to:
| Binary | Example | What it shows |
|---|---|---|
nextneural_converse | nn_k_...wxyz | the last four characters, masked |
nextneural_intel, nextneural_assist, nextneural_coach | nn_k_abcdefg | the first twelve characters of the key |
nextneural_converse is the odd one out: it builds the display value as the
literal nn_k_, an ellipsis, then the key's last four characters. The other
three take the first twelve characters verbatim.
Neither form is a usable credential, and a value from one family cannot be matched against a key the way the other family's can — so a UI that recognises keys by prefix has to know which binary it is talking to.
Revoke a key
DELETE /api/api-keys/{uuid} — nextneural_converse, nextneural_coach
DELETE /api/organization/api-keys/{id} — nextneural_intel, nextneural_assist
Returns 204. The row is kept with revoked_at set rather than deleted: which
key was used and when still matters after it has been revoked. A revoked key
fails authentication immediately.
| Status | Cause |
|---|---|
400 | The identifier is the wrong type — see the note above |
404 | No such key in this organization, or it is already revoked |
Using a key
curl https://your-host/v1/calls \
-H "Authorization: Bearer nn_k_your_key_here"
Every request scopes to the organization the key belongs to. A key cannot be moved between organizations, and it carries no user identity beyond the admin who created it.
What keys deliberately cannot do
No scope in any binary grants:
- Managing organization members or roles
- Creating or revoking API keys
- Connecting or reading telephony provider credentials
- Changing organization identity
Those stay behind a human admin session. A leaked integration credential should not be able to turn itself into permanent tenancy in the organization.
Rotation
There is no rotate endpoint. Rotation is create-then-revoke:
POSTa new key with the same scopes- Deploy it to the consumer
- Confirm the new key's
last_used_atis moving DELETEthe old key
Both keys are valid in between, so there is no window where the integration is without a working credential.