Skip to main content
POST
Required scope: analytics:read This is the endpoint behind the Attribution board. Use it to count new customers per Meta ad set, rank campaigns by attributed revenue against their spend, or export credit by UTM source on a schedule. How attribution works explains how credit is assigned.

Breakdown dimensions

dimension decides what each row is. Each row also carries the platform IDs for its grain when the click had them. Names come from your connected ad platform when the click’s IDs match its data, otherwise from the UTM value on the click. A campaign with no matching platform data shows its raw ID or UTM value as the name. Rows with no value show (not set). The schema accepts other dimension values, but they return source-level rows, so stick to these eight.

Attribution model and window

  • first_click credits the earliest touchpoint in the window, and last_click the latest. Visits with no detectable source only win when the customer has no other visit in the window.
  • any_click credits every source the customer clicked, so rows add up to more than the total. Don’t sum them.
  • attributionWindow is how many days before the order (or the conversion event, with valueSource: events) to look for touchpoints. -1 means no limit.

Filtering results

filters takes the canonical filter tree. Two families of fields work here. To filter by campaign ID, put the ID in attribution.utm_campaign. The channel.* fields, such as channel.campaign_id, belong to the ad-listing endpoints and return 500 here. A value matches every row whose name or ID equals it, across sources, so add an attribution.utm_source condition when the same name appears in more than one channel.
The attribution.* fields only support exact matches today. Use equals, or in for a list. Other operators that List filter fields lists for these fields, such as not_equals or contains, are accepted but run as exact matches: not_equals "Meta Ads" returns only Meta Ads. attribution.utm_medium conditions are ignored, and so are attribution.* conditions inside a not group or inside an and that is nested in an or. Put conditions at the top level of an and, or in one or group under it.
orders.* conditions support their full operator set, but only with valueSource: orders. With valueSource: events they’re ignored.

Order settings aren’t applied for you

The web app applies your saved order settings (pending and voided orders, refund dating, exclusion filters) to attribution. This endpoint only uses the orderSettings you send. Without them, pending and voided orders count and nothing is excluded. To match the board, read them with Get order settings and pass the returned object as orderSettings. Order settings only apply with valueSource: orders.

Reading the response

  • results has one entry with dateRange, dimension, the filter you sent, data.primary, and measureDetails.
  • data.primary holds one row per dimension value, sorted by orders.count (or core.conversions with valueSource: events), highest first. row_number gives the position.
  • Each row carries every measure for the value source, keyed by measure id. valueSource: orders returns orders.* measures (orders, new customers, revenue, margins). valueSource: events returns core.conversions, core.unique_converters, core.conversion_value, core.average_conversion_value, core.cpa, and core.roas. Both add channel.* spend and click measures and tracked.* visitor counts. measureDetails[].measure lists every measure id the rows carry, with its total.
  • Values come back as numeric strings (for example "66930.1000"). A value of zero comes back as null. Parse both before doing math.
  • Each row carries utm_source, the column named by dimension, and the ID columns in the table above. utm_adset and utm_ad appear on every row, but only hold the row’s own ad set or ad at those two grains, so ignore them elsewhere.

Errors and edge cases

  • A malformed filters tree returns 400 with the reason in errors.
  • A filter field this endpoint doesn’t accept, an unknown field, or an operator the field doesn’t list returns 500 with the reason in message, for example Filter field "channel.campaign_id" is not valid for context "attribution".
  • When the query itself fails, the response is 200 with an empty body and no results. Treat that as “no answer”, not “no conversions”.
  • Ad set and ad breakdowns over long ranges can time out with 502. Shorten the date range and retry.
  • Without timezone, dates are read as UTC days. dateRange.end is inclusive.
  • currency is case-insensitive. A code that isn’t three letters falls back to USD without an error. See Reporting currency.
  • pixelId in the body must equal the x-pixel-id header, or the request returns 403.

Authorizations

x-api-key
string
header
required

Your Upstack API key. Starts with upstack_.

x-pixel-id
string
header
required

The pixel id the request targets.

Body

application/json
pixelId
string
required

Must match the x-pixel-id header.

dimension
enum<string>
required

What each row attributes credit to. utm_adset and utm_ad return ad set and ad rows with platform names and IDs.

Available options:
utm_source,
utm_medium,
utm_campaign,
utm_source_campaign,
utm_adset,
utm_term,
utm_ad,
utm_content
Example:

"utm_campaign"

dateRange
object
required

Orders (or conversion events) placed in this range are attributed. Touchpoints can fall before start, up to the window.

attributionModel
enum<string>
required

Which touchpoint gets credit for a conversion.

Available options:
first_click,
last_click,
any_click
attributionWindow
enum<integer>
required

How many days before each conversion to look for touchpoints. -1 means no limit.

Available options:
1,
7,
14,
30,
60,
90,
-1
filters
All of (and) · object

Narrow the rows with attribution.utm_* fields (exact match on name or platform ID) and orders.* fields. Only equals and in behave as written on attribution.* fields.

timezone
string

IANA timezone (e.g. America/New_York) used to read dateRange as local days. Omit for UTC.

Example:

"America/New_York"

currency
string
default:USD

ISO 4217 code to convert money measures to (e.g. EUR). Each amount converts at the rate for the day it occurred. Defaults to USD when omitted, so if your store sells in another currency, pass this explicitly.

valueSource
enum<string>
default:orders

What counts as a conversion. orders uses store orders and returns orders.* measures. events uses the tracked event named in conversionEvent and returns core.* conversion measures.

Available options:
orders,
events
conversionEvent
string
default:purchase

Tracked event name to count as the conversion, in snake_case (e.g. lead). Required when valueSource is events. Ignored for orders.

orderSettings
object

Order settings to apply to this query. The query endpoints don't read the account's saved order settings, so pass them here to match the web app's numbers. The orderSettings object returned by Get order settings can be passed as-is.

Response

Attribution results.

results is absent when the attribution query couldn't be run — the response is then an empty object with a 200. Treat a missing results as "no answer", not as "no conversions": an empty results array is the real no-data case.

results
object[]