Getting Started
By the end of this guide you will have run a SELECT against your tenant’s analytics
data over HTTP and downloaded the results as a CSV file.
Before You Begin
You need:
- Your tenant URL — the hostname of your Britive console, for example
https://acme.britive-app.com. See Finding Your Tenant Name. - A token for an identity holding the
bizintel.analytics.managepermission. This is the same permission that grants access to Britive Analytics; an identity with onlybizintel.analytics.viewis rejected with403. For unattended scripts, use a service identity token. - A table name to query. This API cannot list tables. Open Britive Analytics and note
a table in your tenant’s schema. The examples below use
users_latest— substitute a table you can see there.
Export your token and tenant so the commands below can be pasted as-is:
export BRITIVE_TENANT_URL="https://acme.britive-app.com"
export BRITIVE_TOKEN="<your-token>"Keep tokens out of shell history and source control. Read them from your secret store or an environment variable, never a literal in a committed script.
Steps
Submit a query
POST your SQL to the query endpoint. The body is a JSON object with a single sql field.
curl -sS -X POST "$BRITIVE_TENANT_URL/api/bizintel/analytics/query" \
-H "Authorization: TOKEN $BRITIVE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM users_latest LIMIT 10"}'The response is 202 Accepted with the id you will poll:
{
"query_execution_id": "3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3",
"status": "SUBMITTED"
}Poll until the query finishes
GET the same path with the id appended. Repeat every 10 seconds while the status is
RUNNING or QUEUED. Polling faster adds load without returning results any sooner.
export QID="3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3"
curl -sS "$BRITIVE_TENANT_URL/api/bizintel/analytics/query/$QID" \
-H "Authorization: TOKEN $BRITIVE_TOKEN"While it runs:
{
"query_execution_id": "3f7c1a92-5d84-4b0e-9a11-2c6de0f8b7a3",
"status": "RUNNING"
}Download the results
Once the status is SUCCEEDED, the response carries a signed CSV link and the number of
seconds it stays valid:
{
"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
}Fetch it with no Authorization header — the URL is already signed:
curl -sS -o results.csv "<download_url>"Verify
Confirm the CSV arrived and holds your rows:
head -3 results.csvExpected output — a header row from your SELECT, followed by data rows:
"user_id","username","status","updated_at"
"a1b2c3d4","jane.smith@acme.com","active","2026-08-01 14:22:07.000"
"e5f6g7h8","alex.chen@acme.com","active","2026-08-01 14:22:07.000"A results.csv containing only a header row means the query ran correctly and matched
no rows — widen your WHERE clause or pick a different table.
Full Example
This script runs the whole sequence — submit, poll with a timeout, download — and
writes results.csv.
import os
import time
import requests
BASE = os.environ["BRITIVE_TENANT_URL"].rstrip("/")
TOKEN = os.environ["BRITIVE_TOKEN"]
PATH = f"{BASE}/api/bizintel/analytics/query"
HEADERS = {"Authorization": f"TOKEN {TOKEN}", "Content-Type": "application/json"}
SQL = "SELECT * FROM users_latest LIMIT 10"
POLL_SECONDS = 10
TIMEOUT_SECONDS = 300
def run_query(sql: str) -> str:
"""Submit the query and return its execution id."""
response = requests.post(PATH, json={"sql": sql}, headers=HEADERS, timeout=30)
response.raise_for_status()
return response.json()["query_execution_id"]
def wait_for_results(query_execution_id: str) -> str:
"""Poll until the query settles, then return a signed download URL."""
deadline = time.monotonic() + TIMEOUT_SECONDS
while time.monotonic() < deadline:
response = requests.get(
f"{PATH}/{query_execution_id}",
headers={"Authorization": f"TOKEN {TOKEN}"},
timeout=30,
)
response.raise_for_status()
body = response.json()
status = body["status"]
if status == "SUCCEEDED":
return body["download_url"]
if status in ("FAILED", "CANCELLED"):
raise RuntimeError(f"query {status}: {body.get('error', 'no reason given')}")
time.sleep(POLL_SECONDS)
raise TimeoutError(f"query {query_execution_id} did not finish in {TIMEOUT_SECONDS}s")
if __name__ == "__main__":
qid = run_query(SQL)
print(f"submitted {qid}")
url = wait_for_results(qid)
# No auth header here - the presigned URL carries its own authorization.
csv = requests.get(url, timeout=60)
csv.raise_for_status()
with open("results.csv", "wb") as handle:
handle.write(csv.content)
print(f"wrote results.csv ({len(csv.content)} bytes)")Troubleshoot
| Symptom | Cause | Fix |
|---|---|---|
403 on submit | Identity lacks bizintel.analytics.manage | Grant the permission, or use an identity that can open Britive Analytics |
400 only read-only SELECT queries are permitted | Statement is not a SELECT | Rewrite as a single SELECT; see SQL rules |
404 when polling | Wrong id, or results have aged out | Re-check the id from the submit response; resubmit the query |
download_url returns AccessDenied | URL expired, or sent with an auth header | Poll again for a fresh URL and fetch it with no Authorization header |
Full error catalogue: Troubleshooting.
Next Steps
- API Reference — every field, status code, and SQL rule.
- Overview — why the API is asynchronous and how queries are scoped.