Skip to main content
Version: v12

BQL API

The BQL API provides programmatic access to the Brinqa data warehouse using BQL (Brinqa Query Language). It replaces the legacy GraphQL API with a simpler, more flexible interface that leverages the same query engine used by the Brinqa UI.

Authentication

The BQL API uses API tokens for authentication. API tokens are tied to a specific user account and inherit that user's access control permissions (ACL/RBAC). This means the API token can only access data that the associated user is authorized to see.

note

The BQL API only accepts API token authentication. Session-based or Bearer token authentication is not accepted for BQL API endpoints.

Generate an API token

To generate an API token, follow these steps in the Brinqa Platform UI:

  1. Click the account icon (person circle) in the top navigation bar.
  2. Select API Tokens from the dropdown menu.
  3. On the API Tokens page, click the + (create) button in the header.
  4. In the Generate API Token dialog, fill in:
    • Token name (required): A descriptive name to identify this token's purpose (e.g., SOAR Vulnerability Export). Max 255 characters. Letters, numbers, spaces, hyphens, underscores, and periods are allowed.
    • Expiration: Select one of: 1 Month, 3 Months, 6 Months, 12 Months, or Never expires.
  5. Click Generate. The raw token (starting with brq_) is displayed once.
warning

Copy and store the token securely immediately. It is not shown again. If lost, you must revoke the token and generate a new one.

The API Tokens page also lets you view all your tokens with their status, creation date, expiration date, and last used date.

Token creation restrictions

  • Non-administrator users can only create a new token if they have no active tokens, or if their current active token is within 30 days of expiration.
  • System administrators can create tokens at any time.

Use your API token

Include the API token in the Authorization header using the ApiKey scheme:

Authorization: ApiKey brq_AbCdEfGh.xYz123...

Example with curl:

curl -X POST 'https://<your-brinqa-instance>/v1/api/bql' \
-H 'Content-Type: application/json' \
-H 'Authorization: ApiKey brq_AbCdEfGh.xYz123...' \
-d '{
"query": "FIND Vulnerability",
"returningFields": ["id", "name"],
"limit": 10
}'

Token lifecycle

StatusDescription
ActiveThe token can be used for authentication.
InactiveThe token has been manually revoked and cannot be used. It can be reinstated.
ExpiredThe token has passed its expiration date. It cannot be used or reinstated.
  • Tokens are automatically marked as expired when used after their expiration date.
  • An API token's permissions are inherited from the user account for which it was generated. If the user's permissions change, the token reflects the current permissions.

Manage tokens

On the API Tokens page (account icon > API Tokens), you can manage your existing tokens:

  • Revoke: Hover over a token row and click the Revoke action icon. This sets the token status to Inactive and it can no longer be used for authentication.
  • Reinstate: Hover over a previously revoked token row and click the Reinstate action icon. This reactivates the token, setting its status back to Active. Expired tokens cannot be reinstated.
  • Delete: Hover over a token row and click the Delete action icon to permanently remove the token.

Users can revoke, reinstate, and delete their own tokens. System administrators can revoke, reinstate, and delete tokens for any user.

Querying data

The BQL API uses an asynchronous, two-step query workflow:

  1. Start a query (POST). Returns a polling URL.
  2. Fetch results (GET). Poll until results are ready.

Start a query

POST /v1/api/bql

Headers:

Content-Type: application/json
Authorization: ApiKey <your-api-token>

Request body:

{
"query": "FIND Vulnerability WHERE riskScore > 8",
"returningFields": ["id", "name", "riskScore"],
"orderBy": ["riskScore DESC"],
"limit": 100
}

Response: 202 Accepted

The response body is empty. The response headers contain:

HeaderDescription
LocationThe URL to poll for results (e.g., /v1/api/bql/eyJqb2J...).
Retry-AfterRecommended wait time in seconds before polling.

Fetch results

GET /v1/api/bql/{token}

Headers:

Authorization: ApiKey <your-api-token>

Poll the URL from the Location header. The response depends on the query status:

While processing (202 Accepted)

{
"status": "processing",
"results": []
}

Headers include Location and Retry-After, which indicates when to poll again.

When complete (200 OK)

