# Query

Endpoints under /v1/query.

Source: https://sourceloop.ai/help/api/query/

---

| Endpoint | Method | Path | Permission |
| --- | --- | --- | --- |
| Run query | POST | `/v1/query` | any key |

Base URL: `https://app.sourceloop.ai/api/v1`

## Run query

`POST /v1/query`

Run any engine query

Requires: any valid API key.

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).

### Responses

- `200` Rows, columns, totals and meta
- `400` Something was wrong with the request or the credential.
- `429` Too 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.
- `503` The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds.
- `504` The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.

### Example (cURL)

```bash
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
    }
  }'
```

### Example (Node)

```javascript
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();
```

### Example (Python)

```python
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()
```
