Skip to content

HTTP API reference

Reference · HTTP API

@askdb/http-api is a thin HTTP wrapper around the same ask() pipeline as the library. Two endpoints, JSON over HTTP, designed to sit behind your own gateway.

import { createAskDbHttpServer } from "@askdb/http-api";
const server = createAskDbHttpServer({
port: 3000,
host: "127.0.0.1",
maxBodyBytes: 1024 * 1024,
});
await server.listen();

createAskDbHttpServer() takes an AskDbHttpServerOptions object. Every option is optional.

OptionDefaultDescription
porthttpApi.listen.port, else 3000Port to listen on.
hosthttpApi.listen.host, else 127.0.0.1Bind address. Bind to 0.0.0.0 only behind a gateway.
schemaPathhost.schemaJson, then host.schemaPathServer-default schema artifact path.
maxBodyBytes1048576 (1 MiB)Request body size limit. Larger requests return payload_too_large.

createAskDbHttpServer() returns an object with these members:

MemberDescription
host, portThe bind address and port the server uses, after the defaults above.
serverThe underlying Node http.Server, for hosts that manage its lifecycle themselves.
listen()Starts listening. Returns a Promise<void> that resolves once the server listens, and rejects on a listen error such as EADDRINUSE.
close()Stops the server. Returns a Promise<void> that resolves once it has closed.

The server reads its schema and AI provider settings from askdb.config.ts via @askdb/config — see Configuration for the resolution rules. It gets them when you call createAskDbHttpServer():

  • If your process already called bootstrapAskDbEnv(), the server uses that config and loads nothing.
  • Otherwise the server calls bootstrapAskDbEnv() itself, which loads .env and askdb.config.* from the current working directory. If it finds no config, or an existing .env can’t be read, createAskDbHttpServer() throws.

To load the config from another directory, call bootstrapAskDbEnv({ cwd: "/path/to/project" }) before you create the server.

These keys in askdb.config.ts control request handling. They apply to both createAskDbHttpServer() and the askdb-http binary.

KeyDefaultDescription
httpApi.listen.port / httpApi.listen.host3000 / 127.0.0.1Listen address when no port / host option or CLI flag is given.
httpApi.allowSchemaOverridefalseAccept a per-request schemaJson override. Must be a boolean; any other value (such as "false") fails config loading. When false, a request that sends schemaJson gets 403 schema_override_disabled. An override is a schema artifact the server parses and puts into the prompt through your model key, so enable it only for tests or trusted callers, on @askdb/http-api 1.0.0-beta.48 or later (GHSA-qqx9-qpw2-wcqg).
httpApi.requestTimeoutMs60000Abort the model call after this many milliseconds. A timed-out request returns 502 sql_generation_error. Must be a positive integer no larger than 2147483647 (the Node timer maximum, about 24.8 days); any other value is a config error when the server loads its config.
modes.omitSensitiveFromPromptfalseWhen true, sensitive identifiers are left out of every prompt. A request’s omitSensitiveFromPrompt: false can’t turn this off.
modes.askdbModeschema_onlyThe loosest mode a request gets. A request that sends neither mode nor x-askdb-mode runs this mode. A request can tighten it to schema_only; one that asks for bounded_results while this is schema_only runs schema_only, with no error.
askdb.config.ts
export default defineConfig({
// ...
httpApi: {
listen: { port: 3000, host: "127.0.0.1" },
allowSchemaOverride: false,
requestTimeoutMs: 30_000,
},
});

The package also ships an askdb-http binary, so you can run the service without writing any server code:

Terminal window
npx askdb-http --schema-path ./my-app.schema --port 3000 --host 127.0.0.1
FlagDescription
--schema-path <path>Server-default schema artifact path.
--port <n>Port to listen on.
--host <host>Bind address.
-h, --helpPrint the usage and these flags to stdout, then exit with code 0. It doesn’t load the config or start the server.

Every flag is optional. Each setting comes from its flag, then askdb.config.ts, then the default:

  • Schema: --schema-path, then host.schemaJson, then host.schemaPath. There’s no default: with none, POST /ask answers 500 generation_not_configured, unless the request sends its own schemaJson (accepted when httpApi.allowSchemaOverride is true).
  • Port and host: --port and --host, then httpApi.listen.port and httpApi.listen.host, then 3000 and 127.0.0.1.

