# Public API reference (REST, /v1)

URL: https://traqio.app/docs/api
Last updated: 2026-10-14

Query ROAS, cohorts, revenue and tracking links from your code with Traqio's versioned REST API: one scoped API key, an OpenAPI document, curl examples.

Traqio's **public API** is a versioned REST API, under `/v1`, over the same reports as the console: ROAS by campaign, ad set and ad, daily ROAS, install cohorts, revenue, refused data, connector health and tracking links. The same operations are available as tools to Claude, Cursor and other MCP clients through the [MCP server](https://traqio.app/docs/api/mcp). Both are authenticated by one scoped API key.

## Authentication

Create a key in the console under **Settings → API keys**, tick its scopes, and send it as a bearer token. Keys start with `tq_live_` and are shown once — Traqio stores only a hash and the key's visible prefix. A console login token is refused by the API, and an API key is refused by the console.

```bash
export TRAQIO_API_KEY=tq_live_…

# 1. Which apps do I have? Every report takes a productId.
curl -s "https://api.traqio.app/v1/products" -H "Authorization: Bearer $TRAQIO_API_KEY"

# 2. Which ads made money in September? (`to` is exclusive)
curl -s -G "https://api.traqio.app/v1/reports/roas" -H "Authorization: Bearer $TRAQIO_API_KEY" \
  -d productId=app_… -d from=2026-09-01 -d to=2026-10-01 -d level=ad

# 3. Is the spend pull healthy, or is every ROAS stale?
curl -s "https://api.traqio.app/v1/reports/connectors" -H "Authorization: Bearer $TRAQIO_API_KEY"
```

### Scopes

| Scope | Allows |
| --- | --- |
| reports:read | GET /v1/products and every GET /v1/reports route, and the matching MCP tools. |
| reports:write | POST /v1/reports/sync-spend — trigger a spend pull or a backfill. |
| links:read | List an app's tracking links. |
| links:write | Create tracking links. |
| privacy:write | Erase a user's data (GDPR) — irreversible. Only owners and admins can grant it. |

No scope implies another — `links:write` does not grant `links:read` — and a key can never do more than the role of the member who created it. Revoking a key takes effect at its next call.

## Endpoints

| Method | Path | Scope | Parameters | Returns |
| --- | --- | --- | --- | --- |
| GET | /v1/products | reports:read | — | Your apps: id, name, store ids. Never the SDK keys. |
| GET | /v1/reports/roas | reports:read | productId, from, to, store?, level? | ROAS by campaign, ad set or ad (level=campaign|adset|ad): spend, attributed revenue, installs. |
| GET | /v1/reports/roas-daily | reports:read | productId, from, to, store? | Spend, revenue and ROAS per day. |
| GET | /v1/reports/cohorts | reports:read | productId, from, to, store? | Install-day cohorts: installs, day-0 revenue, total revenue. |
| GET | /v1/reports/revenue | reports:read | productId, from, to | All revenue per currency, split into attributed, organic and unmatched. |
| GET | /v1/reports/rejections | reports:read | productId, from, to | What Traqio refused — rate ceiling, fraud rules — by reason and route. |
| GET | /v1/reports/connectors | reports:read | — | Your ad-network connections, with their last sync and last error. |
| POST | /v1/reports/sync-spend | reports:write | date | from, to | Pull spend now for one day (date=) or a range (from=, to=). Safe to call twice. |
| GET | /v1/links | links:read | productId | One app's tracking links, newest first (at most 200). |
| POST | /v1/links | links:write | { … } (JSON) | Create a tracking link; returns its URL. |
| POST | /v1/privacy/erasure | privacy:write | { productId, deviceId?, appUserId?, confirm: true } (JSON) | Erase everything about a device id and/or user id in one app; returns the erasure receipt. Irreversible. |

The complete machine-readable reference is unauthenticated, at [https://api.traqio.app/v1/openapi.json](https://api.traqio.app/v1/openapi.json) (OpenAPI 3.1). It is generated from the same description the routes are tested against, so it cannot describe an endpoint that does not exist.

## Conventions

- **Dates** are `YYYY-MM-DD` in UTC. `from` is inclusive and `to` is **exclusive**: `from=2026-09-01&to=2026-10-01` is September.
- Report routes take `productId`, `from` and `to`; `store=ios|android` is optional and both stores are combined without it.
- **Money** is never summed across currencies: each currency is its own row, and spend is in each ad account's billing currency.
- `POST /v1/reports/sync-spend` is safe to call twice: a day is overwritten, never doubled — which is also how a network's restatement of recent spend lands.

## Creating a tracking link

```bash
curl -s -X POST "https://api.traqio.app/v1/links" \
  -H "Authorization: Bearer $TRAQIO_API_KEY" -H "content-type: application/json" \
  -d '{
    "productId": "app_…",
    "label": "Meta / autumn / video B",
    "network": "meta",
    "campaignExtId": "1203…",
    "code": "autumn-b",
    "deepLink": "/offers/autumn"
  }'
```

| Field | Meaning |
| --- | --- |
| productId | The app the link points at, from GET /v1/products. |
| label? | Your name for it, e.g. “Meta / summer / video-A”. |
| network? | meta, tiktok or other. |
| source? | Free-text channel, when the network is other. |
| campaignExtId? | The network's own campaign id — what joins the link to spend. |
| adsetExtId? | The network's own ad set id. |
| adExtId? | The network's own ad id. |
| code? | The slug, /c/<code>: 1–50 of A–Z a–z 0–9 - _. Random when omitted; 409 when taken. |
| deepLink? | Where the app lands: an in-app path or an app URL. javascript:, data: and similar are refused. |
| iosUrl? | iOS destination override, https only. Defaults to the app's App Store page. |
| androidUrl? | Android destination override, https only. Defaults to the Play listing. |
| webUrl? | A page of your website, https only. When set, every device lands there with the trackID (traq_click) in the query string. |

See [Tracking links](https://traqio.app/docs/concepts/tracking-links) for what each field does once the link is live.

## Errors

| Status | Means |
| --- | --- |
| 400 | Malformed request: an unparseable date, a missing productId, a level that is not campaign, adset or ad. |
| 401 | No key, a malformed, revoked or expired key, or a console login token. The message says which. |
| 403 | The key lacks the scope (the message names it), or the productId belongs to another workspace. |
| 409 | POST /v1/links: the slug is already taken. |

Errors are JSON, with a human-readable `message`.

## Frequently asked questions

**Is the API included in every plan?**
Yes. The API and the MCP server are not gated by plan: every account can create API keys.

**Can an API key create other keys or change billing?**
No. A key reads reports and, with the matching write scopes, triggers a spend pull, mints tracking links or erases a user's data on request. Managing keys, credentials, members or billing always needs a signed-in person.

**What happens to a key when its creator leaves the team?**
Nothing: a key belongs to the workspace, not to the person who created it, so an integration does not break when they leave. Revoke the keys you no longer want from the console; each shows when it was last used, and a revoked key is refused at its next call.

**Will the API change under my integration?**
Not within /v1. Additive changes — a new endpoint, a new field in a response — happen inside /v1; anything that could break a working integration gets /v2, and /v1 keeps answering.
