# Run Query

```
POST 
/v1/query
```

Run an inline alias-only SQL query across the caller's own datasets.

* `datasetIds` (plural, OPTIONAL):

  <!-- -->

  * **Provided**: each id resolves to that dataset's alias; the SQL joins the aliases. Deny-don't-drop — if the key is not authorized for EVERY id, the whole query is rejected (403 `query_dataset_forbidden`), never executed against the granted subset.
  * **Omitted**: the server INFERS the set from the aliases the SQL references (each resolved to a dataset by `clean_sql_name` for the calling account), then applies the SAME authorization. An alias that matches no dataset in the account → 400 `query_dataset_unresolved`. An inferred dataset the key isn't granted is still rejected (403) — inference is a DX convenience, NOT an authorization relaxation.

* **Dataset readiness**: only a `READY` dataset has an Iceberg table behind it. A participating dataset still ingesting (`PENDING`/`DOWNLOADING`/ `PROCESSING`) → 400 `query_dataset_not_ready`; one whose ingestion failed (`FAILED`) → 400 `query_dataset_failed`. Both name the dataset and its `processingStatus`, in both `backend` and `wasm` modes.

* `sql`: inline, alias-only. Physical tables / DDL / file+remote readers are rejected by the embed SQL validator (403 `embed_sql_invalid`).

* `params`: optional pass-through column filters, bound as escaped literals (never string-interpolated). A filter on a phantom column fails closed (403 `embed_filter_invalid`).

* `type` (Phase 208): `backend` (default) or `wasm`.

  <!-- -->

  * `wasm` asks for a PLAN instead of rows: the rewritten SQL plus one presigned Parquet extract per participating dataset, which the caller runs locally in DuckDB-WASM. Authorization, alias-only validation and filter injection ALL still happen server-side, in that order, BEFORE the capability check — asking for wasm cannot skip a single gate.
  * If any participating dataset is not wasm-capable (no Iceberg snapshot, or an extract over `WASM_EXTRACT_MAX_BYTES`), the query executes server-side exactly as today and the response reports `mode: "backend"` with a `fallbackReason`. Never a silent downgrade.

Returns typed columnar `{ columns:[{name,type}], rows }` — or, in wasm mode, `{ mode:"wasm", plan:{ sql, sources } }`. `mode` always states what the query ACTUALLY ran as. Metered through the existing usage/credits mechanism in both lanes. Result transport (inline vs presigned object-storage blob) is an internal decision hidden by the SDK's `query()`.

## Request[​](#request "Link diretto a request")

## Responses[​](#responses "Link diretto a Responses")

* 200
* 404
* 422

Successful Response

Not found

Validation Error
