API FAQ
Quick answers to the questions integrators ask most. Each entry is self-contained and links to the canonical reference.
How do I get event-level conversions (one row per conversion)?
Use GET /api/v1/stats/conversions/raw. It returns one row per conversion with all dimensions: UTM parameters, country, device, channel group, custom properties, and timestamps.
Constraints:
- Maximum date range: 31 days
- Maximum
page_size: 10,000
curl "https://my.sealmetrics.com/api/v1/stats/conversions/raw?site_id=YOUR_SITE&start_date=2026-04-01&end_date=2026-04-30&page_size=1000" \
-H "X-API-Key: sm_your_api_key"
For aggregated conversion totals (count, revenue, avg value per conversion type), use /stats/conversions instead. For bulk dumps to file, use /exports/stream with export_type: "conversions".
How do I get event-level microconversions (with their custom properties)?
Use GET /api/v1/stats/microconversions/raw — same shape as /stats/conversions/raw but without amount / clid. Each row carries the full properties Map so you can filter or count by combinations of property values (e.g. hotel + checkin_date + duration + country) client-side.
If you only need to break down by one property at a time and want server-side aggregation, use /stats/properties/breakdown instead.
For per-item attribution on conversions (one row per product in a purchase, joinable by timestamp_utc or a shared property like order_id), use GET /api/v1/stats/conversion-items/raw.
Both endpoints share the same constraints as /stats/conversions/raw (31-day max range, page_size up to 10,000, 2-year ClickHouse TTL).
My conversion rows have UTMs twice — at the root and inside properties. Which one is real?
The root-level ones. utm_source, utm_medium, utm_campaign, utm_term and utm_content at the root of the row are the attribution Sealmetrics resolved for that conversion. Analyse those.
UTM keys inside properties are values your own tracking code sent along with the conversion call. Sealmetrics stores them verbatim, like any other custom property, and never writes into properties itself. There is no hierarchy between the two blocks and they do not represent different moments in the attribution — one is our answer, the other is your input.
They can disagree, and that is usually informative:
{
"utm_source": "Direct",
"utm_medium": "Direct",
"properties": { "utm_source": "", "utm_medium": "" }
}
The page sent empty UTMs; Sealmetrics resolved the visit as Direct. Reading properties.utm_source would hand you a blank where the answer is "Direct".
If the duplication is noise for your pipeline, stop passing UTMs in the conversion call — attribution is captured independently of what you send.
What value do I pass for account_id? My site_id is being rejected.
Both parameters refer to the same value: the site slug (the identifier shown in the dashboard URL, e.g. acme in my.sealmetrics.com/acme/...).
The naming differs by route:
| Route group | Accepts | Notes |
|---|---|---|
/stats/* | site_id or account_id | Either works |
/exports/* | account_id only | site_id is rejected |
/batch | account_id only | Used inside the params of each query |
If you got a 400 on /exports/* with site_id, switch the parameter name to account_id and keep the same value.
Why does /exports/stream return an empty file (or used to return 403)?
If you saw 403 with an API key on /exports/* or /batch: that was a permission bug — those routes were checking for a scope that API keys don't carry. It's fixed and deployed. Your existing API key now works without changes.
If you're still getting an empty file, check:
- The
account_idmatches the site slug exactly (see question above). date_from/date_tocover a period where conversions actually exist (verify withGET /stats/conversions).export_typeis one of the values listed in the Export Types table.
Do JWT tokens issued by /auth/token inherit all scopes? Do I need to request extras?
Scopes are assigned automatically based on the user's role — you don't request them.
A standard user has read + write, which already covers every endpoint integrators typically need:
- All
/stats/* - All
/exports/* /batch- All
/sites/*
No extra scope parameters in the /auth/token request body. If you hit a 403 with a valid JWT, the issue is the user's role, not missing scopes — check the role in the dashboard or use an API key bound to the site.
See Authentication → JWT scopes for the full role → scope mapping.
Working curl example for /exports/stream with conversions
curl -X POST "https://my.sealmetrics.com/api/v1/exports/stream?account_id=YOUR_SITE" \
-H "X-API-Key: sm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"export_type": "conversions",
"format": "csv",
"date_from": "2026-04-01",
"date_to": "2026-04-30"
}'
The response is a CSV stream with the header date, conversion_type, amount, properties.
If you need additional dimensions (UTMs, country, device, channel group), use /stats/conversions/raw instead — see the first question on this page.
Response codes you may see:
| Code | Meaning | Action |
|---|---|---|
| 200 | CSV stream | Save the body to a file |
| 400 | Bad request | Check account_id and date format |
| 413 | Too large for streaming | Switch to POST /exports (background job) |