Skip to main content

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.

BinaryPathRevoke 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.

The revoke identifier is not the same everywhere

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."
}
The plaintext key is returned once

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.

Fieldnextneural_intelnextneural_converse / nextneural_assist / nextneural_coach
namerequiredrequired
scopesrequired, at least oneoptional

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 endpoint

It 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:

BinaryExampleWhat it shows
nextneural_conversenn_k_...wxyzthe last four characters, masked
nextneural_intel, nextneural_assist, nextneural_coachnn_k_abcdefgthe 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.

StatusCause
400The identifier is the wrong type — see the note above
404No 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:

  1. POST a new key with the same scopes
  2. Deploy it to the consumer
  3. Confirm the new key's last_used_at is moving
  4. DELETE the old key

Both keys are valid in between, so there is no window where the integration is without a working credential.