{
"status": "completed",
"state": "success",
"message": "Query completed successfully.",
"results": [
{
"id": 1847261953084,
"name": "CVE-2024-1234",
"riskScore": 9.8
},
{
"id": 1847261953085,
"name": "CVE-2024-5678",
"riskScore": 9.1
}
],
"totalRows": 1458,
"pageRows": 312,
"cursor": "eyJwYWdl..."
}
FieldTypeDescription
statusstring"processing" or "completed".
statestring"success" or "failed" (only present when status is "completed").
messagestringDescriptive message about the result.
resultsarrayArray of result objects. Each object contains the requested fields. Up to 10,000 rows are returned per page.
totalRowsintegerTotal number of rows matched by the query across all pages. May be null while the total is still being computed.
pageRowsintegerNumber of rows returned in the current page (i.e., the size of the results array).
cursorstringOpaque token for fetching the next page. null if there are no more results.

Fetching additional pages: If the cursor is not null, the query returned more results than fit in a single page. To retrieve the next page:

  1. Read the Location header from the response. It contains the URL for the next page (e.g., /v1/api/bql/eyJuZXh0...).
  2. Make a GET request to that URL with the same Authorization header.
  3. The response has the same format: a new results array and a new cursor. If the cursor is null, you have reached the last page.

The response also includes a Link header (<next-page-url>; rel="next") that you can use as an alternative to the Location header.

When failed

{
"status": "completed",
"state": "failed",
"message": "Query failed (invalidQuery): Syntax error in BQL expression.",
"results": []
}

Complete workflow example

This example starts a query, waits for results, and follows pagination to collect all pages.

BASE_URL="https://brinqa.example.com"
API_TOKEN="ApiKey brq_AbCdEfGh.xYz123..."

# Step 1: Start the query
HEADERS=$(curl -s -D - -o /dev/null -X POST "$BASE_URL/v1/api/bql" \
-H 'Content-Type: application/json' \
-H "Authorization: $API_TOKEN" \
-d '{
"query": "FIND Vulnerability WHERE riskScore > 7",
"returningFields": ["id", "name", "riskScore", "status"],
"orderBy": ["riskScore DESC"]
}')

LOCATION=$(echo "$HEADERS" | grep -i '^Location:' | awk '{print $2}' | tr -d '\r')
RETRY_AFTER=$(echo "$HEADERS" | grep -i '^Retry-After:' | awk '{print $2}' | tr -d '\r')

# Step 2: Poll until the query completes, then paginate through all results
while true; do
sleep "$RETRY_AFTER"

RESPONSE=$(curl -s -D /tmp/bql_headers.txt "$BASE_URL$LOCATION" \
-H "Authorization: $API_TOKEN")

STATUS=$(echo "$RESPONSE" | jq -r '.status')

# Still running -- wait and poll the same URL again
if [ "$STATUS" = "processing" ]; then
RETRY_AFTER=$(grep -i '^Retry-After:' /tmp/bql_headers.txt | awk '{print $2}' | tr -d '\r')
continue
fi

# Query finished -- check for errors
STATE=$(echo "$RESPONSE" | jq -r '.state')
if [ "$STATE" = "failed" ]; then
echo "Query failed: $(echo "$RESPONSE" | jq -r '.message')"
exit 1
fi

# Print this page's results
echo "$RESPONSE" | jq '.results'

# If cursor is null, this was the last page
CURSOR=$(echo "$RESPONSE" | jq -r '.cursor // empty')
if [ -z "$CURSOR" ]; then
echo "All pages fetched."
break
fi

# Follow the Location header to the next page
LOCATION=$(grep -i '^Location:' /tmp/bql_headers.txt | awk '{print $2}' | tr -d '\r')
RETRY_AFTER=0
done

Request modes

The BQL API supports two distinct modes for requesting data. The mode is determined by whether you include the returningFields parameter in your request.

Analytics mode

In this mode, the BQL query string controls which fields are returned, sorting, and row limits. Use full BQL syntax including RETURN, ORDER BY, and LIMIT clauses directly in the query field.

{
"query": "FIND Vulnerability RETURN id, name, riskScore ORDER BY name LIMIT 100"
}

This mode works the same way as BQL queries in the Brinqa UI (e.g., reports and dashboards).

Projection mode

In this mode, the query field contains only the entity, relationships, and conditions (the FIND ... WHERE ... clauses). The output is controlled by separate parameters: returningFields, orderBy, and limit.

{
"query": "FIND Vulnerability WHERE riskScore > 8",
"returningFields": ["id", "name", "riskScore"],
"orderBy": ["riskScore DESC"],
"limit": 50
}

This mode enables features not available in analytics mode, such as nested relationship projections and wildcards. See Returning fields in detail for all options.

note

When a value for the returningFields is provided, the projection fields take precedence over any RETURN, ORDER BY, or LIMIT clauses in the query string.

How each mode handles relationships

The two modes produce fundamentally different result structures when querying related entities.

Analytics mode: flattened results

