Skip to content

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.manage permission. This is the same permission that grants access to Britive Analytics; an identity with only bizintel.analytics.view is rejected with 403. 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:

terminal
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.

terminal
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.

terminal
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:

terminal
curl -sS -o results.csv "<download_url>"

Verify

Confirm the CSV arrived and holds your rows:

terminal
head -3 results.csv

Expected 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.

analytics_query.py
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

SymptomCauseFix
403 on submitIdentity lacks bizintel.analytics.manageGrant the permission, or use an identity that can open Britive Analytics
400 only read-only SELECT queries are permittedStatement is not a SELECTRewrite as a single SELECT; see SQL rules
404 when pollingWrong id, or results have aged outRe-check the id from the submit response; resubmit the query
download_url returns AccessDeniedURL expired, or sent with an auth headerPoll 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.
Last updated on