Turn data into profit - programmatically.
One HTTP endpoint to query the metrics and dimensions in your Admetrics workspace, with supported attribution settings applied per metric. Pull ROAS, POAS, contribution margin and customer data into any tool, in real time.
Introduction
The Admetrics API is the data engine behind your dashboard. It exposes a single, expressive query interface: you describe the metrics and dimensions you want, the filters and date range to apply, and the shape of the response — Admetrics resolves attribution, blends ad-platform spend with shop revenue, and returns analysis-ready rows.
Composable queries
Mix any metrics and dimensions in one request. Group by date, campaign, product or customer — Admetrics handles the joins.
Attribution built in
Use the attribution settings your stack exposes and apply them per metric in the query payload.
Any output format
JSON records, columnar, CSV or XLSX. Queue a job, poll its status, then fetch the result when it's ready.
Base URLs & environments #
All requests go to the production host. Append the API path (for example /apiv3/queue_query) to the base URL.
/apiv3 endpoints for all new integrations.Authentication #
Admetrics authenticates every request with a JWT bearer token, sent in the Authorization header:
Option A — API key (recommended for integrations)
Contact Admetrics support to be issued a long-lived API token for your workspace. API keys are valid for two years and carry the permissions (allowed clients, metrics) of the account they are minted for. Treat the key as a secret.
Option B — Login & refresh (programmatic)
Exchange dashboard credentials for a short-lived access_token and a longer-lived refresh_token via POST /api/users/login. When the access token expires (≈1 hour), mint a new one with GET /api/users/refresh using the refresh token as the bearer.
login string required[email protected].password string requiredcurl -X POST 'https://app.admetrics.io/api/users/login' \
-H 'Content-Type: application/json' \
-d '{
"login": "[email protected]",
"password": "••••••••"
}'
import requests
r = requests.post(
"https://app.admetrics.io/api/users/login",
json={"login": "[email protected]",
"password": "••••••••"},
)
tokens = r.json()
access_token = tokens["access_token"]
const res = await fetch(
"https://app.admetrics.io/api/users/login",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
login: "[email protected]",
password: "••••••••",
}),
}
);
const { access_token } = await res.json();
{
"user": "[email protected]",
"host": "app.admetrics.io",
"proto": "https",
"url": "https://app.admetrics.io",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
}
curl -X GET 'https://app.admetrics.io/api/users/refresh' \
-H 'Authorization: Bearer <REFRESH_TOKEN>'
Quickstart #
Pull last month's spend, revenue and ROAS broken down by traffic source. Queries run asynchronously: you POST the job to /apiv3/queue_query, poll its status, then fetch the result.
If you need the final result in one round-trip, send the same payload to POST /apiv3/query. That synchronous endpoint is best for smaller server-side calls that can wait for completion.
- Choose your metrics (
spend,revenue,roas) and dimensions (traffic_source). - Add a date filter for the period, and set the response shape (here, JSON
records). - Submit to
queue_query— you get back astatus_urland aresult_url. - Poll
status_urluntildone_ok, then GETresult_url.
# 1 · submit the job
curl -X POST 'https://app.admetrics.io/apiv3/queue_query' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Accept-Language: en-US' \
-d '{
"query_id": "8e0abc8c-4d0e-152f-901c-e1fed3cb8da5",
"info": {
"stack_name": "chprod",
"request_source": "api|CLIENT_NAME"
},
"select": {
"dimensions": [{ "id": "traffic_source" }],
"metrics": [{ "id": "spend" }, { "id": "revenue" }, { "id": "roas" }]
},
"filters": [
{ "id": "date", "filter": { "$gte": "2026-05-01", "$lte": "2026-05-31" } }
],
"shape": { "format": "records", "limit": 100, "offset": 0 },
"options": { "aggregates": ["sum"] }
}'
# ← { "status_url": "…", "result_url": ".../apiv3/query_result/<ID>", … }
# 2 · fetch once status is done_ok
curl -H 'Authorization: Bearer <TOKEN>' \
'https://app.admetrics.io/apiv3/query_result/<RESULTS_ID>'
import requests, time, uuid
payload = {
"query_id": str(uuid.uuid4()),
"info": {
"stack_name": "chprod",
"request_source": "api|CLIENT_NAME",
},
"select": {
"dimensions": [{"id": "traffic_source"}],
"metrics": [{"id": "spend"}, {"id": "revenue"}, {"id": "roas"}],
},
"filters": [
{"id": "date", "filter": {"$gte": "2026-05-01", "$lte": "2026-05-31"}}
],
"shape": {"format": "records", "limit": 100, "offset": 0},
"options": {"aggregates": ["sum"]},
}
h = {"Authorization": f"Bearer {access_token}", "Accept-Language": "en-US"}
# 1) submit
job = requests.post("https://app.admetrics.io/apiv3/queue_query",
headers=h, json=payload).json()
# 2) poll until ready
while True:
st = requests.get(job["status_url"], headers=h).json()
if st["task_status"] in ("done_ok", "done_fail"):
break
time.sleep(1)
# 3) fetch the rows
rows = requests.get(job["result_url"], headers=h).json()
const payload = {
query_id: crypto.randomUUID(),
info: {
stack_name: "chprod",
request_source: "api|CLIENT_NAME",
},
select: {
dimensions: [{ id: "traffic_source" }],
metrics: [{ id: "spend" }, { id: "revenue" }, { id: "roas" }],
},
filters: [
{ id: "date", filter: { $gte: "2026-05-01", $lte: "2026-05-31" } },
],
shape: { format: "records", limit: 100, offset: 0 },
options: { aggregates: ["sum"] },
};
const h = { Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Language": "en-US" };
// 1) submit
const job = await (await fetch(
"https://app.admetrics.io/apiv3/queue_query",
{ method: "POST", headers: h, body: JSON.stringify(payload) }
)).json();
// 2) poll until ready
let st;
do {
await new Promise((r) => setTimeout(r, 1000));
st = await (await fetch(job.status_url, { headers: h })).json();
} while (!["done_ok", "done_fail"].includes(st.task_status));
// 3) fetch the rows
const rows = await (await fetch(job.result_url, { headers: h })).json();
{
"status": 200,
"api_version": "v3",
"query_id": "8e0abc8c-4d0e-152f-901c-e1fed3cb8da5",
"query_info": {
"stack_name": "chprod"
},
"shape": {
"format": "records",
"results": 3
},
"data": [
{ "traffic_source": "facebook", "spend": 48210.55, "revenue": 213880.10, "roas": 4.44, "index": 0 },
{ "traffic_source": "google", "spend": 31544.00, "revenue": 158992.74, "roas": 5.04, "index": 1 },
{ "traffic_source": "tiktok", "spend": 12087.21, "revenue": 39114.66, "roas": 3.24, "index": 2 }
]
}
Conventions #
Headers
Authorization string requiredBearer <token> — your API key or access token.Content-Type string requiredapplication/json for all POST bodies.Accept-Language string optionalen-US, de-DE) controlling number, currency and date formatting. Defaults to en-US.Data conventions
- Dates are ISO-8601 strings,
YYYY-MM-DD. Date ranges are typically half-open or inclusive depending on the operator you choose. - Currency & rate metrics are returned as numbers; formatting/precision is governed by
Accept-Languageandoptions.precision. - IDs for metrics and dimensions are lowercase identifiers.
stack_nameis the client's Admetrics stack identifier: fetch/api/shopify/activeclients, find yourclient_id, and copy itsstackvalue. - Idempotency: supply a UUID-v4
query_idper request. Identical payloads are cache-keyed, so a repeatedquery_id+payload may return a cached result.
Run a query #
Queries run asynchronously. Submit a job to queue_query; the API validates it, returns a set of URLs, and runs the query in the background. Then poll query_status until it's done_ok and fetch the rows from query_result.
The request body is a single JSON object — the query — composed of five blocks: select, filters, shape, options and info. The same payload also works with POST /apiv3/query if you want the result returned synchronously.
Lifecycle
Top-level fields
query_id string (uuid) recommendedselect object requiredfilters array optionalshape object optionaloptions object optionalinfo object optionalstack_name and request_source.{
"query_id": "8e0abc8c-4d0e-152f-901c-e1fed3cb8da5",
"info": {
"stack_name": "chprod",
"request_source": "api|CLIENT_NAME"
},
"select": {
"dimensions": [
{ "id": "month" },
{ "id": "campaign_name" }
],
"metrics": [
{ "id": "impressions" },
{ "id": "spend" },
{ "id": "revenue" },
{
"id": "roas",
"params": {
"attribution_model": "last_touch_non_direct",
"attribution_window": "30"
}
},
{ "id": "poas" },
{ "id": "cm2" }
]
},
"filters": [
{ "id": "date", "filter": { "$gte": "2026-01-01", "$lte": "2026-03-31" } },
{ "id": "client_id", "filter": { "$in": ["2133416"] } }
],
"shape": {
"format": "records",
"offset": 0,
"limit": 500
},
"options": {
"aggregates": ["sum"],
"noise_filter": false,
"totals": true,
"timeout": 25,
"iso8601": true,
"pretty": true
}
}
{
"status": 200,
"api_version": "v3",
"status_url": "https://app.admetrics.io/apiv3/query_status/2133416:86bba14a…",
"result_url": "https://app.admetrics.io/apiv3/query_result/2133416:86bba14a…"
}
Query payload schema #
Reference for every field across the five query blocks.
select — what to return
select.dimensions[] array<object>{ "id": "<dimension>" } with an optional params object. Order defines column order. Omit dimensions if you want a single aggregated row.select.metrics[] array<object>{ "id": "<metric>", "params": {…} } where params may carry context-driven values such as attribution_model, attribution_window, and horizon_days.filters — which rows
An array of filter objects. Use the canonical form { "id": "<field>", "filter": { "<op>": value } }. See operators.
shape — how to render
format enumrecords · columnar · rows · csv · xlsx · txt · arrow. Current server default is columnar, but new integrations should set it explicitly. See output formats.limit integer-1 (server-controlled / no explicit client cap).offset integer0.sort object | array{ "id": "spend", "direction": "desc", "output": "vals" }. Sorting by a dimension requires that dimension to also appear in select.dimensions.is_relative_date booleantrue to let the API generate the date range from relative_date_type and relative_date_delta instead of relying on explicit date boundaries.relative_date_type stringyear, quarter, month, isoWeek, today, yesterday, last-d-7, and last-m-6.relative_date_delta object{ years, quarters, months, weeks, days }. Use the matching key for the selected type, for example months with month, weeks with isoWeek, and days with last-d-7.date boundaries from your query and let the API expand the window for you.{
"shape": {
"format": "records",
"is_relative_date": true,
"relative_date_type": "month",
"relative_date_delta": {
"years": 0,
"quarters": 0,
"months": 1,
"weeks": 0,
"days": 0
}
}
}
"relative_date_type": "month" with "months": 0; yesterday = "relative_date_type": "yesterday" with all delta values set to 0; last 7 days = "relative_date_type": "last-d-7" with "days": 7.options — execution
aggregates array<string>["sum"]. Default [].totals booleantrue.noise_filter booleanfalse.timeout integer (s)0 uses the server default.lang stringAccept-Language.pretty booleanfalse.simple_colnames booleantrue.column_desc · column_infos booleanfalse.precision integercolumnar/rows/records. -1 = automatic.iso8601 booleantrue.csv_sep · csv_header · csv_decimal string · bool · string,, header row true, decimal mark .).info — context
stack_name string/api/shopify/activeclients, find the object for the client_id you want to query, and copy its stack value here. Example: if that response includes { "client_id": "2133416", "stack": "chprod" }, send "stack_name": "chprod".request_source stringapi|CLIENT_NAME so requests can be attributed consistently.Query status #
Non-blocking poll for an enqueued query. Returns the current task_status and an updates array that may be empty. Responds 404 if the results_id is unknown or expired.
results_id string (path) requiredqueue_query (the tail of status_url).{
"status": 200,
"api_version": "v3",
"query_id": "86bba14a-f8f9-11eb-af0b-98fa9b830118",
"task_status": "done_ok",
"updates": []
}
Query result #
Retrieve the finished result of an enqueued query. The body is rendered in the shape.format requested at enqueue time — JSON for records/columnar/rows, or a file download for csv/xlsx. The call blocks briefly if the result is still being finalized; a still-running query returns 504.
results_id string (path) requiredqueue_query (the tail of result_url).{
"status": 200,
"api_version": "v3",
"query_id": "8e0abc8c-4d0e-152f-901c-e1fed3cb8da5",
"query_info": {
"stack_name": "chprod"
},
"shape": {
"format": "records",
"results": 2
},
"data": [
{ "month": "2026-01", "campaign_name": "Spring – Prospecting", "spend": 84120.0, "roas": 3.91, "poas": 1.42, "index": 0 },
{ "month": "2026-02", "campaign_name": "Spring – Prospecting", "spend": 79330.5, "roas": 4.10, "poas": 1.55, "index": 1 }
]
}
Output formats #
Set shape.format to control how results are serialized.
| format | Content-Type | Shape |
|---|---|---|
records | application/json | JSON envelope with data as an array of objects — one object per row, keyed by column. |
columnar | application/json | JSON envelope with data as an object of arrays — one array per column. Current default when shape.format is omitted. |
rows | application/json | JSON envelope with data as an array of arrays. |
csv | text/csv | CSV download. Tune with csv_sep, csv_header, csv_decimal. |
xlsx | spreadsheetml.sheet | Excel workbook download. |
txt | text/plain | Fixed-width ASCII table — handy for quick CLI inspection. |
arrow | vnd.apache.arrow.stream | Apache Arrow IPC stream for high-throughput pipelines. |
Context #
Discover everything your stack supports: the full list of metrics, dimensions, attribution models, attribution windows, and locale formatting tokens — with display names and types. Use it to build dynamic UIs or validate IDs before querying.
keys string (query) requiredstack_name string (query) optionalstack value you got for that client from /api/shopify/activeclients and send in info.stack_name. If omitted, Admetrics resolves it from the request context or the token's stack restrictions when possible.Accept-Language string (header) optionalcurl -X GET \
'https://app.admetrics.io/apiv3/context?keys=dimensions,type_defs,metrics,attribution_models,attribution_windows,formats,horizon_days' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Accept-Language: en-GB'
{
"status": 200,
"api_version": "v3",
"data": {
"dimensions": {
"month": { "id": "month", "name": "Month", "type": "date", "index": true },
"traffic_source": { "id": "traffic_source", "name": "Traffic source", "type": "string" }
},
"type_defs": {
"currency": { "params": { "attribution_model": {}, "attribution_window": {} } }
},
"metrics": {
"spend": { "id": "spend", "name": "Spend", "type": "currency" },
"roas": { "id": "roas", "name": "ROAS", "type": "ratio", "display_formula": "{{revenue}}/{{spend}}" }
},
"formats": {
"percent": "#,##0.00%"
}
}
}
Dimension index #
Enumerate the distinct values of a dimension — for example, every campaign name or every client ID — with optional text filtering and row counts. Ideal for powering autocomplete and filter pickers. Use GET with query params for simple lookups, or POST a JSON body when you need to scope by other filters.
dimension string requiredcampaign_name, client_id, dow.like / filter string optionalfilters array optional (POST)client_id.tables / views string optionaloffset · limit integer optional0 / 100.{
"dimension": "dow",
"stack_name": "chprod",
"offset": 0,
"limit": 200,
"filters": [
{ "id": "client_id", "filter": { "$in": ["2133416"] } }
]
}
{
"status": 200,
"data": [
{ "id": "Monday", "count": 12345 },
{ "id": "Tuesday", "count": 11234 }
]
}
Metrics catalog #
A representative catalog of metric IDs you can request in select.metrics. Your stack may expose more or fewer — call GET /apiv3/context?keys=metrics for the authoritative list. Many metrics accept attribution parameters.
| Metric ID | Description |
|---|---|
| Spend & delivery | |
spend | Advertising spend (currency) |
base_spend | Spend excluding commission |
spend_share | Spend as a share of total |
impressions | Ad impressions |
clicks | Ad clicks |
cpm · cpc | Cost per mille / per click |
ctr · ctv | Click-through rate / click-to-visit rate |
video_views | Video views (with p25–p100 completion variants) |
reactions · engr | Reactions / engagement rate |
| Conversions & cost | |
conversions | Total conversions |
purchases | Platform-reported purchases (purchases_v1d, purchases_v7d) |
cvr · cvr_nc | Conversion rate / new-customer conversion rate |
cpo · cpl · cpatc | Cost per order / lead / add-to-cart |
cac · ncac · rcac | Customer / new-customer / reactivated acquisition cost |
add_to_cart_rate · cart_cvr | Add-to-cart and cart conversion rates |
| ROAS, MER & profitability | |
roas | Return on ad spend (with confidence intervals) |
roas_gross · roas_net | Gross / net ROAS (roas_nc, roas_net_nc for new customers) |
poas | Profit on ad spend (poas_nc, poas_ac) |
mer · amer | Marketing efficiency ratio / acquired MER |
mpr · ampr | Marketing profit ratio / acquired MPR |
acos · crr | Ad cost of sales / cost-revenue ratio |
| Revenue & orders | |
revenue · revenue_net | Gross / net revenue (revenue_nc, revenue_rc by cohort) |
orders · orders_net | Orders, net of cancellations/returns |
gross_sales · net_sales | Gross / net sales |
aov · aov_net | Average order value (aov_nc, aov_rc by cohort) |
aop | Average order profit |
quantity · subscriptions | Items sold / new subscriptions |
| Customers & retention | |
customers · new_customers | Total / new customers (repeat_customers, reactivated_customers) |
retention · repurchase | Retention and repurchase rates |
ncr · racr | New-customer rate / reactivation rate |
rpc · rpv | Revenue per customer / per visit |
| Contribution margin & costs | |
cm1 · cm2 · cm3 | Contribution margin 1–3 (cm2_nc, cm2pi variants) |
gross_profit · gross_margin | Gross profit / margin % |
cogs | Cost of goods sold (cogs_rate, returned_cogs) |
shipping · shipping_cost | Shipping revenue / cost |
tax · discounts | Tax collected / total discounts |
transaction_cost | Payment & transaction fees (base_fee, variable_fee) |
product_roas, product_cac), attribution-window (roas_click_v7d) and lifetime (ttc, tbo, opc) families are all available — discover them via context.Dimensions catalog #
Dimensions are the columns you group by in select.dimensions. A representative set is below; call context for your stack's full list.
| Dimension ID | Description |
|---|---|
| Time | |
date | Calendar date (YYYY-MM-DD) |
week · isoweek | Week / ISO week |
month · quarter · year | Month, quarter, year buckets |
dow | Day of week |
| Campaign & creative | |
campaign_id · campaign_name | Campaign (campaign_status, campaign_type) |
adset_name · ad_name | Ad set / ad (with _id and _status) |
account_name | Ad account |
traffic_source | Channel/source (channel_group, media_source) |
utm_source · utm_campaign | UTM parameters (utm_term, utm_content) |
ad_text · headline1 | Creative copy (ad_preview, description1) |
| Product & order | |
product · product_id | Product (product_vendor, product_type, sku) |
product_variant | Variant (product_variant_id) |
order_id · order_number | Order identifiers (lineitem_id) |
discount_code · discount_type | Discounts applied |
sales_channel · shop | Sales channel / shop (marketplace_platform) |
payment_gateway | Payment method |
| Customer & geography | |
customer_id · customer_type | Customer + New/Returning/Reactivated |
customer_order_index | Order sequence number |
billing_country | Billing country (shipping_country, shipping_city) |
device_category | Desktop / Mobile / Tablet (browser_name, os) |
| Account | |
client_id · client_name | Workspace / client |
market · exchange_name | Market region / ad exchange |
Attribution & metric parameters #
Many metrics accept a params object that tunes how conversions are attributed. Set them per metric inside select.metrics. Discover the allowed attribution values for your stack via context; the examples below are illustrative only.
attribution_model stringGET /apiv3/context?keys=attribution_models.
attribution_window stringGET /apiv3/context?keys=attribution_windows.
horizon_days stringGET /apiv3/context?keys=horizon_days.
lift booleanbaseline enumdebiased baseline for lift analysis."metrics": [
{
"id": "roas",
"params": {
"attribution_model": "last_touch_non_direct",
"attribution_window": "30",
"horizon_days": "90"
}
},
{
"id": "aov",
"params": { "attribution_model": "first_touch", "attribution_window": "7" }
}
]
keys=attribution_models,attribution_windows,horizon_days).Filters & operators #
Filters narrow the rows a query scans. The canonical form pairs a field id with an operator map, and may include a params object when a filter needs attribution-aware settings:
"filters": [
{ "id": "date", "filter": { "$gte": "2026-01-01", "$lte": "2026-03-31" } },
{ "id": "client_id", "filter": { "$in": ["2133416", "2119108"] } },
{ "id": "spend", "filter": { "$gt": 100000 } },
{ "id": "campaign_name", "filter": { "$regex": "Prospecting|Retargeting" } },
{ "id": "traffic_source", "filter": { "$eq": "facebook||google||tiktok" } }
]
| Operator | Meaning | Alias |
|---|---|---|
$eq · $ne | Equal / not equal — accepts || as OR (see below) | = · != |
$gt · $gte | Greater than / or equal | > · >= |
$lt · $lte | Less than / or equal | < · <= |
$in · $nin | In / not in a list | — |
$regex · $match | Regular-expression match (RE2, contains) | — |
$notregex · $notmatch | Negated regular-expression match | — |
$exists · $notexists | Field is / isn't null | — |
Regular expressions #
String dimensions can be matched with full RE2 regular expressions via $regex (alias $match) and its negation $notregex (alias $notmatch). Matching is partial — the pattern matches anywhere in the value (like REGEXP_CONTAINS), so anchor with ^…$ for an exact match and use the (?i) flag for case-insensitivity. Inside a pattern, | is the standard regex alternation (OR).
"filters": [
// contains "Prospecting" OR "Retargeting"
{ "id": "campaign_name", "filter": { "$regex": "Prospecting|Retargeting" } },
// exact, case-insensitive match of either seasonal sale
{ "id": "ad_name", "filter": { "$match": "(?i)^(spring|summer) sale$" } },
// exclude anything that looks like a test/draft campaign
{ "id": "campaign_name", "filter": { "$notregex": "(?i)test|draft|wip" } }
]
|| as OR in $eq / $ne. For quick multi-value matching you don't need a full regex — join values with || and $eq matches any of them, while $ne excludes all of them:
Errors & status codes #
Errors return a JSON envelope with a numeric status, a human-readable msg, and the api_version. The HTTP status mirrors status.
| Code | Meaning |
|---|---|
| 200 | Success. |
| 400 | Validation error — unknown metric/dimension, malformed filter, bad date. |
| 401 | Missing or invalid bearer token. |
| 404 | Unknown results_id or resource. |
| 500 | Internal error. |
| 504 | Result not ready yet — the job is still running. Keep polling query_status. |
{
"status": 400,
"msg": "Unknown metric id: 'roass'. Did you mean 'roas'?",
"api_version": "v3"
}
Limits & timeouts #
Execution timeout
Set options.timeout (seconds) to bound query execution. While a job is still running, query_result returns 504 — keep polling query_status until done_ok.
Pagination
Page with shape.limit and shape.offset. For very large exports prefer csv/xlsx/arrow over deep offset paging.
Health #
Lightweight liveness checks for monitoring.
GET /apiv3/health/api200 ok.GET /apiv3/health/jwt200 ok.curl 'https://app.admetrics.io/apiv3/health/api'
# → ok