askdb-http loads one .env file before the config: the first of these that exists.

  1. The .env in the working directory.
  2. Only when you run it from AskDB’s own repository: a .env in the apps/http-api folder.
  3. Only there too: the .env at the repository root.

An installed askdb-http reads only the working directory’s .env, and it never reads a .env inside node_modules. createAskDbHttpServer() reads only the working directory’s .env too (see Config loading).

askdb-http prints an error to stderr and exits with code 1 when:

  • A flag is wrong: an unknown flag, a value flag (--schema-path, --port, --host) with no value or with a value that starts with --, or a --port that isn’t a whole number from 1 to 65535. askdb-http prints the error, such as askdb-http: Unknown option: --verbose or askdb-http: Invalid --port: 70000, then the help text. It checks every flag before it reads .env or the config. It also checks them before it acts on --help, so a wrong flag beside --help exits with code 1 too.
  • It can’t load its config: no askdb.config.* found, or an invalid value such as an out-of-range httpApi.requestTimeoutMs. The error is one line.
  • It can’t bind: for example, the port is already in use. The error is one line.

Liveness probe.

Terminal window
curl http://localhost:3000/health
{ "ok": true }

Generate SQL from a question.

Request (AskHttpRequest)

{
"question": "How many customers signed up last week?",
"mode": "schema_only",
"explain": false,
"omitSensitiveFromPrompt": false,
"schemaJson": "{\"version\":2, \"schemaId\":\"...\", \"tables\":[...]}"
}
FieldRequiredDefaultDescription
questionyes—The natural-language question.
modenomodes.askdbMode, else schema_onlyOne of schema_only, bounded_results. Also accepted as the x-askdb-mode header (the body wins). It can only tighten the server’s modes.askdbMode: bounded_results runs schema_only unless the server sets modes.askdbMode: "bounded_results". An unrecognized value returns 400 bad_request.
explainnofalseReturn metadata on the read-only checks. Must be a JSON boolean: any other value, such as "false", returns 400 bad_request.
omitSensitiveFromPromptnomodes.omitSensitiveFromPrompttrue omits sensitive columns from the prompt for this request. It can only tighten the server setting. When the server sets modes.omitSensitiveFromPrompt: true, sending false has no effect. Must be a JSON boolean: any other value, such as "true" or 1, returns 400 bad_request.
schemaJsonno—Inline schema artifact that replaces the server schema for this request. Rejected with 403 schema_override_disabled unless the server sets httpApi.allowSchemaOverride: true. The server generates and checks SQL for the artifact’s own provider, which must be a built-in dialect id; the config’s dialect applies to the server’s own schema only. Preferred: set host.schemaPath in askdb.config.ts server-side.

An optional field sent as JSON null counts as absent, as does an empty or whitespace-only schemaJson. A null body mode falls back to the x-askdb-mode header, then to config.

Success response (200, AskHttpSuccessResponse)

{
"ok": true,
"correlationId": "8f14e45f-ceea-4e7a-9f2b-1c3d5e7a9b0c",
"sql": "SELECT count(*) FROM customers WHERE created_at >= …",
"usage": {
"promptTokens": 1250,
"completionTokens": 45,
"totalTokens": 1295
},
"sensitiveGuardrail": {
"passed": true,
"references": []
}
}
FieldTypeNotes
sqlstringThe generated, checked SQL.
explainobject, only when requestedMetadata on the read-only checks: which ran. Present only when the request sent explain: true.
usageobject | nullToken counts for the generation call: promptTokens, completionTokens, totalTokens (each number | null). null when the provider does not report usage.
sensitiveGuardrailobject, only on some responsesPresent only when the schema marks at least one table or column sensitive. Shows which sensitive identifiers the returned sql references: passed (boolean), references ({ table, schema?, column, matchKind }[]), and unresolvedScope when the table scope couldn’t be resolved. passed: false is a warning. The SQL is still returned, so check it before you execute. See validateSensitiveReferences.

Error response (4xx / 5xx, AskHttpErrorResponse)

{
"ok": false,
"correlationId": "8f14e45f-ceea-4e7a-9f2b-1c3d5e7a9b0c",
"error": {
"code": "sql_validation_error",
"message": "SQL must start with SELECT or WITH (got 'delete').",
"rule": "SQL_NOT_SELECT_OR_WITH"
}
}
CodeHTTPWhen
not_found404Any route other than GET /health and POST /ask, including GET /ask.
bad_request400Malformed JSON, missing question, invalid mode, non-boolean omitSensitiveFromPrompt or explain, non-string schemaJson, a retired execution field, or a request schemaJson that records no provider or one with no AskDB dialect.
payload_too_large413Body exceeds maxBodyBytes.
schema_override_disabled403The request sent schemaJson but httpApi.allowSchemaOverride is not true.
schema_parse_error400A request schemaJson couldn’t be loaded or parsed. The server’s own schema gets 500 generation_not_configured instead.
sql_validation_error400Model output failed the SQL checks (must start with SELECT/WITH, a single statement, no comments, no unterminated strings or quoted identifiers, no write or DDL keywords, no locking reads, no blocked side-effecting functions). Includes rule, the check’s SqlValidationRuleCode, such as SQL_NOT_SELECT_OR_WITH.
guardrail_violation422Reserved for the tenant or sensitive check rejecting the generated SQL, with rule. The server doesn’t return it today: it runs the sensitive-identifier check in warn mode (see sensitiveGuardrail), and it can’t pass a tenant scope, so a schema with a tenant policy fails earlier with 500 tenant_scope_not_configured.
generation_not_configured500The server can’t generate SQL for its configured schema, for one of four reasons: no model provider is configured; no schema is configured; the schema can’t be loaded, such as a host.schemaPath that doesn’t exist; or the schema’s provider has no AskDB dialect and dialect isn’t set in askdb.config.*. The server never falls back to PostgreSQL. The message is generic and names no path. The log line’s errName names the cause: ModelNotConfiguredError, SchemaNotConfiguredError, SchemaLoadError or DialectNotSupportedError, with the full error.
tenant_scope_not_configured500The schema has a tenant policy (tenant-policy.md), and the server can’t set a tenantScope, so ask() refuses it before any model call. No SQL is returned. To serve a tenant-scoped schema, call ask() with a tenantScope in your own server; server-side scoping for this server is planned in #343.
sql_generation_error502The model call failed (provider error, or it hit httpApi.requestTimeoutMs). The message is generic. The provider’s own error text is logged server-side and never returned.
internal_error500Anything else. The message is generic. The full error is logged server-side under the response’s correlationId.

The server picks the status code from the error’s type, never from its message text. Every non-2xx response from POST /ask is logged as an askdb.run.error event with its status and code, under the same correlationId. A request rejected before ask() runs (malformed or oversized body, invalid field, schema_override_disabled) is logged with the client-facing message, or the body parse error. A failure inside ask() is logged with the error’s name, message, and cause, plus the stack for 5xx. A 404 not_found for an unknown route isn’t logged. A last-resort 500 (an error that escapes the request handler) is written to stderr with the correlation ID, not to the structured log.

Every response except GET /health includes a correlationId. The same ID is emitted in the server’s structured log events, so you can join HTTP requests to generated SQL and any errors.

To carry a correlation ID supplied by your gateway, send the x-correlation-id header on the request. The server trims the value and uses it; when the header is missing or blank, it generates a UUID.

Terminal window
curl -X POST http://localhost:3000/ask \
-H "content-type: application/json" \
-H "x-correlation-id: req_abc123" \
-d '{"question":"How many users?"}'

@askdb/http-api exports four types, for a host or a client written in TypeScript:

import type {
AskDbHttpServerOptions,
AskHttpErrorResponse,
AskHttpRequest,
AskHttpSuccessResponse,
} from "@askdb/http-api";
TypeDescribes
AskDbHttpServerOptionsThe options of createAskDbHttpServer(): see Server.
AskHttpRequestThe POST /ask body: see POST /ask. Its fields have no null member. The server still reads a JSON null as absent.
AskHttpSuccessResponseThe 200 body, with ok: true. explain is typed unknown.
AskHttpErrorResponseThe error body, with ok: false. error.code is a union of the codes in Error codes. error.rule is an optional string.
  • No auth. Put your gateway in front.
  • No TLS termination. Use your ingress.
  • No rate limiting. Same.
  • No database connection. It returns SQL; execution is your caller’s responsibility.

See Deploy as HTTP service for the production deployment pattern.