API Reference
Overview
Complete reference for the two Analytics Query API endpoints. For a worked example, see Getting Started.
Base URL — your tenant hostname:
https://<tenant>.britive-app.comAuthentication — every request carries a token for an identity holding
bizintel.analytics.manage:
Authorization: TOKEN <token>POST /api/bizintel/analytics/query
Submits a query and starts its execution.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
sql | string | yes | A single read-only SELECT. Non-empty. See SQL rules. |
{
"sql": "SELECT username, status FROM users_latest WHERE status = 'active' LIMIT 100"
}Response — 202 Accepted
| Field | Type | Description |
|---|---|---|
query_execution_id | string | Identifier to poll for status and results. |
status | string | Always SUBMITTED. |
{
"query_execution_id": "3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3",
"status": "SUBMITTED"
}Status codes
| Code | Meaning |
|---|---|
202 | Query accepted and started. |
400 | Body is not valid JSON, is not a JSON object, sql is missing or empty, sql is not a single read-only SELECT, or the query engine rejected the SQL text itself. |
403 | Identity lacks bizintel.analytics.manage. |
502 | Upstream failure starting the query. Retry. |
202 means the query started, not that it will succeed. Errors that need the data
to be read — an unknown column, a type mismatch — surface as a FAILED status when you
poll.
GET /api/bizintel/analytics/query/{query_execution_id}
Returns the current state of a query, and a result download URL once it succeeds.
Path parameter
| Name | Type | Description |
|---|---|---|
query_execution_id | string | The id returned by the submit call. |
Response — 200 OK
Fields present depend on status:
| Field | Type | When | Description |
|---|---|---|---|
query_execution_id | string | always | Echo of the id polled. |
status | string | always | QUEUED, RUNNING, SUCCEEDED, FAILED, or CANCELLED. |
download_url | string | SUCCEEDED | Signed URL to the result CSV. Fetch with no Authorization header. |
expires_in | integer | SUCCEEDED | Seconds the download_url stays valid. Default 300. |
error | string | FAILED, CANCELLED | Reason the query did not complete. |
Still running:
{
"query_execution_id": "3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3",
"status": "RUNNING"
}Finished successfully:
{
"query_execution_id": "3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3",
"status": "SUCCEEDED",
"download_url": "https://.../3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3.csv?X-Amz-Signature=...",
"expires_in": 300
}Failed:
{
"query_execution_id": "3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3",
"status": "FAILED",
"error": "COLUMN_NOT_FOUND: line 1:8: Column 'usrname' cannot be resolved"
}Status codes
| Code | Meaning |
|---|---|
200 | Status retrieved — including for a FAILED or CANCELLED query. The lookup succeeded, so the query’s failure is reported as data rather than an HTTP error. |
403 | Identity lacks bizintel.analytics.manage. |
404 | No such query execution id. Unknown and expired ids are deliberately indistinguishable. |
502 | Upstream failure checking status or signing the result URL. Retry. |
Treat FAILED as an application-level outcome, not an HTTP error. A client that only
checks the HTTP status code will read 200 and assume success. Always branch on the
status field.
Error Response Shape
4xx and 5xx responses from the API carry a JSON body:
| Field | Type | Description |
|---|---|---|
statusCode | integer | Repeats the HTTP status code. |
message | string | Human-readable reason. |
{
"statusCode": 400,
"message": "only read-only SELECT queries are permitted (got INSERT)"
}Upstream errors present as 502.
SQL Rules
Only a single read-only SELECT is accepted. Enforcement is an allowlist: the
statement’s first top-level verb must be SELECT. A leading WITH is fine, because CTE
bodies are nested in parentheses — the verb after the CTE list is what counts.
Accepted
| Statement | Note |
|---|---|
SELECT ... | The ordinary case. |
WITH t AS (SELECT ...) SELECT ... | CTEs are allowed. |
(SELECT ...) UNION (SELECT ...) | Set operations are allowed. |
SELECT 'INSERT INTO t' | Keywords inside string literals are harmless. |
SELECT unload FROM t | Column names that collide with verbs are unaffected. |
SELECT ...; | A single trailing semicolon is fine. |
Rejected with 400
| Statement | Message |
|---|---|
INSERT / UPDATE / DELETE / MERGE | only read-only SELECT queries are permitted (got INSERT) |
CREATE / DROP / ALTER / TRUNCATE / MSCK | only read-only SELECT queries are permitted (got CREATE) |
UNLOAD (SELECT ...) TO 's3://...' | only read-only SELECT queries are permitted (got UNLOAD) |
SHOW / DESCRIBE / EXPLAIN / USE / SET | only read-only SELECT queries are permitted (got SHOW) |
SELECT 1; DROP TABLE x | multiple SQL statements are not permitted; submit a single SELECT query |
| `` (empty string) | query must not be empty |
SELECT 'unclosed | unterminated quoted literal in query |
Comments and quoted literals are stripped before the statement is analysed, so no keyword can hide inside them, and only top-level tokens are examined, so a subquery can never be mistaken for the statement’s verb.
SHOW, DESCRIBE, and EXPLAIN are rejected by design, not oversight. This API serves
query data; schema exploration belongs in Britive Analytics.
Statement Checking Is Not the Only Safeguard
Rejecting non-SELECT statements gives you a clean 400 rather than an opaque failure
part-way through execution. Underneath it, the data is read-only — nothing in this API
can delete or alter it.
Limits
| Limit | Value | Note |
|---|---|---|
| Result URL lifetime | 300 seconds | Reported per-response as expires_in. Poll again for a fresh URL. |
| Statements per request | 1 | Stacked statements are rejected. |
| Result format | CSV | Includes a header row of column names. |
Related
- Getting Started — a worked end-to-end example.
- Overview — the asynchronous model and how queries are scoped.
- Troubleshooting — error-by-error resolution.