Query
Endpoints under /v1/query. REST endpoints, parameters, and response schemas for the SourceLoop attribution API.
| Endpoint | Method | Path |
|---|---|---|
| Run query Run any engine query | POST | /v1/query |
Base URL https://app.sourceloop.ai/api/v1
Run query
Run any engine query
POST /v1/query
Any valid API key. No extra scope needed.
The engine behind the dashboards, docs and MCP: one vocabulary, one set of answers. Send `{ "query": {...} }` for one query or `{ "queries": [{ "id", "query" }] }` for up to 24.
kind `metrics`: `metrics` (each `{ id, as?, events?, types?, stages?, models? }` or `{ formula, as }`), `groupBy` (up to 4), `granularity` (hour to quarter), `compare` (`previous_period`, `previous_year`), `filters` (strings `dim:op:value` or an and/or tree), `sort`, `limit`, `others`. Other kinds: `paths`, `products`, `funnel`, `records`.
Ad platform metrics are ordinary metrics: `spend`, `impressions`, `clicks`, `ctr`, `cpc`, `cpm`, `platform_conversions`, `platform_roas`, and `cost_per_conversion` and `roas`, which take `models` like `conversions` and `revenue` do. Break them down by an ad dimension (`ad_platform`, `ad_campaign`, `ad_set`, `ad`, `keyword`, `ad_search_term`, ...). Rows of an ad dimension show its name, with the platform id in `<dimension>_id`. Spend is converted to the workspace currency at each day's exchange rate: a converted column lists its source currencies in `convertedFrom` and `meta.notes` says so. A day with no published rate stays in the ad account currency, which the column then names in `currency`, and `meta.notes` says when a ratio across currencies cannot be computed.
**Limits.** At most 1,000 rows per query (`limit`, default 50); `meta.truncated` is true when more existed. A window of at most 400 days. At most four `groupBy` dimensions, of which one ad dimension. Ad, cost and profit metrics by day, week, month or quarter, never by hour. Conversions and revenue grouped by an ad dimension need `models`; spend alone needs none. For every ad row by day, page through `GET /ads/stats` instead.
Every answer carries `columns` (what each key means), window `totals` (unique counts are never summed from rows) and `meta` (window, timezone, routes, notes, truncated).
curl -s -X POST "https://app.sourceloop.ai/api/v1/query" \
-H "Authorization: Bearer $SOURCELOOP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": {
"kind": "metrics",
"window": {
"period": "last 30 days"
},
"groupBy": [
"ad_campaign"
],
"metrics": [
{
"id": "spend"
},
{
"id": "conversions",
"models": [
"last_non_direct"
]
},
{
"id": "roas",
"models": [
"last_non_direct"
]
}
],
"sort": [
{
"by": "spend",
"dir": "desc"
}
],
"limit": 100
}
}' const res = await fetch(
"https://app.sourceloop.ai/api/v1/query",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"query": {
"kind": "metrics",
"window": {
"period": "last 30 days"
},
"groupBy": [
"ad_campaign"
],
"metrics": [
{
"id": "spend"
},
{
"id": "conversions",
"models": [
"last_non_direct"
]
},
{
"id": "roas",
"models": [
"last_non_direct"
]
}
],
"sort": [
{
"by": "spend",
"dir": "desc"
}
],
"limit": 100
}
}),
},
);
const data = await res.json(); import os, requests
res = requests.post(
"https://app.sourceloop.ai/api/v1/query",
headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
json={
"query": {
"kind": "metrics",
"window": {
"period": "last 30 days"
},
"groupBy": [
"ad_campaign"
],
"metrics": [
{
"id": "spend"
},
{
"id": "conversions",
"models": [
"last_non_direct"
]
},
{
"id": "roas",
"models": [
"last_non_direct"
]
}
],
"sort": [
{
"by": "spend",
"dir": "desc"
}
],
"limit": 100
}
},
)
data = res.json() Responses
-
200Rows, columns, totals and meta -
400Something was wrong with the request or the credential. -
429Too many requests for this workspace in the current minute (`rate-limit-exceeded`), the monthly allowance used (`quota-exceeded`), or too many analytics queries running at once (`too-many-concurrent`). Wait Retry-After seconds and repeat the request unchanged. -
503The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds. -
504The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.