Skip to content

Submit a query

POST
/api/v1/bi/queries
curl -X POST 'https://api.justcrawl.io/api/v1/bi/queries' \
-H 'Authorization: Bearer $JUSTCRAWL_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT status, count(*) FROM jobs GROUP BY 1","label":"status mix"}'

Submit either read-only SQL or one structured aggregate definition against your bi schema. The two input forms are mutually exclusive. The call waits briefly for the query to finish: if it completes inside that window the first page of rows comes back in the same response under results; otherwise you get { jobId, status } and poll GET /api/v1/bi/queries/{id}.

A 200 does not mean the query succeeded. A query that failed fast returns 200 with status: failed and an error object describing the query’s failure — that error is query detail, not an HTTP error envelope, and carries no docs_url or request_id. Branch on status, not on the HTTP code.

dryRun is the pre-submit EXPLAIN estimate. It is informational: nothing is rejected for size, and only the engine’s own time limit is enforced.

Send a stable UUID in Idempotency-Key when retrying an indeterminate submission. An exact replay returns the original query and does not enqueue it again; reusing the key for a different request returns idempotency_conflict.

Idempotency-Key
string format: uuid

Stable UUID for replay-safe submission.

Media typeapplication/json
One of:
object
sql
required

The query. Read-only; max 100000 characters.

string
label

Optional human label shown in query history. Max 200 characters.

string
sourceQueryId

Owned history execution whose immutable scope this SQL edit retains.

string format: uuid

Query accepted. results is present only when the query completed inside the sync-wait window; error is present only when it failed inside that window.

Media typeapplication/json
object
jobId
required
string format: uuid
replayed
required
boolean
submissionKey
string format: uuid
status
required
string
Allowed values: queued running success failed canceled
dryRun

Informational EXPLAIN estimate from the Postgres dry-run planning pass. All three fields are present together when the estimate succeeded, and all three are absent (an empty object) when no estimate exists — the CH-force kill switch skips the dry-run entirely, or it timed out before producing a plan. There is no valid flag on the wire; the caller cannot distinguish “skipped” from “timed out” from this field alone.

object
planRows
integer
planWidth
integer
estimatedBytes
integer
sqlPreview

Display-only rendered SQL for structured submissions. Never accepted as input.

string
results

One page of result rows. Returned bare by GET /api/v1/bi/queries/{id}/results, but nested under results by POST /api/v1/bi/queries when the sync-wait window catches a completed query. The two are not interchangeable.

object
rows
required
Array<object>
object
key
additional properties
any
page
required
integer
pageSize
required
integer
totalRows
required

Total rows materialized for this query. -1 when unknown.

integer
hasMore
required
boolean
historyCoverage

Present only when a structured aggregate left out readings older than the typed-column history backfill. Those readings have no typed values, so measures and typed filters could not include them. Absent when the answer covers every matching reading.

object
backfillHorizon
required

Readings created before this instant may be missing from the result.

string format: date-time
preHorizonRows
required

Matching source readings excluded for lack of typed history.

integer
>= 1
scopeKind
string
Allowed values: org agent
scopeAgentId
string format: uuid
nullable
error

The query’s own failure detail — not an HTTP error envelope.

object
code
string
Allowed values: syntax_error statement_timeout rls_denied too_many_queries kill_switched canceled transient
message
string
retriable
boolean
Example
{
"status": "queued",
"scopeKind": "org",
"error": {
"code": "syntax_error"
}
}

Body/header rejected (invalid_query_input, invalid_definition, invalid_sql, invalid_label, invalid_idempotency_key, invalid_execution_context) or SQL failed validation (syntax_error)

Media typeapplication/json
object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
error
required
object
code
string
Allowed values: invalid_query_input invalid_definition invalid_sql invalid_label invalid_idempotency_key invalid_execution_context syntax_error
Example
{
"error": {
"code": "invalid_query_input"
}
}

Missing or invalid authentication token

