Report Queries API
/api/v1/reportQueries manages saved Query Studio queries. A saved report
query is metadata over the same query engine /api/v1/query
already exposes: it stores a target entity, a visibility scope, and an
advancedQuery payload in the exact GenericPagingAndFiltering grammar the
ad hoc workbench speaks, and running it delegates straight to that engine.
There is no second query engine here, only a named, reusable, shareable
wrapper around the one the workbench already ships.
This page covers slice 1 of the reporting workbench (see
ADR-v1-reporting-workbench):
metadata CRUD plus synchronous execution. Scheduling, asynchronous execution,
result polling, file download, reportExtracts, and the standard
agedReceivables report are designed but not shipped yet; see
Deferred in this slice.
Prerequisites
A bearer token, per Authentication:
export TOKEN=$(curl -s -X POST "$KEYCLOAK_TOKEN_URL" \
-d "grant_type=client_credentials&client_id=$KEYCLOAK_CLIENT_ID&client_secret=$KEYCLOAK_CLIENT_SECRET" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
Quick start
curl -X POST https://acme.billerang.com/api/v1/reportQueries \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"code": "RQ_SMOKE_CUSTOMERS",
"description": "Smoke: customers by code",
"targetEntity": "customer",
"visibility": "PUBLIC",
"advancedQuery": {"genericFields": ["code", "description"], "filters": {}}
}'
Run it:
curl -X POST https://acme.billerang.com/api/v1/reportQueries/RQ_SMOKE_CUSTOMERS/executions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/reportQueries | Create a saved query. 409 if the code already exists, or a name/visibility collision. |
POST | /api/v1/reportQueries/{code} | Upsert by functional code. |
PUT | /api/v1/reportQueries/{code} | Partial update. Null keeps, empty-for-type clears. |
DELETE | /api/v1/reportQueries/{code} | Delete a saved query. 204 on success. |
POST | /api/v1/reportQueries/{code}/executions | Run the query. Only mode=sync is available in this slice. |
Reads (list, get, filter) never go through this resource. They go through
GET /api/v1/query/reportQuery, the same as any other
queryable entity.
Create a saved report query
POST /api/v1/reportQueries
Creates a new saved query. code, targetEntity, visibility, and
advancedQuery are required; advancedQuery must be non-empty. Fails 409
if the code already exists, or (folded in from the legacy POST /verify
rule) if the code/visibility pair collides with another user's query
under the same visibility scope.
Example, live-verified:
curl -X POST https://acme.billerang.com/api/v1/reportQueries \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"code": "RQ_SMOKE_CUSTOMERS",
"description": "Smoke: customers by code",
"targetEntity": "customer",
"visibility": "PUBLIC",
"advancedQuery": {"genericFields": ["code", "description"], "filters": {}}
}'
Response (201), echoes the input plus the read-only fields:
{
"code": "RQ_SMOKE_CUSTOMERS",
"description": "Smoke: customers by code",
"targetEntity": "customer",
"visibility": "PUBLIC",
"advancedQuery": {"genericFields": ["code", "description"], "filters": {}},
"system": false
}
Duplicate code, live-verified:
{
"status": "FAIL",
"message": "Query Already exists and belong to you",
"error_code": "resource-already-exists"
}
Missing advancedQuery, live-verified:
{
"status": "FAIL",
"message": "advancedQuery is required and must be non-empty",
"error_code": "missing-parameter"
}
Create or update by code (upsert)
POST /api/v1/reportQueries/{code}
Idempotent upsert keyed on the path code. Creates the query if absent (same
validation and response shape as plain create, 201), otherwise updates it
(200). The path code wins over any code in the body.
Update a saved report query (partial)
PUT /api/v1/reportQueries/{code}
Partial update following the platform's null-keeps/empty-clears rule (see
API principles): a
field that is null or absent keeps its current value; an empty value for
the type ("", [], {}) clears it. One deliberate exception:
advancedQuery cannot be cleared with {} because a saved query must always
have a query. Sending {} for advancedQuery returns 400 missing-parameter. tags and aliases use whole-collection replace
semantics on write (mirrors the legacy layer, which itself replaces rather
than merges).
A system query is read-only: PUT against one returns 409 resource-in-use.
Delete a saved report query
DELETE /api/v1/reportQueries/{code}
Deletes the query. Returns 204 with no body. Fails 404 if the code does
not exist, 409 resource-in-use if the query is a system query.
Live-verified: DELETE /api/v1/reportQueries/RQ_SMOKE_CUSTOMERS returns
204.
Run a saved report query
POST /api/v1/reportQueries/{code}/executions
Executes the saved query by building a GenericPagingAndFiltering from the
stored advancedQuery and targetEntity, and running it through the same
engine /api/v1/query uses. Only mode=sync is available in this
slice (also the default when mode is omitted); results come back inline
in the response body.
Sync row cap: results are capped at 100 rows, matching the query
engine's own api.list.defaultLimit default. Rows beyond the cap are
silently truncated. There is no way to raise the cap or paginate a sync
execution in this slice; a caller needing more than 100 rows must wait for
mode=async (deferred, see below).
Example, live-verified:
curl -X POST https://acme.billerang.com/api/v1/reportQueries/RQ_SMOKE_CUSTOMERS/executions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
Response (200):
{
"status": "SUCCESS",
"rowCount": 25,
"rows": [
{"code": "LIDLE", "description": "LIDLE Belgium"}
]
}
mode=async, live-verified, not available in this slice:
curl -X POST 'https://acme.billerang.com/api/v1/reportQueries/RQ_SMOKE_CUSTOMERS/executions?mode=async' \
-H "Authorization: Bearer $TOKEN"
{
"status": "FAIL",
"message": "Async execution is not yet available; only mode=sync is supported in this slice",
"error_code": "invalid-parameter"
}
Unknown code, live-verified:
{
"status": "FAIL",
"message": "No report query with code NO_SUCH_RQ",
"error_code": "resource-not-found"
}
Fields reference
ReportQueryV1 request/response fields:
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
code | string | Yes (create) | Functional code (legacy queryName). Immutable identity of the saved query. | "RQ_SMOKE_CUSTOMERS" |
description | string | No | Free-text description of what the query returns. | "Smoke: customers by code" |
targetEntity | string | Yes (create) | Simple name of the target JPA entity the query runs against. | "customer" |
visibility | string | Yes (create) | Sharing scope: PUBLIC (everyone), PRIVATE (creator only), PROTECTED (creator's group). | "PUBLIC" |
tags | array of string | No | Free-form tags for organizing saved queries. Replace semantics on write. | ["finance"] |
aliases | object | No | Column alias map (query field to display alias). Replace semantics on write. | {"amountWithTax": "Amount"} |
advancedQuery | object | Yes (create) | The GenericPagingAndFiltering-shaped query payload: filters, genericFields, sortBy, sortOrder, groupBy, having, nested selectors. Same grammar as /api/v1/query. Must be non-empty on create; cannot be cleared on update. | {"genericFields": ["code", "description"], "filters": {}} |
queryType | string | Read-only | Internal classification (VISUAL/HQL/NATIVE_SQL), derived server-side. | "VISUAL" |
system | boolean | Read-only | true for a system-provided query. System queries reject PUT/DELETE with 409 resource-in-use. | false |
ReportQueryExecutionV1 (execution response), this slice:
| Field | Type | Description |
|---|---|---|
status | string | Always SUCCESS in this slice (sync-only). |
rowCount | integer | Number of rows returned, after the 100-row cap. |
rows | array of object | Result rows in the shape produced by the delegated query engine. |
customFields is not exposed: ReportQuery is a plain BusinessEntity, not
a custom-field-capable entity. The deprecated legacy flat columns (fields,
filters, sortBy, sortOrder, queryParameters) are not carried into
this DTO either; v1 speaks only advancedQuery.
Error responses
All errors use the platform's ErrorV1 shape: {"status": "FAIL", "message": "...", "error_code": "..."}.
| Status | error_code | Cause |
|---|---|---|
400 | missing-parameter | Missing code, targetEntity, visibility, or advancedQuery on create; advancedQuery empty on create or cleared with {} on update. |
400 | invalid-parameter | Unknown visibility; unsupported mode value on execution (including mode=async, deferred). |
404 | resource-not-found | No report query with that code. |
409 | resource-already-exists | Code already exists, or a name/visibility collision with another owner's query. |
409 | resource-in-use | PUT or DELETE targeting a system query. |
Live-verified examples of each are in the endpoint sections above.
Deferred in this slice
The ADR designs a larger reporting workbench than this slice ships. The following are on the endpoint catalog but not available yet:
- Schedule sub-resource (
PUT/GET/DELETE /api/v1/reportQueries/{code}/schedule) to run a saved query on a cron-like recurrence and notify a distribution list. - Asynchronous executions (
mode=async). Currently rejected with400 invalid-parameterand the message shown above. - Execution result polling (
GET /api/v1/reportQueries/{code}/executions/{executionId}) for an async run's status and rows. - File download (
GET /api/v1/reportQueries/{code}/executions/{executionId}/file?format=CSV|EXCEL). reportExtracts(/api/v1/reportExtracts), the scripted SQL/Java file-export surface. A different model from a saved query; not part of this slice.- Standard reports (
/api/v1/reports/agedReceivablesand its export), the fixed aged-receivables report. Not CRUD, not part of this slice.
Each of these has a stable target shape in the ADR; nothing here is a placeholder that will change contract, only functionality being added.
See also
- Query API for the engine
/api/v1/reportQueries/{code}/executionsdelegates to, including reads of saved queries viaGET /api/v1/query/reportQuery. - API principles for update semantics and the error model shared by every v1 resource.
- The full request/response schemas (
ReportQueryV1,ReportQueryExecutionV1) are in the Interactive API Explorer.