Skip to main content

Accounts

Signup, login, password reset, and profile. These routes are identical across all four binaries; only the path prefix differs.

BinaryPrefix
nextneural_converse, nextneural_intel, nextneural_assist/api/auth
nextneural_coach/api/nn-coach/auth

Paths below use /api/auth.

Auth configuration​

GET /api/auth/config — no credential required.

Tells a client what sign-in methods this deployment offers, so the login page can render the right buttons.

{
"firebase_enabled": true,
"deployment_mode": "cloud"
}

Signup​

Signup is two calls. The account does not exist after the first one — the submitted details are held until the emailed code is confirmed.

Start signup​

POST /api/auth/signup

{
"email": "[email protected]",
"password": "a-strong-password",
"first_name": "Priya",
"last_name": "Kumar"
}

202 Accepted:

{ "detail": "We've emailed you a code — enter it to finish creating your account." }

A second attempt with the same address replaces the pending signup rather than creating a duplicate, which is what makes "resend" safe.

StatusCause
400A required field is missing or malformed (nextneural_intel only — see below)
409An account with this email already exists
Validation differs by binary

nextneural_intel validates at the binding layer: email must be a well-formed address and password must be at least 8 characters, both rejected with 400 before any work happens. nextneural_converse and nextneural_coach declare no binding constraints on this route.

Verify the code​

POST /api/auth/verify-signup-otp

{ "email": "[email protected]", "code": "428913" }

201 Created — this is the call that creates the account, and it returns a session immediately:

{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 4,
"uuid": "dac0309e-d46d-4b48-a420-05d976e64c5c",
"email": "[email protected]",
"first_name": "Priya",
"last_name": "Kumar",
"role": "admin",
"mobile_number": null,
"phone_number": null,
"profile_picture": null,
"organization": null,
"must_change_password": false,
"has_password": true,
"superadmin": false
}
}
StatusCause
400{"detail": "that code is incorrect or has expired — request a new one"}
429{"detail": "too many incorrect attempts — request a new code"}
409An account with this email already exists

Wrong-code attempts are counted per pending signup and locked out well before a six-digit space could be exhausted.

Resend the code​

POST /api/auth/resend-signup-otp

{ "email": "[email protected]" }

Always returns 200 with the same body whether or not a signup is pending — this does not reveal whether an address is registered:

{ "detail": "If a signup is pending for that address, a new code is on its way." }

Login​

POST /api/auth/login

{ "email": "[email protected]", "password": "a-strong-password" }

200 OK — same {token, user} shape as verification above.

StatusCause
401{"detail": "incorrect email or password"} — returned for both a wrong password and an unknown address

Google sign-in​

POST /api/auth/firebase

Registered only when the deployment has Firebase configured. Where it is not, the route does not exist and returns 404 — which is what GET /api/auth/config reports via firebase_enabled.

{ "id_token": "<Google ID token>", "intent": "signin" }
FieldRequiredNotes
id_token✓The Google ID token
intent—signin or signup. Omitted means either is acceptable

Exchanges a Google ID token for a session in the same {token, user} shape. An account created this way has no password: its has_password is false, which is what lets the UI offer to create one rather than confirm an existing one.


Password reset​

Request a reset​

POST /api/auth/forgot-password

{ "email": "[email protected]" }

Always 200, regardless of whether the address exists. The emailed link carries a token; only its hash is stored, so a leaked database does not let anyone complete someone else's reset.

Complete the reset​

POST /api/auth/reset-password

{ "token": "<token from the email>", "new_password": "a-new-password" }

The signed-in account​

All of these require Authorization: Bearer <session-token>. They work before organization setup is complete — that is the point, since a fresh account needs /auth/me to discover that setup is still pending.

Current user​

GET /api/auth/me

Returns the same user object shown above. The organization field is an object or null, never omitted: a client gates the whole app on it being null.

Log out​

POST /api/auth/logout

Change password​

POST /api/auth/change-password

{ "current_password": "...", "new_password": "..." }

new_password must be at least 8 characters.

An account that has no password at all — one created by Google sign-in — sets its first password here without supplying current_password. Reaching this endpoint already required a valid session, which is the same bar as knowing the old password.

An invited user does have a password (the generated one that was emailed to them) and must supply it. Skipping that check would let whoever holds the session take the account over silently.

StatusCause
400{"detail": "the new password must be at least 8 characters"}
401{"detail": "current password is incorrect"}
403Sent with an API key — this route requires a signed-in user

Update profile​

PATCH /api/auth/profile

{ "first_name": "Priya", "last_name": "Kumar", "mobile_number": "+919876543210" }

Avatar​

POST /api/auth/profile/picture — multipart upload DELETE /api/auth/profile/picture — remove

In nextneural_intel, the image itself is served from GET /api/auth/profile/picture, which accepts the token in the header or as a ?token= query parameter, because an <img src> cannot set a header.


Invited users​

A member created by an admin (see Organizations) is issued a temporary password and flagged must_change_password: true. Until they set their own, every org-scoped route refuses them:

{ "detail": "set your own password before using the app", "must_change_password": true }

The only routes that stay open are the account routes above — enough to log in and call POST /api/auth/change-password.