Media typeapplication/json
object
error
string
Example
{
"error": "Missing or invalid authentication token"
}

Access to the BI surface itself is refused. no_org and feature_disabled fire on every BI route from the mount guard, before any handler runs — feature_disabled is the first response most new integrations see, because the SQL console is entitled per organization. Permission and verified-email gates use insufficient_permissions and email_not_verified; rls_denied and kill_switched come from the engine. This response never describes resource-level authorization: an id you do not own returns 404, not 403.

Media typeapplication/json
object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
error
required
object
code
string
Allowed values: no_org feature_disabled insufficient_permissions email_not_verified authentication_required user_not_found rls_denied kill_switched
Example
{
"error": {
"code": "feature_disabled",
"message": "SQL console is not enabled for this organization",
"docs_url": "https://docs.justcrawl.io/guides/errors/bi",
"request_id": "req_9f3a1c7e2b40"
}
}

Not found, or not owned by your organization. The two are deliberately indistinguishable so cross-tenant existence cannot be probed

Media typeapplication/json

Error envelope returned by every /api/v1/bi/* handler error. Unlike the rest of the API — where the structured shape is opt-in via the application/vnd.justcrawl.v1+json media type — a BI handler always emits it, and all four fields are always present. The one exception: 401s from missing or invalid auth are rejected by shared middleware before any BI handler runs, so they carry the flat { "error": "<message>" } shape (Error schema) instead — see the Unauthorized response on each operation.

object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
Example
{
"error": {
"code": "not_found",
"message": "Query not found",
"docs_url": "https://docs.justcrawl.io/guides/errors/not_found",
"request_id": "req_9f3a1c7e2b40"
}
}

The engine could not complete the submission and the failure is not attributable to your SQL (transient, canceled), or the idempotency key was reused for another request (idempotency_conflict).

Media typeapplication/json
object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
error
required
object
code
string
Allowed values: transient canceled idempotency_conflict
Example
{
"error": {
"code": "transient"
}
}

Your organization’s concurrency or queue-depth cap is full (too_many_queries).

No Retry-After header is sent — this API emits none anywhere on the BI surface, so do not read one. Back off with capped exponential retry instead.

Media typeapplication/json
object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
error
required
object
code
string
Allowed values: too_many_queries
Example
{
"error": {
"code": "too_many_queries"
}
}

Unexpected server error. Full detail goes to the logs and PostHog $exception capture, never the response

Media typeapplication/json

Error envelope returned by every /api/v1/bi/* handler error. Unlike the rest of the API — where the structured shape is opt-in via the application/vnd.justcrawl.v1+json media type — a BI handler always emits it, and all four fields are always present. The one exception: 401s from missing or invalid auth are rejected by shared middleware before any BI handler runs, so they carry the flat { "error": "<message>" } shape (Error schema) instead — see the Unauthorized response on each operation.

object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
Example
{
"error": {
"code": "internal_error",
"message": "Failed to list queries",
"docs_url": "https://docs.justcrawl.io/guides/errors/internal_error",
"request_id": "req_9f3a1c7e2b40"
}
}

The query exceeded the engine’s time limit (statement_timeout). The limit that applied is reported as maxQuerySec by the result manifest.

Media typeapplication/json
object
error
required
object
code
required

Stable, machine-readable error code. Match on this, not on message.

string
message
required

Customer-safe description. Never raw exception or driver text, with one deliberate exception: a syntax_error (SQLSTATE 42601) passes the database’s own message through verbatim, because it describes SQL the caller wrote themselves and names nothing they could not already see — see the root CLAUDE.md §Error reporting for the full carve-out.

string
docs_url
required

Reference page for this code. Codes shared with the rest of the API have their own page; the BI-local vocabulary points at the single BI errors page that tabulates all of them.

string format: uri
request_id
required

Correlation id. Identical to the X-Request-ID response header — quote either one to support.

string
error
required
object
code
string
Allowed values: statement_timeout
Example
{
"error": {
"code": "statement_timeout"
}
}