In analytics mode, you use THAT clauses to traverse relationships, and related entity attributes are flattened into the same result row. The field names in the result preserve the alias prefix from the query (e.g., vd.displayName).

For one-to-one relationships, the related attributes appear as additional fields in each row:

{
"query": "FIND Vulnerability AS v THAT IS VulnerabilityDefinition AS vd RETURN v.id, v.riskScore, vd.displayName, vd.description LIMIT 10"
}

Example response (results array):

[
{ "v.id": 1847261953084, "v.riskScore": 9.8, "vd.displayName": "CVE-2024-1234", "vd.description": "SQL injection in login form" },
{ "v.id": 1847261953085, "v.riskScore": 8.5, "vd.displayName": "CVE-2024-5678", "vd.description": "XSS in search field" }
]

For one-to-many relationships, each related entity produces a separate row. The main entity's attributes are repeated for each related entity:

{
"query": "FIND Vulnerability AS v THAT HAS Host AS h RETURN v.id, v.riskScore, h.displayName, h.ipAddresses LIMIT 10"
}

Example response (results array):

[
{ "v.id": 1847261953084, "v.riskScore": 9.8, "h.displayName": "prod-web-01.us-east.example.com", "h.ipAddresses": ["10.0.0.1", "10.0.0.2"] },
{ "v.id": 1847261953084, "v.riskScore": 9.8, "h.displayName": "prod-db-02.us-west.example.com", "h.ipAddresses": ["10.0.1.1"] },
{ "v.id": 1847261953085, "v.riskScore": 8.5, "h.displayName": "prod-web-01.us-east.example.com", "h.ipAddresses": ["10.0.0.1", "10.0.0.2"] }
]

This is similar to how a SQL JOIN works. Vulnerability 1847261953084 appears in two rows because it has two hosts.

Projection mode: nested results

In projection mode, related entities are returned as nested objects within a single row. One-to-many relationships produce an array, and one-to-one relationships produce a single object.

{
"query": "FIND Vulnerability",
"returningFields": ["id", "riskScore", "targets(displayName, ipAddresses)"]
}

Example response (results array):

[
{
"id": 1847261953084,
"riskScore": 9.8,
"targets": [
{ "displayName": "prod-web-01.us-east.example.com", "ipAddresses": ["10.0.0.1", "10.0.0.2"] },
{ "displayName": "prod-db-02.us-west.example.com", "ipAddresses": ["10.0.1.1"] }
]
},
{
"id": 1847261953085,
"riskScore": 8.5,
"targets": [
{ "displayName": "prod-web-01.us-east.example.com", "ipAddresses": ["10.0.0.1", "10.0.0.2"] }
]
}
]

Each vulnerability appears once, with all its targets grouped in an array.

Request parameters

ParameterTypeRequiredDescription
querystringYesThe BQL query to execute. In analytics mode, include RETURN/ORDER BY/LIMIT clauses. In projection mode, include only FIND ... WHERE ....
returningFieldsarray of stringsNoControls which attributes appear in each result row. When provided, the API uses projection mode. See Returning fields in detail.
orderByarray of stringsNoSort order. Each entry is a field name optionally followed by ASC (ascending, default) or DESC (descending). Example: ["riskScore DESC", "name ASC"].
limitintegerNoMaximum number of rows to return. If not specified, all matching rows are returned, paginated in pages of up to 10,000 rows each.
appstringNoThe application context for the query. Used when the Brinqa instance has multiple apps configured.

Returning fields in detail

The returningFields parameter provides fine-grained control over which data is included in each result row. Its behavior depends on the value provided.

Analytics mode (returningFields omitted)

When returningFields is not included in the request, the API operates in analytics mode. The RETURN clause in your BQL query string determines which columns are returned. No nested relationship data is available in this mode.

{
"query": "FIND Vulnerability RETURN id, name, riskScore ORDER BY name LIMIT 100"
}

Default projection (empty array)

When returningFields is an empty array [], the API returns only the id and the display attribute of the main entity. The display attribute is defined in each Data Model (e.g., displayName for Vulnerability).

{
"query": "FIND Vulnerability WHERE riskScore > 8",
"returningFields": []
}

Explicit fields

List specific attribute names to include only those fields in the results.

{
"query": "FIND Vulnerability",
"returningFields": ["id", "name", "riskScore", "status"]
}

Wildcard

Use "*" to return every persisted attribute of the main entity. Relationships are automatically expanded with their id and display attribute.

{
"query": "FIND Vulnerability",
"returningFields": ["*"]
}

Relationship projections

