Endpoints
All sixteen paths of the Level API — reports and their data, campaigns, ad accounts, segments, organizations and metric catalogues, with paging, filters and caching.
Sixteen paths, every one a GET, all under https://api.marketing-bar.com/v1. The
canonical machine-readable version is GET /v1/openapi.json, rendered at
GET /v1/docs; this page is the map.
The whole surface
| Path | Scope | What it returns |
|---|---|---|
/v1/health | — | Liveness. No token needed. |
/v1/openapi.json | — | The OpenAPI document. |
/v1/docs | — | The rendered reference. |
/v1/me | any | This token's identity, plan, quota and allow-list. |
/v1/organizations | organizations:read | The organization this client reads, as a list of one. |
/v1/organizations/{id} | organizations:read | One organization. |
/v1/organizations/{id}/ad-accounts | ad-accounts:read | The ad accounts, cut by the allow-list. |
/v1/organizations/{id}/segments | segments:read | The segments. |
/v1/organizations/{id}/campaigns | campaigns:read | The campaigns, cut by the ad-account allow-list. |
/v1/organizations/{id}/metrics | reports:read | System metrics plus this organization's custom ones. |
/v1/metrics | reports:read | The system metric catalogue. |
/v1/reports | reports:read | The reports this client may read. |
/v1/reports/{id} | reports:read | One report's definition. |
/v1/reports/{id}/data | reports:data | The numbers. |
/v1/reports/{id}/export.xlsx | reports:export | The same rows as an Excel workbook. |
/v1/reports/{id}/export.csv | reports:export | The same rows as CSV. |
Paging
Every list endpoint takes page (from 1) and pageSize (1–200, default 50) and
answers in the same envelope:
{
"data": [ … ],
"page": 1,
"pageSize": 50,
"totalCount": 137,
"hasMore": true
}
A pageSize over 200 is refused with 400, not silently clamped — a job that asks
for 1 000 rows and quietly gets 200 is a job that loses data without noticing.
Reading a report
GET /v1/reports/{reportId}/data is the endpoint most integrations live on.
curl -H "Authorization: Bearer $TOKEN" \
"https://api.marketing-bar.com/v1/reports/$ID/data\
?dateFrom=2026-08-01&dateTo=2026-08-31&groupBy=campaign&metrics=cost,clicks,roas"
{
"reportId": "…",
"groupBy": "campaign",
"currency": "EUR",
"dateFrom": "2026-08-01",
"dateTo": "2026-08-31",
"metrics": ["cost", "clicks", "roas"],
"dataFreshness": "2026-09-12T09:00:00Z",
"rows": [
{
"dimensions": { "campaignId": "…", "campaignName": "Brand · Search" },
"metrics": { "cost": 1240.55, "clicks": 8123, "roas": 4.12 }
}
]
}
Every row has the same shape whatever the grouping — dimensions identify it,
metrics carry the numbers — so one parser handles all seven groupings.
Parameters
| Parameter | Notes |
|---|---|
dateFrom, dateTo | Inclusive. Required, except on a months report, which spans everything it holds. At most 400 days apart. |
groupBy | total, campaign, segment, month, ad-account, video or creative. Default total. A grouping the report can't answer is refused with a 400 naming the ones it can. |
metrics | Which metrics to return. Omit for every metric the report saves. Repeat the parameter or pass one comma-separated value. An unknown name is refused, not dropped. |
adAccountIds | Narrow to some of the report's ad accounts. Also one of the two inputs to what the call costs — see Quotas. |
segmentIds | Segment reports only. |
sort, sortDesc | Sort rows by a metric. Campaign, video and creative groupings only. |
nameFilter | Substring filter on the row's own name. |
The same parameters apply to export.xlsx and export.csv, which return the same
rows as a file.
Currency and freshness
currencyis the report's own. Money metrics arrive already converted into it — you never have to apply a rate yourself.metricsin the response lists what was actually returned. It can be shorter than what you asked for: conversion-derived metrics are absent on plans that don't include them.dataFreshnessis the newest successful platform fetch across this report's ad accounts, and it is also theLast-Modifiedheader.nullmeans nothing has ever loaded. It is the honest answer to "how old are these numbers?" — see Data sync.
Caching: ask again for free
Data responses carry a weak ETag. Send it back as If-None-Match and an unchanged
report answers 304 Not Modified — and the quota charge is refunded, so polling
a report that hasn't moved costs nothing.
curl -H "Authorization: Bearer $TOKEN" \
-H 'If-None-Match: W/"a1b2c3…"' \
"https://api.marketing-bar.com/v1/reports/$ID/data?dateFrom=…&dateTo=…"
This is the recommended shape for a job that runs every 15 minutes against a report that refreshes hourly.
Two rules that shape every response
- The allow-list is invisible, not forbidden. A report or ad account outside what
this client may read answers
404, exactly like one that doesn't exist. A403would confirm that somebody else's row exists. - A filter naming something out of reach returns an empty page, not an error —
?organizationId=for another organization,?adAccountId=for an unlisted cabinet.
Other endpoints worth knowing
/v1/organizations/{id}/campaignstakesadAccountIdandname(a case-insensitive substring). TheexternalIdfield on each campaign is the platform's own id — the join key for anything outside Level./v1/organizations/{id}/ad-accountstakesplatformto narrow to one network./v1/organizations/{id}/metricsdescribes what the metric names in report data mean: display name, kind (money,count,percent,ratio,decimal), unit, and whether it's a system metric. Fetch it once and cache it — it changes only when someone edits custom metrics.