Organizations
An organization is the tenant boundary. Every query in every binary is scoped to the caller's organization, whether they authenticated with a session or an API key.
A fresh account has no organization. Until it creates one, every org-scoped route refuses it:
{ "detail": "organization setup is required before using the app", "needs_org_setup": true }
Create an organization
POST /api/organization — session required, no organization yet
{
"name": "Pragati AI",
"website": "https://pragati.example",
"employee_count": "11-50",
"company_type": "generic_sales",
"company_type_other": ""
}
| Field | nextneural_intel / nextneural_assist | nextneural_converse |
|---|---|---|
name | required | required |
employee_count | required | optional |
company_type | required, from a fixed list | optional, free text |
website | optional | optional |
company_type_other | optional | optional |
company_type accepts a different set of values in each binary:
| Binary | Accepted values |
|---|---|
nextneural_intel | real_estate, call_center, generic_sales, freelancer, other |
nextneural_assist | sales_team, customer_support, agency_bpo, saas_company, other |
nextneural_converse | not validated |
An unrecognised value returns 400 naming the accepted list.
nextneural_intel and nextneural_assist both reject a body without employee_count and
company_type; nextneural_converse accepts name alone. A client that posts the
same body to all three will get a 400 from two of them.
Returns the created organization together with a new session token that carries the organization claim — use it in place of the token you signed in with:
{
"organization": {
"id": 1,
"name": "Pragati AI",
"website": "https://pragati.example",
"employee_count": "11-50",
"company_type": "generic_sales",
"company_type_other": null,
"waitlist_status": "allowed"
},
"token": "eyJhbGciOiJIUzI1NiIs..."
}
The account that creates the organization becomes its admin.
| Status | Cause |
|---|---|
400 | A required field is missing |
409 | {"detail": "this account already belongs to an organization"} |
403 | The email domain is not permitted to create an organization on this deployment |
nextneural_coach uses a different route and field
POST /api/nn-coach/auth/organization
{ "organization_name": "Pragati AI" }
One field, named organization_name — not name — and no company metadata at
all.
Read and update
GET /api/organization — any member
{
"id": 1,
"name": "Pragati AI",
"website": "https://pragati.example",
"employee_count": "11-50",
"company_type": "generic_sales",
"waitlist_status": "approved"
}
PATCH /api/organization — admin only. Same fields as creation.
Waitlist approval
New organizations may be gated behind manual approval. At signup the email
domain is checked against ALLOWED_EMAIL_DOMAINS:
| Domain was | waitlist_status | Effect |
|---|---|---|
| On the allowlist, or no allowlist configured | allowed | Never gated |
| Not on the allowlist | pending | Blocked until approved |
| — | approved | Cleared by hand |
| — | rejected | Blocked, permanently |
Both pending and rejected are blocked. The distinction is for whoever is
reviewing the list, not for the check itself.
Signup and organization creation always succeed regardless of status — what the gate blocks is everything afterwards:
{ "detail": "your organization is awaiting approval", "needs_waitlist_approval": true }
Moving an organization from pending to approved is deliberately an
out-of-band database operation, not an endpoint:
UPDATE organizations SET waitlist_status = 'approved' WHERE id = 1;
Approval is a property of the organization, not the individual member — what is being vetted is the company signing up, so one update clears every member at once. The status is re-read from the database on every request, so it takes effect on the caller's next request without them logging in again.
Members
All member routes require the admin role and refuse API keys.
| Binary | Path |
|---|---|
nextneural_converse | /api/organization/members, :uuid identifier |
nextneural_intel, nextneural_assist | /api/organization/members, :id identifier |
nextneural_coach | POST /api/nn-coach/auth/users only — see below |
List members
GET /api/organization/members
Add a member
POST /api/organization/members
{
"email": "[email protected]",
"first_name": "Arjun",
"last_name": "Rao",
"role": "member"
}
email and role are required; role must be one of admin, manager,
member. The new member is issued a temporary password, emailed an invite, and
flagged must_change_password until they set their own.
Change a member's role
PATCH /api/organization/members/{id}
{ "role": "manager" }
Remove a member
DELETE /api/organization/members/{id}
Resend an invite
POST /api/organization/members/{id}/resend-invite
Fails if the deployment has no email provider configured — a failed invite is reported rather than silently dropped.
nextneural_coach: create a user directly
POST /api/nn-coach/auth/users — admin only
{
"email": "[email protected]",
"password": "their-initial-password",
"first_name": "Arjun",
"last_name": "Rao",
"role": "member"
}
nextneural_coach has no invite flow — the password is set here and communicated by
whatever means you choose. There is no list, update, or delete counterpart.
Superadmin
A platform-level capability, not an org role. It is derived from the caller's
own email domain via SUPERADMIN_EMAIL_DOMAINS / SUPERADMIN_EMAIL, so it
cannot be granted through the API.
| Binary | Prefix |
|---|---|
nextneural_converse, nextneural_intel, nextneural_assist | /api/superadmin |
nextneural_coach | /api/nn-coach/auth/superadmin |
List every organization
GET /api/superadmin/organizations
{
"organizations": [
{ "id": 1, "name": "Pragati AI", "member_count": 4, "created_at": "2026-09-18T18:07:18Z" }
]
}
Enter an organization
POST /api/superadmin/organizations/{id}/enter
Issues a new session token scoped to that organization, so an operator can see
the app exactly as its members do. Their own users row still has no
organization — the org travels on the token.
{ "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "...": "..." } }
Grant or revoke app access
PATCH /api/superadmin/organizations/{id}/app-access
{ "is_active": true }
Entitles an organization to use this particular app. An organization with no
row is treated as active; an explicitly inactive one is refused by
requireAppAccess on every org-scoped route.