Skip to main content

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 groupAcceptsNotes
/stats/*site_id or account_idEither works
/exports/*account_id onlysite_id is rejected
/batchaccount_id onlyUsed 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:

  1. The account_id matches the site slug exactly (see question above).
  2. date_from / date_to cover a period where conversions actually exist (verify with GET /stats/conversions).
  3. export_type is 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:

CodeMeaningAction
200CSV streamSave the body to a file
400Bad requestCheck account_id and date format
413Too large for streamingSwitch to POST /exports (background job)

Written and maintained by the Sealmetrics Team