Skip to main content

Errors and conventions

Error shape​

Every error carries a human-readable detail:

{ "detail": "not found" }

Some carry an additional machine-readable flag, so a client can act on the condition rather than matching on prose:

{ "detail": "organization setup is required before using the app", "needs_org_setup": true }
FlagMeaning
needs_org_setupThe account has no organization yet
needs_waitlist_approvalThe organization is pending or rejected
must_change_passwordAn invited user has not set their own password

Status codes​

StatusMeaning
400 Bad RequestMalformed body, or a field that failed validation
401 UnauthorizedMissing, invalid, expired, or revoked credential
403 ForbiddenValid credential, insufficient role / scope / org state
404 Not FoundNo such resource in the caller's organization
409 ConflictState conflict — duplicate email, campaign already running
429 Too Many RequestsToo many failed OTP attempts
500 Internal Server ErrorUnhandled failure; details are logged, not returned

Cross-tenant requests return 404​

Asking for a resource that belongs to another organization returns 404, the same as one that does not exist. The two are deliberately indistinguishable — returning 403 would let a caller probe for another tenant's UUIDs by watching which ones answered differently.

Internal errors reveal nothing​

A 500 always returns the same body:

{ "detail": "internal error" }

The underlying error is logged server-side. Database messages carry column names and query fragments, so they are never sent to a client.

Identifiers​

Resources are addressed by UUID in public-facing routes, never by the integer primary key. UUIDs are unguessable, which is what lets a recording URL or a widget key be handed out without becoming an enumeration vector.

Two binaries differ in their console routes:

BinaryIdentifier in paths
nextneural_converse:uuid throughout
nextneural_coach:uuid throughout
nextneural_intel:id — integer primary key
nextneural_assist:id for customers and agents, :uuid for calls

Pagination​

List endpoints that paginate take limit and offset:

ParamDefault
limit50
offset0

They return the page alongside the unpaginated total, so a client can render "page 2 of 18" without a second request:

{ "calls": [ "..." ], "total": 891 }

Dates and times​

Timestamps are RFC 3339. Values read back from the database carry the server's UTC offset and sub-second precision rather than a Z suffix:

2026-09-22T09:36:21.936466+05:30

Parse with a full RFC 3339 parser rather than matching on Z. Values the application generates itself — webhook timestamp, transcript ts — are emitted as UTC:

2026-09-22T09:00:04Z

Date filters (date_from, date_to) accept either a full RFC 3339 timestamp or a bare YYYY-MM-DD. A bare date_to covers the whole of that day.

Campaign scheduling uses the server's clock

nextneural_converse campaign calling windows are evaluated against the server's local time, not a per-organization timezone. On a UTC server, a window of 09:00–18:00 means 14:30–23:30 IST. Set TZ on the service accordingly.

Rate limits​

These binaries do not apply per-key HTTP rate limiting. The pacing that exists is domain-specific rather than a middleware:

  • nextneural_converse campaigns dial at calls_per_minute (default 10)
  • Signup OTP verification locks out after repeated wrong codes (429)
  • Voice sample generation refuses a second concurrent run with 409

If you need request-rate limiting, terminate it at your reverse proxy.

Content types​

Request bodies are JSON unless the endpoint is documented as multipart. File uploads — avatars, call recordings, knowledge base documents — are multipart/form-data.