Skip to content

How a request flows

Every POST /v1/query goes through the same stages, in order. A failure at any stage stops the request there, and every outcome is written to the audit log.

client ──HTTP──▶ curral
├─ authenticate Basic (bcrypt) · API keys · OIDC/JWT
├─ queue global and per-user concurrency limits
├─ inspect DuckDB's own planner: statement type, tables read and written
├─ authorize embedded OPA policy (Rego) → allow / deny
├─ protect rewrite the query with row filters and column masks
└─ execute & stream JSON · CSV · NDJSON · Arrow IPC

The Authorization header picks the method: a local user (Basic), a service API key or an OIDC token. The result is a user name and a list of roles. Repeated failures lock the IP or user name out. See Users and API keys and OIDC.

Failure: 401, or 429 during a lockout.

At most --max-concurrency queries execute at once. A request waits up to --queue-timeout for a slot. Per-user quotas keep one user from taking every slot. See Limits and fairness.

Failure: 503 (queue full) or 429 (user over quota).

The statement is prepared, not executed. curral reads DuckDB’s unoptimized logical plan to learn the statement type, the base tables read (views expanded) and the table functions used, and a tokenizer finds the objects written. When something cannot be determined with certainty, resolved is false. See Policy input.

Failure: 400 for invalid SQL. Requests with more than one statement are rejected before anything runs.

The inspection result, the user and their roles go to the embedded OPA policy, which must return allow. Optional rules return per-request limits and column masks. Some statements (ATTACH, LOAD, INSTALL…) are denied whatever the policy says. See Writing a policy.

Failure: 403.

If the user has row filters or column masks on any table read, each reference to that table is replaced by a subquery that filters and masks first. Queries that cannot be rewritten safely are denied.

Failure: 403 with decided_by: protection.

The query runs on a fresh connection, inside one transaction that also covered inspection and authorization. Results stream as JSON, CSV, NDJSON or Arrow IPC, cut at the row limit in effect.

Failure: 504 on timeout; an error after streaming started arrives in the X-Curral-Error trailer. See HTTP API.