Skip to main content

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

MethodPathPurpose
POST/api/v1/reportQueriesCreate 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}/executionsRun 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:

FieldTypeRequiredDescriptionExample
codestringYes (create)Functional code (legacy queryName). Immutable identity of the saved query."RQ_SMOKE_CUSTOMERS"
descriptionstringNoFree-text description of what the query returns."Smoke: customers by code"
targetEntitystringYes (create)Simple name of the target JPA entity the query runs against."customer"
visibilitystringYes (create)Sharing scope: PUBLIC (everyone), PRIVATE (creator only), PROTECTED (creator's group)."PUBLIC"
tagsarray of stringNoFree-form tags for organizing saved queries. Replace semantics on write.["finance"]
aliasesobjectNoColumn alias map (query field to display alias). Replace semantics on write.{"amountWithTax": "Amount"}
advancedQueryobjectYes (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": {}}
queryTypestringRead-onlyInternal classification (VISUAL/HQL/NATIVE_SQL), derived server-side."VISUAL"
systembooleanRead-onlytrue for a system-provided query. System queries reject PUT/DELETE with 409 resource-in-use.false

ReportQueryExecutionV1 (execution response), this slice:

FieldTypeDescription
statusstringAlways SUCCESS in this slice (sync-only).
rowCountintegerNumber of rows returned, after the 100-row cap.
rowsarray of objectResult 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": "..."}.

Statuserror_codeCause
400missing-parameterMissing code, targetEntity, visibility, or advancedQuery on create; advancedQuery empty on create or cleared with {} on update.
400invalid-parameterUnknown visibility; unsupported mode value on execution (including mode=async, deferred).
404resource-not-foundNo report query with that code.
409resource-already-existsCode already exists, or a name/visibility collision with another owner's query.
409resource-in-usePUT 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 with 400 invalid-parameter and 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/agedReceivables and 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}/executions delegates to, including reads of saved queries via GET /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.