> For the complete documentation index, see [llms.txt](https://developer.celigo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.celigo.com/api/api-reference/data-stores/storage-query.md).

# Storage Query

Storage Query runs a single read-only SQL `SELECT` over CSV, JSON, NDJSON, and Parquet files held in Celigo Storage. A file or folder is referenced by its storage path, written where a table name would go (`FROM "orders/2026/september.csv"`) or passed to a reader function such as `read_csv_auto`. Submitting a query validates and authorizes it synchronously, then runs it in the background: poll the job's status until it completes and fetch the rows page by page. The account's license must include the Storage Query entitlement (`filesql`); every call on an account without it answers `403 STORAGE_QUERY_NOT_ENTITLED`.

### Storage query job schema

## The StorageQueryJob object

```json
{"openapi":"3.2.0","info":{"title":"Storage Query","version":"1.0.0"},"components":{"schemas":{"StorageQueryJob":{"type":"object","description":"A submitted query and its progress. Created by `POST /v1/storage/query`, read from\n`GET /v1/storage/query/{_id}/status`. The result manifest (`rowCount`, `pageCount`,\n`columns`, …) appears once the job completes; rows are fetched separately from\n`GET /v1/storage/query/{_id}/results`. Jobs are visible only to the account that\nsubmitted them and expire on their own after a retention period, after which the\nstatus and results routes answer 404.","required":["jobId","status","createdAt"],"properties":{"jobId":{"type":"string","format":"objectId","readOnly":true,"description":"Identifier of the query job. Use it as `{_id}` on the status and results routes."},"status":{"type":"string","enum":["queued","running","completed","failed"],"description":"Lifecycle of the job. Every job starts `queued` (the submit response) and moves\nto `running` when a worker claims it, then to `completed` or `failed`; there is\nno cancel. Constant queries usually complete within a few hundred milliseconds of\nsubmission, so a status read straight after the submit may already show\n`completed`."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the submit accepted the query."},"startedAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a worker began executing the statement. Absent while the job is `queued`."},"completedAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the job reached `completed` or `failed`. Absent until then."},"executionMs":{"type":"integer","minimum":0,"readOnly":true,"description":"Only present when `status` is `completed`. Milliseconds the statement took to execute, excluding time spent queued."},"rowCount":{"type":"integer","minimum":0,"readOnly":true,"description":"Only present when `status` is `completed`. Total rows in the result set across\nall pages; 0 when the query matched nothing, in which case `pageCount` is also 0\nand page 1 of the results route returns an empty page."},"truncated":{"type":"boolean","readOnly":true,"description":"Only present when `status` is `completed`. When true, the result set hit the\nservice's row cap and `rowCount` counts only the rows that were kept; narrow the\nquery (a `WHERE`, or a `LIMIT` with `ORDER BY`) to see the rest."},"pageCount":{"type":"integer","minimum":0,"readOnly":true,"description":"Only present when `status` is `completed`. Number of result pages to fetch, numbered from 1; 0 for an empty result set."},"columns":{"type":"array","readOnly":true,"description":"Only present when `status` is `completed`. Result columns in `SELECT` order, each\nwith the engine's type name for the column — the key each row object uses.\nPresent, and possibly non-empty, even when `rowCount` is 0.","items":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string","description":"Column name as projected by the statement (the alias when one was given)."},"type":{"type":"string","description":"SQL type of the column as reported by the engine, for example `INTEGER`,\n`VARCHAR`, `BOOLEAN`, `DECIMAL(2,1)`, or `TIMESTAMP`. Parameterized types\ncarry their parameters, so treat the value as free text rather than a fixed\nset."}}}},"error":{"type":"object","readOnly":true,"description":"Only present when `status` is `failed`. `code` names the failure class — for\nexample `STORAGE_QUERY_TIMEOUT` when the statement exceeded the execution\nbudget, `STORAGE_QUERY_EXECUTION_FAILED` when the engine rejected it while\nrunning, or `STORAGE_QUERY_JOB_INTERRUPTED` when the worker holding it went away\n(resubmit the same statement) — and `message` explains it.","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable failure code."},"message":{"type":"string","description":"Human-readable explanation of the failure."}}}}}}}}
```

## Submit a storage query

> Validates one read-only SQL \`SELECT\`, resolves every storage path it references\
> with the caller's own permissions, and queues it for execution. The response is\
> the new job in \`queued\` state; poll \`GET /v1/storage/query/{\_id}/status\` until it\
> is \`completed\`, then read rows from \`GET /v1/storage/query/{\_id}/results\`.\
> \
> Everything that can be checked before running is checked here and answered\
> synchronously, and a rejected submit creates no job: the statement must parse, be\
> exactly one \`SELECT\` (a \`DESCRIBE \<query>\` or \`SUMMARIZE \<query>\` over a storage\
> file is accepted as well and returns the column or summary rows), use only\
> allow-listed functions, reference files only through\
> bare storage paths or the storage reader functions, and every path must resolve to\
> a file — or a folder of same-format files — the caller can read. A statement that\
> references no file is accepted only when it is a pure constant expression. Supported\
> file formats are CSV/TSV, JSON, NDJSON/JSONL, and Parquet; the reader is chosen from\
> the file's extension (or its MIME type), and an explicit reader function must match\
> it. A compressed file such as \`orders.csv.gz\` is refused, even through a reader call\
> that passes \`compression = 'gzip'\`. A folder is referenced with a\
> trailing slash and contributes its direct child files only; wildcard paths are not\
> supported. A file whose upload has not finished yet is refused until it is active.\
> \
> Read options are named arguments of the reader call, checked against a per-reader\
> allowlist — for \`read\_csv\`/\`read\_csv\_auto\`: \`header\`, \`delim\`, \`quote\`, \`escape\`,\
> \`sample\_size\`, \`types\`, \`names\`, \`dateformat\`, \`timestampformat\`, \`ignore\_errors\`,\
> \`null\_padding\`, \`all\_varchar\`, \`nullstr\`, \`allow\_quoted\_nulls\`, \`compression\`,\
> \`union\_by\_name\`; for the JSON and NDJSON readers: \`sample\_size\`, \`ignore\_errors\`,\
> \`dateformat\`, \`timestampformat\`, \`map\_inference\_threshold\`, \`maximum\_depth\`,\
> \`maximum\_sample\_files\`, \`maximum\_object\_size\`, \`columns\`, \`format\`, \`records\`,\
> \`convert\_strings\_to\_integers\`, \`compression\`, \`union\_by\_name\`; for \`read\_parquet\`:\
> \`union\_by\_name\`. Option values must be literals.\
> \
> Submissions are rate-limited per account; over the limit the route answers\
> \`429 STORAGE\_QUERY\_SUBMIT\_RATE\_LIMITED\` with a \`Retry-After\` header, and every\
> submit — accepted or rejected — counts toward it. The number of distinct files a\
> statement may reference, the number of files a referenced folder may hold, and the\
> total bytes a statement may read are each capped; the 422 that reports an exceeded\
> cap names the current limit. Requires the account's license to include the Storage\
> Query entitlement (\`filesql\`).

```json
{"openapi":"3.2.0","info":{"title":"Storage Query","version":"1.0.0"},"tags":[{"name":"Storage Query","description":"Storage Query runs a single read-only SQL `SELECT` over CSV, JSON, NDJSON, and Parquet files held in Celigo Storage. A file or folder is referenced by its storage path, written where a table name would go (`FROM \"orders/2026/september.csv\"`) or passed to a reader function such as `read_csv_auto`. Submitting a query validates and authorizes it synchronously, then runs it in the background: poll the job's status until it completes and fetch the rows page by page. The account's license must include the Storage Query entitlement (`filesql`); every call on an account without it answers `403 STORAGE_QUERY_NOT_ENTITLED`.\n\n## Storage query job schema\n\n{% openapi-schemas spec=\"storagequery\" schemas=\"StorageQueryJob\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"StorageQueryRequest":{"type":"object","description":"One read-only SQL statement to run over files in Celigo Storage. The body carries\nnothing else — reader options such as CSV delimiters belong inside the SQL, as\nnamed arguments of the reader function that reads the file.","required":["sql"],"properties":{"sql":{"type":"string","description":"A single `SELECT` statement. Storage files are its tables: write a file or\nfolder path where a table name would go, quoted as an identifier\n(`FROM \"orders/2026/september.csv\"`) or as a string literal, or pass the path as\nthe first, literal argument of a reader function (`read_csv_auto`, `read_csv`,\n`read_json_auto`, `read_json`, `read_ndjson_auto`, `read_ndjson`, `read_parquet`)\nwhen the file needs explicit read options. Paths are relative to the account's\nstorage root and case-sensitive; a leading `/` is accepted. A folder is\nreferenced with a trailing `/` (`FROM \"orders/2026/\"`) and reads its direct\nchild files — not its subfolders — as one table; a folder path without the\nslash is refused at submit with 422 `STORAGE_NOT_A_FILE`, a file path with one\nwith 422 `STORAGE_ITEM_NOT_FOLDER`. Wildcards are not supported: a `*` or `?`\nin a path answers 422 `STORAGE_QUERY_INVALID_SOURCE`. Anything other than one\n`SELECT` — an `UPDATE`, two statements, a table function that is not a storage\nreader, or a function outside the allowlist — is rejected at submit with a 4xx\nand creates no job; `DESCRIBE <query>` and `SUMMARIZE <query>` are accepted and\nrun as ordinary jobs. A statement that references no file is accepted only when\nit is a pure constant expression."}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/storage/query":{"post":{"operationId":"submitStorageQuery","tags":["Storage Query"],"summary":"Submit a storage query","description":"Validates one read-only SQL `SELECT`, resolves every storage path it references\nwith the caller's own permissions, and queues it for execution. The response is\nthe new job in `queued` state; poll `GET /v1/storage/query/{_id}/status` until it\nis `completed`, then read rows from `GET /v1/storage/query/{_id}/results`.\n\nEverything that can be checked before running is checked here and answered\nsynchronously, and a rejected submit creates no job: the statement must parse, be\nexactly one `SELECT` (a `DESCRIBE <query>` or `SUMMARIZE <query>` over a storage\nfile is accepted as well and returns the column or summary rows), use only\nallow-listed functions, reference files only through\nbare storage paths or the storage reader functions, and every path must resolve to\na file — or a folder of same-format files — the caller can read. A statement that\nreferences no file is accepted only when it is a pure constant expression. Supported\nfile formats are CSV/TSV, JSON, NDJSON/JSONL, and Parquet; the reader is chosen from\nthe file's extension (or its MIME type), and an explicit reader function must match\nit. A compressed file such as `orders.csv.gz` is refused, even through a reader call\nthat passes `compression = 'gzip'`. A folder is referenced with a\ntrailing slash and contributes its direct child files only; wildcard paths are not\nsupported. A file whose upload has not finished yet is refused until it is active.\n\nRead options are named arguments of the reader call, checked against a per-reader\nallowlist — for `read_csv`/`read_csv_auto`: `header`, `delim`, `quote`, `escape`,\n`sample_size`, `types`, `names`, `dateformat`, `timestampformat`, `ignore_errors`,\n`null_padding`, `all_varchar`, `nullstr`, `allow_quoted_nulls`, `compression`,\n`union_by_name`; for the JSON and NDJSON readers: `sample_size`, `ignore_errors`,\n`dateformat`, `timestampformat`, `map_inference_threshold`, `maximum_depth`,\n`maximum_sample_files`, `maximum_object_size`, `columns`, `format`, `records`,\n`convert_strings_to_integers`, `compression`, `union_by_name`; for `read_parquet`:\n`union_by_name`. Option values must be literals.\n\nSubmissions are rate-limited per account; over the limit the route answers\n`429 STORAGE_QUERY_SUBMIT_RATE_LIMITED` with a `Retry-After` header, and every\nsubmit — accepted or rejected — counts toward it. The number of distinct files a\nstatement may reference, the number of files a referenced folder may hold, and the\ntotal bytes a statement may read are each capped; the 422 that reports an exceeded\ncap names the current limit. Requires the account's license to include the Storage\nQuery entitlement (`filesql`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageQueryRequest"}}}},"responses":{"202":{"description":"Query accepted and queued. Poll the status route with `jobId`.","content":{"application/json":{"schema":{"type":"object","required":["jobId","status"],"properties":{"jobId":{"type":"string","format":"objectId","description":"Identifier of the new job — the `{_id}` for the status and results routes."},"status":{"type":"string","enum":["queued"],"description":"Initial lifecycle state of the job."}}}}}},"400":{"description":"The body or the statement is malformed: `sql` is missing or not a non-empty\nstring (`STORAGE_QUERY_SQL_REQUIRED`), the statement does not parse\n(`STORAGE_QUERY_INVALID_SQL` — the message carries the parser's position), or it\nexceeds the size cap (`STORAGE_QUERY_SQL_TOO_LARGE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"description":"The account's license does not include the Storage Query entitlement\n(`STORAGE_QUERY_NOT_ENTITLED`). Also returned when the caller's role,\nenvironment, or account-share context does not permit the query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A referenced storage path did not resolve to a file the caller can read\n(`STORAGE_QUERY_SOURCE_NOT_FOUND`). A path outside the caller's reach looks\nexactly like a missing one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The statement parsed but is outside the supported surface. As-built cases:\nnot a `SELECT` (`STORAGE_QUERY_NOT_SELECT`); more than one statement\n(`STORAGE_QUERY_MULTIPLE_STATEMENTS`); a table function other than the storage\nreaders, `unnest`, `generate_series`, or `range`\n(`STORAGE_QUERY_TABLE_FUNCTION_NOT_ALLOWED`); a scalar function outside the\nallowlist (`STORAGE_QUERY_FUNCTION_NOT_ALLOWED`); a reader option the reader does\nnot accept, or a positional option (`STORAGE_QUERY_INVALID_READ_OPTION`); a reader\nwhose first argument is not a literal path or list of paths\n(`STORAGE_QUERY_READER_PATH_NOT_LITERAL`); a reader that does not match the\nfile's format (`STORAGE_QUERY_READER_FORMAT_MISMATCH`); a row generator with no\nfile reference (`STORAGE_QUERY_NO_STORAGE_REFERENCE`) or one that would generate\nmore rows than allowed (`STORAGE_QUERY_GENERATOR_TOO_LARGE`); a schema- or\ncatalog-qualified table name, or a path carrying a wildcard\n(`STORAGE_QUERY_INVALID_SOURCE`); a folder path without its trailing slash\n(`STORAGE_NOT_A_FILE`) or a file path with one (`STORAGE_ITEM_NOT_FOLDER`); an\nunsupported file type (`STORAGE_QUERY_UNSUPPORTED_FILE_TYPE`) — a compressed\nfile is refused too, with \"Querying `<name>` is not supported; supported formats\nare CSV, JSON, NDJSON, and Parquet\"; a file still uploading (\"Upload for `<path>`\nis not yet complete\"); a folder that is\nempty, mixes formats, or holds too many files (`STORAGE_QUERY_FOLDER_EMPTY`,\n`STORAGE_QUERY_MIXED_FORMAT_FOLDER`, `STORAGE_QUERY_FOLDER_TOO_MANY_FILES`); too\nmany distinct files (`STORAGE_QUERY_TOO_MANY_SOURCES`); or more input bytes than\nthe cap (`STORAGE_QUERY_INPUT_TOO_LARGE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many submits for this account in the current window\n(`STORAGE_QUERY_SUBMIT_RATE_LIMITED`). Wait for the number of seconds in the\n`Retry-After` header before submitting again.","headers":{"Retry-After":{"description":"Seconds to wait before the next submit.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get a storage query job's status

> Returns the job's lifecycle state and timestamps. Once \`status\` is \`completed\` the\
> response also carries the result manifest — \`rowCount\`, \`pageCount\`, \`truncated\`,\
> \`columns\`, and \`executionMs\` — which is everything needed to page through\
> \`GET /v1/storage/query/{\_id}/results\`. A \`failed\` job carries \`error\` instead.\
> Jobs are scoped to the submitting account: another account's job, or one that has\
> expired, answers 404 exactly like an unknown id. Requires the account's license to\
> include the Storage Query entitlement (\`filesql\`).

```json
{"openapi":"3.2.0","info":{"title":"Storage Query","version":"1.0.0"},"tags":[{"name":"Storage Query","description":"Storage Query runs a single read-only SQL `SELECT` over CSV, JSON, NDJSON, and Parquet files held in Celigo Storage. A file or folder is referenced by its storage path, written where a table name would go (`FROM \"orders/2026/september.csv\"`) or passed to a reader function such as `read_csv_auto`. Submitting a query validates and authorizes it synchronously, then runs it in the background: poll the job's status until it completes and fetch the rows page by page. The account's license must include the Storage Query entitlement (`filesql`); every call on an account without it answers `403 STORAGE_QUERY_NOT_ENTITLED`.\n\n## Storage query job schema\n\n{% openapi-schemas spec=\"storagequery\" schemas=\"StorageQueryJob\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"StorageQueryJob":{"type":"object","description":"A submitted query and its progress. Created by `POST /v1/storage/query`, read from\n`GET /v1/storage/query/{_id}/status`. The result manifest (`rowCount`, `pageCount`,\n`columns`, …) appears once the job completes; rows are fetched separately from\n`GET /v1/storage/query/{_id}/results`. Jobs are visible only to the account that\nsubmitted them and expire on their own after a retention period, after which the\nstatus and results routes answer 404.","required":["jobId","status","createdAt"],"properties":{"jobId":{"type":"string","format":"objectId","readOnly":true,"description":"Identifier of the query job. Use it as `{_id}` on the status and results routes."},"status":{"type":"string","enum":["queued","running","completed","failed"],"description":"Lifecycle of the job. Every job starts `queued` (the submit response) and moves\nto `running` when a worker claims it, then to `completed` or `failed`; there is\nno cancel. Constant queries usually complete within a few hundred milliseconds of\nsubmission, so a status read straight after the submit may already show\n`completed`."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the submit accepted the query."},"startedAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a worker began executing the statement. Absent while the job is `queued`."},"completedAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the job reached `completed` or `failed`. Absent until then."},"executionMs":{"type":"integer","minimum":0,"readOnly":true,"description":"Only present when `status` is `completed`. Milliseconds the statement took to execute, excluding time spent queued."},"rowCount":{"type":"integer","minimum":0,"readOnly":true,"description":"Only present when `status` is `completed`. Total rows in the result set across\nall pages; 0 when the query matched nothing, in which case `pageCount` is also 0\nand page 1 of the results route returns an empty page."},"truncated":{"type":"boolean","readOnly":true,"description":"Only present when `status` is `completed`. When true, the result set hit the\nservice's row cap and `rowCount` counts only the rows that were kept; narrow the\nquery (a `WHERE`, or a `LIMIT` with `ORDER BY`) to see the rest."},"pageCount":{"type":"integer","minimum":0,"readOnly":true,"description":"Only present when `status` is `completed`. Number of result pages to fetch, numbered from 1; 0 for an empty result set."},"columns":{"type":"array","readOnly":true,"description":"Only present when `status` is `completed`. Result columns in `SELECT` order, each\nwith the engine's type name for the column — the key each row object uses.\nPresent, and possibly non-empty, even when `rowCount` is 0.","items":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string","description":"Column name as projected by the statement (the alias when one was given)."},"type":{"type":"string","description":"SQL type of the column as reported by the engine, for example `INTEGER`,\n`VARCHAR`, `BOOLEAN`, `DECIMAL(2,1)`, or `TIMESTAMP`. Parameterized types\ncarry their parameters, so treat the value as free text rather than a fixed\nset."}}}},"error":{"type":"object","readOnly":true,"description":"Only present when `status` is `failed`. `code` names the failure class — for\nexample `STORAGE_QUERY_TIMEOUT` when the statement exceeded the execution\nbudget, `STORAGE_QUERY_EXECUTION_FAILED` when the engine rejected it while\nrunning, or `STORAGE_QUERY_JOB_INTERRUPTED` when the worker holding it went away\n(resubmit the same statement) — and `message` explains it.","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable failure code."},"message":{"type":"string","description":"Human-readable explanation of the failure."}}}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/storage/query/{_id}/status":{"get":{"operationId":"getStorageQueryStatus","tags":["Storage Query"],"summary":"Get a storage query job's status","description":"Returns the job's lifecycle state and timestamps. Once `status` is `completed` the\nresponse also carries the result manifest — `rowCount`, `pageCount`, `truncated`,\n`columns`, and `executionMs` — which is everything needed to page through\n`GET /v1/storage/query/{_id}/results`. A `failed` job carries `error` instead.\nJobs are scoped to the submitting account: another account's job, or one that has\nexpired, answers 404 exactly like an unknown id. Requires the account's license to\ninclude the Storage Query entitlement (`filesql`).","parameters":[{"name":"_id","in":"path","required":true,"description":"The `jobId` returned by `POST /v1/storage/query`.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Current state of the job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageQueryJob"}}}},"400":{"description":"The path segment is not a valid job identifier (`STORAGE_QUERY_INVALID_JOB_ID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"description":"The account's license does not include the Storage Query entitlement (`STORAGE_QUERY_NOT_ENTITLED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No job with this id is visible to the caller (`STORAGE_QUERY_JOB_NOT_FOUND`) — unknown, expired, or submitted by another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many status or results reads for this account (`STORAGE_QUERY_RATE_LIMITED`). Wait for the seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before the next read.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get a page of storage query results

> Returns one page of a completed job's rows. Pages run from 1 to the job's\
> \`pageCount\`; omit \`page\` for the first one and keep requesting \`page + 1\` while\
> \`hasMore\` is true. A page holds as many whole rows as fit the service's page size,\
> which is a byte budget rather than a row count — a result of several hundred\
> thousand small rows can arrive as a single page — and no parameter changes it, so a\
> caller that must hold a page in memory bounds the result with \`LIMIT\` in the\
> statement. A completed job that matched nothing has \`pageCount\` 0 and still serves\
> page 1 as an empty page, so an empty result set never looks like a bad request. Rows exist only for a \`completed\` job: a \`queued\` or \`running\` job answers\
> 409 until it finishes, and a \`failed\` job answers 409 permanently — read its \`error\`\
> from the status route instead. Jobs are scoped to the submitting account, and an\
> expired job answers 404. Requires the account's license to include the Storage\
> Query entitlement (\`filesql\`).

```json
{"openapi":"3.2.0","info":{"title":"Storage Query","version":"1.0.0"},"tags":[{"name":"Storage Query","description":"Storage Query runs a single read-only SQL `SELECT` over CSV, JSON, NDJSON, and Parquet files held in Celigo Storage. A file or folder is referenced by its storage path, written where a table name would go (`FROM \"orders/2026/september.csv\"`) or passed to a reader function such as `read_csv_auto`. Submitting a query validates and authorizes it synchronously, then runs it in the background: poll the job's status until it completes and fetch the rows page by page. The account's license must include the Storage Query entitlement (`filesql`); every call on an account without it answers `403 STORAGE_QUERY_NOT_ENTITLED`.\n\n## Storage query job schema\n\n{% openapi-schemas spec=\"storagequery\" schemas=\"StorageQueryJob\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"StorageQueryResultsPage":{"type":"object","description":"One page of a completed job's rows, as returned by\n`GET /v1/storage/query/{_id}/results?page=N`. Pages are numbered from 1 up to the\njob's `pageCount`; fetch them in turn until `hasMore` is false.","required":["page","pageCount","rowCount","hasMore","rows"],"properties":{"page":{"type":"integer","minimum":1,"description":"Page number served. 1 when the `page` query parameter was omitted."},"pageCount":{"type":"integer","minimum":0,"description":"Total pages in the result set — the same value as `pageCount` on the job's status. 0 when the query matched nothing."},"rowCount":{"type":"integer","minimum":0,"description":"Number of rows on this page (not the job-wide total the status route reports)."},"hasMore":{"type":"boolean","description":"When true, a further page exists — request `page + 1`. False on the last page and on an empty result set."},"rows":{"type":"array","description":"Rows on this page, in the statement's output order. Each row is an object keyed\nby column name (see `columns` on the job's status). `TINYINT`, `SMALLINT`,\n`INTEGER`, `FLOAT`, `DOUBLE` and `BOOLEAN` values arrive as JSON numbers and\nbooleans. `BIGINT`, `UBIGINT`, `HUGEINT` and `DECIMAL` values arrive as strings so\nno precision is lost (`\"9007199254740993\"`, `\"1.50\"`) — and every whole-number\ncolumn inferred from a CSV, JSON or NDJSON file is `BIGINT`, as are `COUNT`, `SUM`\nover integer columns and the ranking window functions, so those come back as\nstrings such as `\"40\"`. `DATE`, `TIME` and `TIMESTAMP` values arrive as strings\n(`2026-09-01`, `10:00:00`, `2026-09-01 10:00:00`). A list column (`INTEGER[]`,\n`VARCHAR[]` — including a JSON array the reader typed as a list) arrives as a JSON\narray, a `STRUCT` as a JSON object, a `MAP` as an array of `{\"key\", \"value\"}`\npairs, an `INTERVAL` as `{\"months\", \"days\", \"micros\"}` where `months` and `days`\nare numbers and `micros` is a string (`{\"months\": 0, \"days\": 1, \"micros\": \"0\"}`\nfor `INTERVAL 1 DAY`). Empty on an empty result set.","items":{"type":"object","additionalProperties":true}}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/storage/query/{_id}/results":{"get":{"operationId":"getStorageQueryResults","tags":["Storage Query"],"summary":"Get a page of storage query results","description":"Returns one page of a completed job's rows. Pages run from 1 to the job's\n`pageCount`; omit `page` for the first one and keep requesting `page + 1` while\n`hasMore` is true. A page holds as many whole rows as fit the service's page size,\nwhich is a byte budget rather than a row count — a result of several hundred\nthousand small rows can arrive as a single page — and no parameter changes it, so a\ncaller that must hold a page in memory bounds the result with `LIMIT` in the\nstatement. A completed job that matched nothing has `pageCount` 0 and still serves\npage 1 as an empty page, so an empty result set never looks like a bad request. Rows exist only for a `completed` job: a `queued` or `running` job answers\n409 until it finishes, and a `failed` job answers 409 permanently — read its `error`\nfrom the status route instead. Jobs are scoped to the submitting account, and an\nexpired job answers 404. Requires the account's license to include the Storage\nQuery entitlement (`filesql`).","parameters":[{"name":"_id","in":"path","required":true,"description":"The `jobId` returned by `POST /v1/storage/query`.","schema":{"type":"string","format":"objectId"}},{"name":"page","in":"query","required":false,"description":"Page to return, from 1 to the job's `pageCount`. Defaults to 1 when omitted.\nAnything that is not a positive integer within range — `0`, a non-numeric\nvalue, or a number past `pageCount` — answers 400 rather than being coerced.","schema":{"type":"integer","minimum":1,"default":1}}],"responses":{"200":{"description":"One page of rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageQueryResultsPage"}}}},"400":{"description":"`page` is not a positive integer within `1..pageCount`\n(`STORAGE_QUERY_PAGE_OUT_OF_RANGE` — the message names the job's page count),\nor the path segment is not a valid job identifier (`STORAGE_QUERY_INVALID_JOB_ID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"description":"The account's license does not include the Storage Query entitlement (`STORAGE_QUERY_NOT_ENTITLED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No job with this id is visible to the caller (`STORAGE_QUERY_JOB_NOT_FOUND`) — unknown, expired, or submitted by another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The job has not produced rows: it is still `queued` or `running`, or it `failed`\n(`STORAGE_QUERY_JOB_NOT_READY` — the message names the current status). Poll the\nstatus route; retry here only once it reports `completed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many status or results reads for this account (`STORAGE_QUERY_RATE_LIMITED`). Wait for the seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before the next read.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```
