REST API Reference
20260709 — Current
Use https://api.admetrics.io/api/20260709 as this version's API base.
Select the version explicitly in the URL; the API-Version response header identifies the selected contract.
Download this version's OpenAPI specification · All supported versions · Release and upgrade notes
Authentication
REST callers can authenticate with:
Authorization: Bearer <token>
Use an existing managed API key or OAuth access token. Keep credentials in server-side configuration or your application's secure credential storage. Refresh expired OAuth tokens through the grant's existing resource and scopes. Every reporting client in a query must be authorized for the caller.
REST Query Flow
Structured query execution: build or receive a trusted structured analytics query, optionally validate it, then call POST /api/20260709/queries/run. The run endpoint validates again before execution, so backend integrations should treat this as the primary REST execution call.
analytics_query.execution.status, execution.result, and table_preview when available.Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/20260709/openapi.json | Get the 20260709 OpenAPI document |
GET | /api/20260709/catalog | Get the normalized analytics catalog |
GET | /api/20260709/clients | List token-scoped reporting accounts |
GET | /api/20260709/clients/{client_id}/freshness | Get client data freshness |
POST | /api/20260709/catalog/search | Search metrics, dimensions, clients, and indexed values |
POST | /api/20260709/questions/compile | Compile and validate a natural-language analytics question |
POST | /api/20260709/questions/enqueue | Compile, validate, and enqueue an analytics question |
POST | /api/20260709/questions/run | Compile, validate, run, and fetch an analytics result |
POST | /api/20260709/queries/run | Validate and run a structured analytics query |
POST | /api/20260709/queries/explain | Explain query-planning metadata |
GET | /api/20260709/mcp/tools | Inspect advertised MCP tools over REST |
POST | /api/20260709/mcp/tools/call | Call an advertised MCP tool over REST |
Shared endpoints include GET /health and the aggregate GET /openapi.json.
The MCP interface uses POST /mcp and retains the current contract.
Question endpoints are documented for agent and MCP-style workflows. Backend REST integrations should prefer direct structured-query execution through POST /api/20260709/queries/run.
Client Listing
Use GET /api/20260709/clients when an integration only needs accessible client IDs and names. It is lighter than fetching the full catalog and is the preferred backing endpoint for searchable client pickers.
| Query parameter | Notes |
|---|---|
q | Optional substring filter across client ID, display name, external name, parent/child IDs, and stack name. |
limit | Defaults to 100 and is capped at 500. |
offset | Pagination offset. Continue with next_offset until it is null. |
The response includes client_count, returned_count, next_offset, and clients. Each client contains id, client_id, optional display/external names, optional stack metadata, parent client ID, and child client IDs.
Data Freshness
Use GET /api/20260709/clients/{client_id}/freshness before
interpreting results that cover today or yesterday, or whenever ingestion lag
could affect an answer.
curl -sS "https://api.admetrics.io/api/20260709/clients/2938354/freshness" \
-H "Authorization: Bearer ${AQL_BEARER_TOKEN}"
{
"client_id": "2938354",
"observed_at": "2026-08-03T10:00:00Z",
"dbt": { "last_success_at": "2026-08-03T08:00:00Z" },
"vendor_pull": {
"last_success_at": "2026-08-03T09:00:00Z",
"vendors": ["google"]
}
}
All timestamps are UTC completion times. Both checkpoints include only regular
successful runs; backfills are excluded. A token-authorized client with no
successful history returns null for the relevant timestamp. Missing or
invalid credentials return 401, and a valid token without access to the
requested client returns 403.
Common Natural-Language Request Fields
| Field | Notes |
|---|---|
question | Required for /api/20260709/questions/*. Ask for one atomic analytics query. |
client_id, client_ids | Optional but recommended. |
date_context | Optional YYYY-MM-DD base date for relative phrases. |
metric_ids, dimension_ids, sort_id, sort_direction | Exact catalog overrides. |
attribution_model, attribution_window, metric_params | Optional attribution parameters validated against the live catalog. |
limit | Optional row limit. |
format, result_format | records, rows, columnar, csv, txt, xlsx, dataframe, or arrow. |
dimension_filters | Exact indexed dimension values to constrain the query. |
wait_for_result | Whether to wait for queued completion; run endpoints wait by default. |
timeout_ms, poll_interval_ms | Optional queued wait and polling controls in milliseconds. |
response_detail | compact or full response detail. |
Structured Analytics Query Run Fields
| Field | Type and limits | Meaning |
|---|---|---|
analytics_query (required) | object | Structured query with select, filters, shape and options. Use catalog IDs for metrics and dimensions. |
query_source (required) | stringcompiled_question user_provided_query | Use user_provided_query for application-built queries, or compiled_question for compiler output. |
format | stringrecords rows columnar csv txt xlsx dataframe arrow json text | Output format alias. result_format takes precedence when both are supplied. |
result_format | stringrecords rows columnar csv txt xlsx dataframe arrow json text | Preferred output format override. |
wait_for_result | boolean Default: true | Wait for a queued result. False returns after submission. |
timeout_ms | integer 1000–900000 Default: 120000 | Maximum wait for a queued result, in milliseconds. |
poll_interval_ms | integer 1000–30000 Default: 1000 | Interval in milliseconds between queued status checks. |
response_detail | stringcompact full | Response detail level. Does not change the requested report data. |
Unknown top-level fields are rejected. Keep execution controls outside analytics_query.
The service validates the query before execution. Read validation before assuming that a result is present.
Records request
Replace the example client ID with an authorized reporting client. Save this body as query.json:
{
"query_source": "user_provided_query",
"wait_for_result": true,
"result_format": "records",
"analytics_query": {
"select": {
"dimensions": [
{
"id": "date"
}
],
"metrics": [
{
"id": "consumption.spend",
"as": "spend"
}
]
},
"filters": [
{
"id": "client_id",
"filter": {
"$in": [
"1234567"
]
}
},
{
"id": "date",
"filter": {
"$gte": "2026-10-01",
"$lte": "2026-10-07"
}
}
],
"shape": {
"limit": 100,
"offset": 0
},
"options": {
"totals": true,
"column_infos": true,
"column_desc": true
}
}
}
curl --fail-with-body --max-time 135 \
"https://api.admetrics.io/api/20260709/queries/run" \
-H "Authorization: Bearer ${AQL_BEARER_TOKEN}" \
-H 'Content-Type: application/json' --data-binary @query.json
Queued execution
A structured query uses queued execution by default. With wait_for_result: true (the default),
the request waits for completion up to timeout_ms. With wait_for_result: false, it returns
after submission. The response's execution contains queued, and may contain status,
result, timed_out, or error. A response without execution.result is not a completed report.
A wait timeout does not establish that the query was cancelled. Follow the returned job links
when available instead of submitting the same query again.
Metadata-Aware Filters
Filter-only metrics and dimensions do not need to be selected as outputs. Filters constrain the query scope; additional rows are combined with AND. Use exact IDs and parameter values from the catalog.
| Field class | Operations | AQL operators |
|---|---|---|
| Numeric | equals, comparisons, between, in/not in, exists/not exists | $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $notexists |
| Date | on, before/after, between, in/not in, exists/not exists | $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $notexists |
| Text | equals, in/not in, contains/not contains, regex/not regex, exists/not exists | $eq, $ne, $in, $nin, $regex, $notregex, $exists, $notexists |
| Identifier | equals, in/not in, exists/not exists | $eq, $ne, $in, $nin, $exists, $notexists |
| Boolean | is/is not, exists/not exists | $eq, $ne, $exists, $notexists |
Use a regex to match a substring; escape characters that should be interpreted literally. Indexed dimensions offer client-scoped value suggestions through /api/20260709/catalog/search, but typed values remain valid:
{
"dimension": "campaign_name",
"value_query": "brand",
"client_ids": [2617401],
"value_limit": 12
}
A query can repeat a field; AQL merges the constraints. A range and a case-insensitive contains filter serialize as:
"filters": [
{ "id": "client_id", "filter": { "$in": [2617401] } },
{ "id": "date", "filter": { "$gte": "2026-06-01", "$lte": "2026-06-30" } },
{ "id": "attributed_events.visits", "filter": { "$gte": 1000, "$lte": 5000 } },
{ "id": "campaign_name", "filter": { "$regex": "(?i).*brand.*" } }
]
Return Formats
| Format | Notes |
|---|---|
records | JSON-style row objects. |
rows | Row-oriented output. |
columnar | Column-oriented output. |
csv | Comma-separated text payload. |
txt | Text payload. |
xlsx | Spreadsheet payload. |
dataframe | DataFrame-oriented payload for connector workflows. |
arrow | Arrow binary payload. This is the Query Builder default; responses may include execution.result.table_preview for UI inspection. |
json | Alias for records. |
text | Alias for txt. |
Return Format Examples
Results are returned under execution.result. JSON formats expose the serialized report in data; text formats use text, and binary formats use base64. Read content_type, format, and byte_length before decoding. The examples below show illustrative results.
records or json
{
"format": "records",
"content_type": "application/json",
"byte_length": 256,
"data": {
"shape": { "format": "records", "offset": 0, "results": 1, "total": 7 },
"data": [{ "date": "2026-10-01", "spend": 1284.5 }],
"totals": { "spend": 8120.2 }
}
}
byte_length is illustrative. The report's data contains row objects for records,
row arrays for rows, or a map of column names to arrays for columnar.
The report also carries shape and requested totals and column metadata.
csv or txt
date,campaign_name,spend,revenue,roas
2026-06-01,Brand Search,1284.50,8120.20,6.32
Binary results
{
"format": "arrow",
"content_type": "application/vnd.apache.arrow.stream",
"byte_length": 1024,
"base64": "<encoded binary payload>",
"table_preview": {
"columns": ["date", "campaign_name", "spend", "revenue", "roas"],
"rows": [
{
"date": "2026-06-01",
"campaign_name": "Brand Search",
"spend": 1284.5,
"revenue": 8120.2,
"roas": 6.32
}
],
"row_count": 1,
"truncated": false
}
}
base64 contains the entire returned binary payload. Decode Arrow as an IPC stream and consume every record batch.
table_preview is only a display sample; it must not be used as the full report.
Arrow request
{
"query_source": "user_provided_query",
"wait_for_result": true,
"result_format": "arrow",
"analytics_query": {
"select": {
"dimensions": [
{
"id": "date"
}
],
"metrics": [
{
"id": "consumption.spend",
"as": "spend"
}
]
},
"filters": [
{
"id": "client_id",
"filter": {
"$in": [
"1234567"
]
}
},
{
"id": "date",
"filter": {
"$gte": "2026-10-01",
"$lte": "2026-10-07"
}
}
],
"shape": {
"limit": 100,
"offset": 0
},
"options": {
"totals": true,
"column_infos": true,
"column_desc": true
}
}
}
This version returns Arrow rows without the Admetrics totals and column-metadata document. Request records when you need those values in this version.
Totals, pagination and partial results
For JSON results, inspect the report under execution.result.data. shape.total is the authoritative
row count before pagination, shape.offset is the requested offset, and shape.results is the
returned count. Continue from the next offset while more rows remain. An empty page beyond the
last row does not mean the complete report is empty.
Request totals with analytics_query.options.totals: true. Totals include metric filters and
are calculated before pagination. Do not sum a page or average displayed ratios to reconstruct
them. If a KPI deliberately excludes table metric filters, submit a separate query with the
intended scope filters.
Request options.column_infos and options.column_desc independently. Column information
describes output identities, aliases, parameters, formatting and scaling. Inspect shape.missing
for incomplete metric evaluation; a null value alone does not prove a metric is missing or complete.
Apply display multipliers once. Records may apply the requested rounding precision; Arrow retains
typed numeric values. Preserve 64-bit grouping keys rather than converting them to lossy numbers.
Catalog and client metadata
Fetch the catalog to discover exact metric and dimension IDs and their supported parameters.
Use POST /api/20260709/catalog/search to narrow results by
question, or to find indexed values with dimension, value_query, and client_ids.
limit bounds catalog matches; value_limit defaults to 20 (maximum 50), and dimension_limit
defaults to 3 (maximum 5). Catalog visibility follows the caller's permissions.
Send concrete date bounds. A reporting date follows the source's reporting-day convention; it is not automatically a UTC timestamp or a guarantee that every source has the same timezone.
Usage and limits
A queued execution consumes one weighted execution unit. Raw request counts and execution
counts are reported separately. Protected requests can count as calls even when no execution starts.
Applicable account, credential and reporting-client limits all apply; a reporting client is an
Admetrics account, distinct from an OAuth application's client_id.
The default decoded-result limit is 64 MiB. Base64 adds roughly one third to a binary payload's size, and your HTTP client may impose a smaller limit. On an oversized-result error, reduce the page size and submit a new request. Reports are never silently truncated or replaced with preview rows. A new accepted submission is a new execution charge.
Troubleshooting Failed Requests
Most failures mean the request needs a more precise analytics contract. Treat these as recoverable integration states rather than generic server errors.
| What you see | What it means | What to do |
|---|---|---|
| The response asks for clarification. | AQL could not infer enough scope to build one clear query. | Add an explicit client, date range, metric, and grouping. For REST integrations, prefer exact catalog IDs over labels. |
| Validation failed. | The generated or supplied structured query contains an unsupported field, filter, parameter, or field combination. | Read validation.errors, replace guessed fields with IDs from Catalog Explorer or /api/20260709/catalog/search, then retry. |
| The run timed out. | The response deadline expired before the result was returned. | Use a larger timeout_ms within the selected mode's limit, or use queued execution for longer queries. |
The request returns 401. | The bearer token is missing, expired, invalid or revoked. | Refresh the token or authenticate again. |
The request returns 403. | The caller lacks access to the client or the requested execution mode. | Check client permissions and the applicable policy. |
| The response says the output format is unsupported. | The requested format or result_format is not available for this endpoint. | Use one of the supported formats listed above, such as records, csv, xlsx, or arrow. |
The request returns 413. | The result exceeds the payload limit. | Reduce page size and submit a new request. |
The request returns 429. | A request rate, capacity or execution-budget limit was reached. | Read the error code and honor Retry-After when present. A monthly budget requires an allowance change or the next period. |
The request returns 503. | Execution is unavailable for this request. | The selected mode may be disabled, or the service may be temporarily unavailable. An already accepted execution remains charged. |
Check the HTTP status and content type before decoding an error body. Gateways can return their
own error representation. Use bounded backoff with jitter for temporary errors and honor
Retry-After where provided. A timeout or disconnect does not guarantee cancellation.