You can include data from related entities using nested projection syntax. In the examples below, targets is a relationship attribute on Vulnerability that points to related assets.

Bare relationship name

Using just the relationship name returns each related entity's id and display attribute.

{
"query": "FIND Vulnerability",
"returningFields": ["id", "name", "targets"]
}

Nested projection with specific fields

Specify exactly which attributes of the related entity to return.

{
"query": "FIND Vulnerability",
"returningFields": ["id", "name", "targets(ip, hostname)"]
}

Nested wildcard

Use * inside the projection to return all simple (non-relationship) attributes of the related entity.

{
"query": "FIND Vulnerability",
"returningFields": ["id", "type(*)"]
}

Response shape for relationships

Relationship typeJSON value
One-to-many (e.g., Vulnerability -> targets)Array of objects: "targets": [{"ip": "10.0.0.1"}, ...]
One-to-one (e.g., Vulnerability -> type)Single object or null: "type": {"id": 1512748391002, "name": "SQL Injection"}

Limitation

Nested-inside-nested projections are not supported. You cannot request a relationship attribute inside a projection where that attribute is itself a relationship (e.g., targets(id, type) where type is a relationship). This produces an error.

Combining modes

You can freely combine explicit fields, bare relationships, nested projections, and wildcards:

{
"query": "FIND Vulnerability WHERE status = \"open\"",
"returningFields": ["id", "name", "riskScore", "targets(ip, hostname)", "type(*)"],
"orderBy": ["riskScore DESC"],
"limit": 50
}

Pagination

Results are delivered in pages of up to 10,000 rows each. When more results are available, the response includes a cursor field and a Location header with the URL for the next page.

Pagination flow:

  1. Start your query with POST /v1/api/bql. You receive a Location header.
  2. Fetch the first page with GET /v1/api/bql/{token}.
  3. If the response contains a cursor value (non-null), more results are available. The response also includes:
    • A Location header with the URL for the next page.
    • A Link header: <next-page-url>; rel="next".
  4. Fetch the next page by following the Location or Link header URL.
  5. Repeat until cursor is null, indicating no more results.

ETag support:

Each completed page response includes an ETag header. You can include an If-Match header in subsequent fetch requests to verify that results have not changed. If the results have changed, the API returns 412 Precondition Failed.

# Fetch first page
RESPONSE=$(curl -s -D /tmp/bql_headers.txt 'https://brinqa.example.com/v1/api/bql/eyJqb2J...' \
-H 'Authorization: ApiKey brq_AbCdEfGh.xYz123...')

# Extract ETag from response headers
ETAG=$(grep -i 'ETag:' /tmp/bql_headers.txt | awk '{print $2}' | tr -d '\r')

# Fetch next page with ETag validation
curl -s 'https://brinqa.example.com/v1/api/bql/eyJuZXh0...' \
-H 'Authorization: ApiKey brq_AbCdEfGh.xYz123...' \
-H "If-Match: $ETAG"

Rate limiting

The BQL API enforces rate limits to ensure fair usage. Separate rate limits apply to:

  • Starting queries (POST /v1/api/bql)
  • Fetching results (GET /v1/api/bql/{token})

When you exceed the rate limit, the API returns:

429 Too Many Requests

{
"status": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded"
}

The response includes a Retry-After header indicating how many seconds to wait before retrying.

Best practices:

  • Respect the Retry-After header values returned in both 202 Accepted and 429 responses.
  • Avoid sending requests faster than the recommended polling interval.
  • Use exponential backoff when receiving 429 responses.

Error handling

HTTP StatusMeaningCommon cause
202 AcceptedQuery submitted or still processing.Normal. Poll the Location URL.
200 OKResults ready (check state for success/failure).Normal response.
401 UnauthorizedAuthentication failed.Missing, invalid, expired, or revoked API token.
412 Precondition FailedETag mismatch.Results changed between fetches (when using If-Match).
429 Too Many RequestsRate limit exceeded.Too many requests in a short period. Wait and retry.

Query-level errors are returned in the response body with state: "failed" and a descriptive message, not as HTTP errors:

{
"status": "completed",
"state": "failed",
"message": "Query failed (invalidQuery): Unrecognized entity type 'Vuln'.",
"results": []
}

Quick reference

ItemValue
Start queryPOST /v1/api/bql
Fetch resultsGET /v1/api/bql/{token}
Auth headerAuthorization: ApiKey <token>
Token formatbrq_...
Token expiry options1, 3, 6, 12 months, or never
Max rows per page10,000
Manage tokensAccount icon > API Tokens

For specific examples of how to use the BQL API to retrieve different datasets, see the following articles: