Skip to content

HTTP API

The auto-generated HTTP API reference (SK-DOCS-003 slice d) is blocked on apps/api emitting an OpenAPI schema. The contract this page will eventually render is canonical in packages/sdk/src/index.ts — every HTTP endpoint has a matching SDK method, by GLOBAL-001.

While slice d is blocked, use the SDK reference: each method (ask, runSql, createDatabase, listKeys, …) documents the exact wire shape that flows over the corresponding POST /v1/<resource> endpoint.

  • POST /v1/ask — the canonical NL→SQL endpoint. See NlqClient.ask.
  • POST /v1/run — raw-SQL escape hatch (GLOBAL-015). See NlqClient.runSql.
  • POST /v1/databases — create a database from a plain-English brief. See NlqClient.createDatabase.
  • GET /v1/databases — list databases. See NlqClient.listDatabases.
  • POST /v1/keys, GET /v1/keys, DELETE /v1/keys/:id — API-key lifecycle.
  • POST /v1/chat/messages — chat-style transcript endpoint.

Curl-shaped examples for /v1/ask (read + write + anonymous mode) live in the curl tutorial.

Every non-2xx response carries a JSON envelope { "error": { "code": <code>, "message", "action", "retryable", "params"? } } (GLOBAL-012). An agent can read this table before calling to decide which codes are retryable and which need a human. code is the discriminant the SDK exposes as err.code; the same set is the ApiErrorCode union in packages/sdk/src/index.ts, kept in lockstep by a build-time guard (apps/docs/scripts/check-error-codes.ts).

Code HTTP Meaning Retryable? Recovery action
db_not_found 404 Pinned dbId doesn’t exist. No Drop dbId to create from the goal, or fix the id.
schema_unavailable 422 The DB’s schema fingerprint isn’t ready yet. Yes Retry shortly.
db_misconfigured 502 The DB’s stored config is invalid. No Recreate the database; if it persists, contact support.
db_unreachable 502 The backing engine is unreachable. Yes Retry with backoff.
sql_rejected 400 The SQL failed the allow-list or the engine (body.reason). No Rephrase the goal, or hand-write valid SQL via /v1/run.
llm_failed 502 The model chain couldn’t produce SQL. Yes Retry; if persistent, simplify the goal.
rate_limited 429 Too many requests (body.limit, body.count). Yes Wait, honor Retry-After, then retry.
unauthorized 401 Missing, invalid, or expired credential. No Sign in again, or mint a new key.
auth_unavailable 503 The session store hit a transient blip while verifying the caller — not a signed-out session. Yes Retry shortly; you stay signed in.
forbidden 403 A read-only key tried to write via /v1/run (SK-APIKEYS-003). No Use a write-capable key.
ambiguous_db 409 2+ databases match; confidence below the floor. No Pick from body.candidate_dbs, resend with dbId.
clarify_required 409 Pinned dbId but the goal looks like a create (body.pinned_db). No Resend without dbId to create, or cancel.
invalid_engine 400 engine isn’t in the allowed set. No Use postgres or clickhouse.
invalid_model 400 model isn’t in the preset set (body.allowed). No Use auto, fast, or best.
model_unavailable 409 model: "best" but the account has no frontier lane (no BYOLLM key, no paid plan). No Add a provider key at body.link, or drop model.
goal_required 400 goal missing or empty. No Supply a non-empty goal.
dbId_required 400 dbId required for this call. No Supply dbId.
sql_required 400 /v1/run called without sql. No Supply sql.
sql_too_long 400 sql exceeds the size cap. No Shorten the query.
db_required 400 /v1/run called without db. No Supply db.
invalid_json 400 Request body isn’t valid JSON. No Fix the JSON.
invalid_body 400 Body failed validation (body.message). No Fix the offending field.
invalid_email 400 Email is malformed. No Fix the email.
invalid_byollm_key 400 The BYOLLM credential is mis-shaped. No Fix provider / model / key.
byollm_unavailable 503 The server can’t seal keys (KEK unset). Yes Retry; if persistent it’s an operator action.
secret_unconfigured 503 The server is missing a required secret. No Operator action — not caller-fixable.
wrong_preset 409 remember() target DB isn’t an agent_memory_v1 preset. No Point at an agent-memory database, or create one.
connect_requires_account 403 /v1/db/connect called without a signed-in session. No Sign in, then reconnect.
invalid_request 400 Malformed body, or a bad / non-HTTPS / egress-blocked connection URL (body.message). No Fix the field named in body.message.
introspection_failed 502 The connected database couldn’t be reached or read. Yes Check the host and credential, then retry.
sealing_unconfigured 503 The server can’t seal secrets (KEK unset). No Operator action — not caller-fixable.
schema_mismatch 409 The plan named a table this database doesn’t have (body.params). No Ask about a table it has, or create a new database.
write_constraint 409 An approved write hit a constraint (FK / unique / not-null / check); nothing was written (body.params). No Fix the named field, then ask again.
write_no_rows 409 An approved write matched no rows — previewed or committed (body.params.phase). No Name the row or widen the filter, then ask again.
invalid_value 409 A value didn’t fit its column (Postgres SQLSTATE class 22). No Restate with values that match the column types.
goal_too_long 400 goal exceeds the length limit (body.params.maxLength). No Shorten it to the essential question.
invalid_scope 400 agentId / endUserId / threadId weren’t strings. No Send strings, or omit the fields.
byollm_requires_session 400 A per-request provider key needs a signed-in session. No Sign in, or save the key to your account.
auth_required 401 The anonymous budget is exhausted (body.params.cap). No Sign in — your prompt is saved.
account_required 403 The call needs an account-scoped key, not a public embed key. No Mint an account key and retry.
create_requires_session 403 Creating a database needs a signed-in session. No Sign in, then create it.
challenge_required 428 A human check is required before running that. No Complete the check, then retry.
key_not_found 404 No key of yours matched that id. No Check the key list, then retry.
unconfigured 503 That feature isn’t configured on this deployment. No Operator action — retry later.
internal_error 500 An unexpected server-side failure. No Retry once; if it persists, report it.
invalid_bearer 400 The Authorization header wasn’t a usable bearer token. No Send Authorization: Bearer <key>.
invalid_token 400 The anonymous token isn’t valid. No Start a new session.
token_taken 409 The anonymous session was already claimed by another account. No Carry on in this account.
adopt_failed 500 nlqdb couldn’t move anonymous work into this account. No Retry once; if it persists, contact support.
import_not_found 404 The import draft no longer exists. No Start the import again.
import_busy 409 That import is already running. Yes Wait for it to finish, then check the result.
unknown_pack 400 The import pack doesn’t exist (body.params.allowed). No Use a supported pack.
source_required 400 No source was given for the import. No Name the source, then retry.
invalid_preset 400 The schema preset doesn’t exist (body.params.allowed). No Use a supported preset.
preset_disabled 400 That preset isn’t enabled on this deployment. No Create without a preset, or ask the operator to enable it.
preset_engine_conflict 400 The preset doesn’t run on the requested engine. No Drop the engine override, or pick a compatible preset.
provision_failed 502 nlqdb couldn’t finish creating the database. Yes Retry; nothing was left half-created.
oauth_not_configured 503 This deployment can’t connect that provider by OAuth. No Connect with a connection URL instead.
pick_expired 410 The connection choice expired before it was confirmed. No Start the connection again.
unknown_error 5xx A 5xx with no parseable envelope. Yes Retry; if persistent, report it.
non_json_response A proxy/CDN returned non-JSON (SDK sentinel). Maybe Retry; check the network path.
network_error Transport failure, no response (SDK sentinel, httpStatus === 0). Yes Retry with backoff.
aborted The caller’s AbortSignal fired (SDK sentinel, httpStatus === 0). No Re-issue the request if still wanted.

The last four are SDK-only sentinels — the API never sends them; the SDK raises them for transport states. Recoverable failures (transport errors and transient 5xx) are retried automatically by @nlqdb/sdk before they ever surface (SK-SDK-008).

Some codes carry extra fields (under error.params, and mirrored top-level on err.body by the SDK) an agent should branch on:

  • candidate_dbs — on ambiguous_db: [{ id, slug }] to render a picker.
  • pinned_db — on clarify_required: the DB that was pinned when the goal looked like a create.
  • requires_confirm + diff — on a write preview (dryRun / destructive op): apply only after the human approves the diff (GLOBAL-023).
  • limit + count — on rate_limited: the window cap and how many you’ve used.
  • link — on model_unavailable: deep-link to add a BYOLLM key or plan.