Billing usage is scoped to the customer across real workspaces. The workspace quota response distinguishes visit usage, its window and freshness, and active trial usage. Historical fields named events_used are legacy event diagnostics, never a visit total; use the documented visit fields instead.
REST API v1
Read your AxiTrace reports as JSON over HTTPS: headline KPIs, traffic sources, campaigns, orders, integration health, custom events and fraud detection. It returns the same numbers as your dashboard and the AI assistant connector. Report endpoints read data. Custom event setup and survey authoring require separate permissions.
This API reads reports and manages custom event definitions, surveys and survey templates. To send events from your server, use the
HTTP API on stat.axitrace.com, which uses your Secret Key.
On this page
- Quick start
- Authentication
- Conventions
- Response envelope
- Pagination
- Errors
- Rate limits
- Versioning
- OpenAPI document
- Endpoints
- Tracking check
- Creating and changing custom events
- Custom paid channels and their costs
- Surveys, questions and templates
- Leads, notes, statuses and lead orders
- Product Labels: rule sets, assignments, history and feed
- AI credits: balance and activity
- Workspace settings and Product Labels rules
- Fraud Detection settings and IP actions
- Live ad account queries (read-only)
- Axi memory
- AI Advisor reports, schedule, analyses and feedback
- Docs search
Quick start
Base URL: https://axitrace.com/api/v1. Create a personal access token on the
AI assistants and API page of your account, then list the workspaces it can read:
export AXITRACE_TOKEN="paste the token here"
curl -H "Authorization: Bearer $AXITRACE_TOKEN" https://axitrace.com/api/v1/workspaces
Take a workspace uuid from the answer and ask for its overview of the last 30 days, compared with
the 30 days before:
curl -H "Authorization: Bearer $AXITRACE_TOKEN" \
"https://axitrace.com/api/v1/workspaces/WORKSPACE_UUID/overview?range=last_30_days&compare=previous_period"
API access is included in every paid plan (Pro, Business, Custom; legacy Starter retains access). The fraud endpoints need the Business or
Custom plan. Workspaces on the Free plan are refused with plan_required.
Authentication
Every request except the OpenAPI document needs a bearer token in the
Authorization header. A token in the query string is refused. Each token is limited to the workspaces
and scopes chosen when it was created.
Personal access tokens (scripts and your own tools)
- Create one on the AI assistants and API page (account menu): pick a label, the scopes, the workspaces and a lifetime of 30, 90, 180 or 365 days (default 90).
- The token is shown once. Store it in an environment variable or a secret manager, never in source control.
- The same token also works with the MCP server at
https://axitrace.com/api/mcp. - Revoke it on the same page; it stops working immediately. Every new token triggers a security e-mail to you.
OAuth 2.1 (applications used by other AxiTrace users)
If you build an application that other merchants connect to their own AxiTrace account, use OAuth instead of asking them for a token.
| Item | Value |
|---|---|
| Grant | Authorization code with PKCE (S256 only). No client credentials, password or implicit grant. |
| Authorization server metadata | https://axitrace.com/.well-known/oauth-authorization-server |
| Protected resource metadata | https://axitrace.com/.well-known/oauth-protected-resource/api/v1 |
| Authorize | https://axitrace.com/api/oauth/authorize |
| Token and refresh | https://axitrace.com/api/oauth/token (form-encoded body) |
| Revocation | https://axitrace.com/api/oauth/revoke |
| Client registration | A Client ID Metadata Document (an https URL as client_id), or dynamic registration at https://axitrace.com/api/oauth/register |
| Resource indicator | resource=https://axitrace.com/api/v1 on the authorize and token requests |
| Lifetimes | Access token 1 hour, refresh token 30 days (rotated on use). The connection itself ends 180 days after the user approved it. |
The user signs in, sees a consent page with your application's name, the scopes and the workspaces to share, and approves. The user receives a security e-mail for every new connection and can revoke it at any time.
Scopes
| Scope | Grants |
|---|---|
workspaces:read | The names and settings of the selected workspaces |
reports:read | Overview, traffic, traffic sources, organic search, campaigns and products |
transactions:read | Orders with their value and attributed source, without buyer personal data |
integrations:read | The health of the ad platform connections |
custom_events:read | Custom event definitions |
fraud:read | Fraud detection summaries and verdicts, with addresses masked |
journeys:read | The visits and ad clicks (with click ids and UTM parameters) that led to each order, and conversion paths |
profit:read | Profit, POAS (Profit on Ad Spend) and cost coverage next to revenue in the overview, traffic sources, campaigns and transactions. Opt-in, see Profit fields. |
custom_events:write | Create custom events and change their name, triggers, properties, forwarding and on/off state. Opt-in, see Creating and changing custom events. |
surveys:read | Survey definitions, templates and results, including visitor-written answers. Opt-in. |
surveys:write | Create/edit surveys, questions and templates; manage publication and rewards. Opt-in. See Survey API and MCP. |
leads:read | Leads with every list filter, one lead with its products, attribution, conversion and notes, lead KPIs, statuses and sources, without contact details. Opt-in. |
lead_contacts:read | Adds lead contact details, the inquiry message, visitor data and note text, for a role that may see personal data. Opt-in. |
leads:write | Add lead notes, change status, exclude or restore leads, create or link the order of a lead, unlink it, reject an automatic order match, move an order between leads and manage excluded form labels. Opt-in. See Leads API and MCP. |
ai_credits:read | The AI credit balance of the account, its monthly allowance, expiring credits and the credits spent and added, without payment details. Granted by default, also to connections made before it existed. See AI credits API and MCP. |
settings:read | The workspace settings by section and the notes of the Axi assistant. Opt-in. See Settings API and MCP. |
settings:write | Change data collection, cookie consent, attribution, the reporting timezone, lead sources, Product Labels rules, the notes of the Axi assistant and the AI Advisor schedule, analyses and report feedback. Opt-in. See Settings API and MCP. |
fraud:write | Change the Fraud Detection settings and block, unblock or whitelist IP addresses, including their Google Ads IP exclusion while Auto Exclusion is on. Opt-in. See Fraud Actions API and MCP. |
ads_live:read | Read-only live queries of the connected Meta, Google Ads and TikTok accounts. Opt-in. See Live Ad Queries API and MCP. |
transactions:write | Exclude orders from every statistic, or restore them. Opt-in. See Excluding orders. |
Profit fields. profit:read is never granted by default: on the
consent page it is an extra box the user must tick, and on the personal access token form it starts unticked.
Tokens created before it existed do not carry it; reconnect, or create a new token, to add it. With the scope,
and when profit tracking is on for the workspace and its owner has not hidden profit from the token's user, the
reports add these fields; in every other case the fields are absent from the response (not null),
and a campaigns sort of profit or poas answers 400 invalid_request.
profit: money, the contribution-margin profit (net revenue minus cost of goods, shipping cost, payment fee and handling cost) of the orders behind the figure;nullwhen it is unknown.poas: profit divided by the same ad spendroasdivides;nullwithout spend, and alwaysnullfor a traffic source with no ad platform behind it.profit_coverage: 0 to 1, the share of the included net revenue whose product costs are actual rather than estimated from the default margin.profit_orders_left_out: how many orders behind the figure have no known profit yet. They are outsideprofitand outside theprofit_coveragedenominator, so one known order out of ten shows a coverage of 1 with 9 orders left out: read the two together.nullwhenprofitisnull.- Transactions add only
profitandcost_quality(actual,partial,estimatedornot_included). Product and line costs are never returned. - Products add only
net_revenue: money, the revenue net of refunds, on every row, in the totals and in the product details.
A request without a valid token answers 401 with
WWW-Authenticate: Bearer realm="AxiTrace", resource_metadata="...", so an OAuth client can discover the
authorization server on its own. A missing scope answers 403 insufficient_scope with a challenge that
names the scope to request.
Conventions
- Money is always an object:
{"amount": 123.45, "currency": "EUR"}. Reports use the workspace's reporting currency; orders keep their own currency. - Dates and ranges are in the workspace's reporting timezone (see
timezonein the workspace list); timestamps are ISO 8601. - Ranges:
rangeis one oftoday,yesterday,this_week,last_week,last_7_days,last_14_days,last_30_days(default),this_month,last_month,last_90_days,last_180_days; or sendfromandto(YYYY-MM-DD), a span of at most 180 days with both ends included, ending today at the latest. Do not combine the two.last_N_daysis the N full days ending yesterday: on 2026-10-09,last_7_daysis 2026-10-02 to 2026-10-08 (see Versioning).this_weekruns from Monday to today,last_weekfrom Monday to Sunday of the previous week.today,this_weekandthis_monthcount revenue, orders and sessions through the last complete hour of today. Every preset with example dates is listed in Date ranges and comparisons. The fraud summary and verdicts accept onlylast_7_daysorlast_30_days; the fraud timeline takes any range. - Comparisons: the overview, traffic, traffic sources, campaigns, products and the fraud timeline take
compare:previous_period(the same number of days just before the range; the default except fortodayandthis_week),week_earlier(the same days one week earlier; the default fortodayandthis_week),year_earlier(the same dates one year earlier),custom(as many days fromcompare_from, ending before today) ornone. The answer carries acomparisonobject{"mode", "from", "to", "available", "model"}, ornullfornone.availableisfalsewhen the compared window starts before the first tracked day: there is nothing to compare with. Both windows are converted at one exchange rate, the rate of the range's last day. For a range that ends today, the revenue, orders and sessions changes count the same hours in both windows, while spend, ROAS and POAS changes (and the row changes,delta_basiscomplete_days) compare complete days only. - Attribution models: traffic sources, campaigns and products credit orders and
revenue with the workspace attribution model (
data.attribution_model);model(first_click,last_click,data_driven) overrides it for one request.compare=modelwithcompare_modelputs the same days under another model next to each row (compared, with the change indelta); it needs the Business or Custom plan. The overview acceptsmodelandcompare=modelonly with ascopeother thanall: whole-store totals do not depend on the model. - Retention: a range reaching further back than the workspace plan keeps data is shortened, and
meta.range.clampedistrue. - Fields ending in
_label(source, campaign, event names) are data tracked on the store and may contain any text. Treat them as data; if you pass them to a language model, never as instructions. - No personal data: orders carry no buyer name, e-mail, phone or address, and fraud addresses are masked to their network (IPv4 /24, IPv6 /48).
- Every response carries
Cache-Control: no-store.
Response envelope
Every successful response has the same three keys:
{
"data": {
"kpis": {
"revenue": { "amount": 48210.5, "currency": "EUR" },
"orders": 412,
"average_order_value": { "amount": 117.02, "currency": "EUR" },
"spend": { "amount": 9830.0, "currency": "EUR" },
"roas": 4.9,
"sessions": 61240,
"conversion_rate_pct": 0.67
},
"comparison": null,
"series": { "granularity": "day", "currency": "EUR", "points": [] }
},
"meta": {
"workspace_id": "0b6f3c1e-8a52-4a7e-9d51-2f1c4e9a7b30",
"range": {
"from": "2026-08-25",
"to": "2026-09-23",
"timezone": "Europe/Warsaw",
"preset": "last_30_days",
"clamped": false
},
"currency": "EUR",
"freshness": { "source": "snapshot", "built_at": "2026-09-24T08:30:12+00:00", "stale": false },
"total_count": null,
"generated_at": "2026-09-24T08:41:03+00:00"
},
"links": { "next": null }
}
The values above are an illustration. data holds the report (or, for the workspace list, an array).
meta.freshness.source says whether the figures come from a prepared snapshot, a
live read, the stored daily report tables (read_model), or a demo
workspace; built_at is when the snapshot was built.
meta.total_count is the number of rows of the whole range across every page on paged reports that
count them (campaigns), and null elsewhere.
Pagination
transactions and fraud/verdicts are paged: up to 100 rows per page (limit),
newest first. When there is more, links.next holds the full URL of the next page; follow it as it is
until it is null. The cursor inside it is opaque, do not build or edit it. Transactions stop
at 1,000 orders per range (row_cap_reached is then true); narrow the range to read more.
campaigns is paged the same way, ranked by sort: 50 campaigns per page by default, up to
200 (limit), at most 2,000 campaigns per range. products is paged by page number
(page, 1 to 200, and per_page, up to 100); links.next works the same way.
traffic-sources is not paged and returns at most 50 rows.
Errors
Errors are application/problem+json (RFC 9457) with a stable machine-readable code and one
English sentence in detail:
{
"type": "about:blank",
"title": "Plan upgrade required",
"status": 403,
"detail": "Fraud detection is included in the Business and Custom plans.",
"code": "plan_required"
}
| Status | code | Meaning and what to do |
|---|---|---|
| 400 | invalid_request | A parameter is malformed (for example a bad cursor or limit below 1). Fix the request. |
| 400 | invalid_range | The range or comparison is not valid: unknown preset or compare, from without to, from after to, to after today, a span of more than 180 days, compare=custom without compare_from or ending today or later, compare_model missing or equal to the current model, model or compare=model on the overview without a scope, or a fraud summary or verdicts range other than 7 or 30 days. detail says which. |
| 400 | range_too_large | The custom range is longer than 180 days. Choose a shorter one. (Some workspaces answer this case with invalid_range; handle both.) |
| 401 | unauthorized | No token was sent. |
| 401 | invalid_token | The token is malformed, expired or revoked. Create a new token or reconnect. |
| 403 | insufficient_scope | The token lacks the scope this endpoint needs. |
| 403 | plan_required | The workspace is on the Free plan, or a fraud endpoint or compare=model was used for a plan below Business. |
| 404 | workspace_not_found | The workspace does not exist or the token does not cover it. Check GET /api/v1/workspaces. |
| 403 | workspace_read_only | A write was refused: the token's user may view the workspace but not edit its settings, or it is a demo workspace. |
| 404 | order_not_found | The workspace has no order with that id. Take order ids from GET .../transactions. |
| 404 | survey_not_found / survey_template_not_found | The survey or template does not exist in the selected workspace. |
| 404 | leads_unavailable | The Leads module is not enabled for this account yet. |
| 404 | lead_not_found / lead_note_not_found / lead_status_not_found | The lead, the note replied to or the status does not exist in the selected workspace. Take ids from GET .../leads and GET .../lead-statuses. |
| 404 | custom_channel_not_found | No custom paid channel with this id in the selected workspace, or an archived one where an active channel is needed. Take ids from GET .../custom-channels. |
| 409 | lead_conflict | The lead's current state refuses the action: already converted, the order id exists, a cross-profile link needs confirmation, or another editor changed the lead at the same moment. Nothing was changed. |
| 404 | product_not_found | The workspace has never tracked a product with that key, or product reports are not available yet. Take keys from GET .../products. |
| 404 | custom_event_not_found | The workspace has no custom event with that key (or only a deleted one). Take keys from GET .../custom-events. |
| 422 | validation_failed | A write breaks a rule. errors maps every field to fix (for example triggers.0.selector) to its messages; nothing was saved. |
| 429 | rate_limited | A rate limit was reached. Wait for Retry-After seconds. |
| 503 | busy | Too many live reports are running for this workspace. Retry after Retry-After seconds. |
| 503 | report_unavailable | The report could not be built right now. Retry in a few minutes. |
| 503 | temporarily_unavailable | Lead data could not be read or saved right now; nothing was changed. Retry in a minute. |
| 503 | api_unavailable | The API is temporarily switched off. Retry later. |
New codes may be added in v1 (see Versioning); treat an unknown code by its HTTP status.
Rate limits
| Limit | Key | Allowance |
|---|---|---|
| Per token | One personal access token or OAuth connection | 120 requests per minute and 5,000 per day |
| Per workspace | All tokens reading one workspace, together | 120 requests per minute (the workspace list does not count) |
| Live reports per workspace | Reports read live rather than from a snapshot: custom ranges, transactions, fraud | 20 per minute |
| OAuth token endpoint | Client IP address | 60 requests per minute |
| OAuth dynamic registration | Client IP address | 10 per hour and 50 per day |
| OAuth revocation | Client IP address | 60 requests per minute |
Responses carry the per-token budget (the tighter of the minute and day windows):
RateLimit-Limit: the size of the window.RateLimit-Remaining: requests left in it.RateLimit-Reset: seconds until the next request is allowed when none is left; otherwise the window length (the windows slide, so capacity comes back gradually).Retry-Afteron a429(and on503 busy): seconds to wait, at least 1.
Back off on 429 and 503, and do not retry any other 4xx unchanged.
Versioning
- Additive changes ship without a version change: new fields in a response, new endpoints, new optional parameters, new error codes. Ignore fields you do not know.
- Breaking changes ship only under
/api/v2: removing or renaming a field, changing a type or a meaning, making a parameter required. - v1 stays available for at least 12 months after a v2 is announced.
- Deprecated fields are marked
deprecatedin the OpenAPI document and keep working until a removal date announced in the changelog, at least 90 days ahead. Deprecated now:comparison.baselineon the overview (readcomparison.fromandcomparison.to) and thecompare_previousparameter, which is ignored.
The release that added the calendar presets (today, yesterday, this_week,
last_week, this_month, last_month) and compare also changed
the meaning of last_N_days in v1: it used to be N days up to and including today and is now the N
full days ending yesterday, so a range covers the same days in the dashboard and in the API. On 2026-10-09,
last_7_days used to end on 2026-10-09, today, and now covers 2026-10-02 to 2026-10-08. Read
meta.range.from and meta.range.to rather than assuming the days, and send
from and to if you need a range that includes today. Details in the
changelog.
OpenAPI document
The full description of every endpoint, parameter and response schema is published as OpenAPI 3.1 at
https://axitrace.com/api/v1/openapi.json. It needs no token.
Import it into Postman, Insomnia or a client generator.
Endpoints
Report endpoints are GET; the write operations are marked with their method. {workspace_id} is a workspace uuid from the workspace
list. Report endpoints take range, or from and to, as described under
Conventions.
| Endpoint | Scope | Returns |
|---|---|---|
/api/v1/workspaces | workspaces:read | The workspaces the token covers |
.../{workspace_id}/overview | reports:read | Headline KPIs and a series |
.../{workspace_id}/traffic-sources | reports:read | Revenue, orders, sessions and add-to-carts per source |
.../{workspace_id}/traffic | reports:read | The Traffic page: visitors, pageviews, purchases and revenue over time, filtered by channel group, source, medium or campaign |
.../{workspace_id}/traffic/filter-values | reports:read | The values each Traffic filter can take, with their sessions |
.../{workspace_id}/traffic/breakdown | reports:read | The Traffic figures and their change per channel group, source, medium or campaign |
.../{workspace_id}/campaign-participation | reports:read | The orders a channel group, source, medium or campaign took part in, credited or not, beside what the model credits to it |
.../{workspace_id}/klaviyo/performance | reports:read | Klaviyo campaigns and flows with their Klaviyo figures and the AxiTrace visits and orders they brought |
.../{workspace_id}/channels/{group}/overview | reports:read | The Organic search page: KPIs, trend, search engines, the role of search in the order path, new and returning customers, products, leads and landing pages |
.../{workspace_id}/store-health | reports:read | The Store health section: conversion rate, average order value, add to cart rate, engagement, visits before an order, returning customers, with series and states |
/api/v1/ai-credits, /api/v1/ai-credits/activity | ai_credits:read | The AI credit balance of the account and every credit spent and added |
.../{workspace_id}/settings | settings:read | The workspace settings by section |
PATCH .../{workspace_id}/settings/{section} | settings:write | Change data collection, attribution, the timezone or the lead sources |
POST .../{workspace_id}/product-labels/rule-sets, .../templates/apply, .../refresh | settings:write | Save Product Labels rules, apply a starter template, refresh the labels now |
PATCH .../{workspace_id}/fraud/settings, POST .../fraud/blocks, .../fraud/whitelist | fraud:write | Change Fraud Detection, block, unblock or whitelist an IP address |
POST .../{workspace_id}/ads/live-query | ads_live:read | A read-only live query of a connected Meta, Google Ads or TikTok account |
.../{workspace_id}/assistant-memory | settings:read, PUT: settings:write | The notes the Axi assistant keeps about the workspace |
.../{workspace_id}/advisor/... | reports:read, changes: settings:write | AI Advisor reports, schedule, analyses (paid in AI credits) and feedback |
.../{workspace_id}/recommendations | reports:read | The recommendations Axi and the AI Advisor gave the workspace |
/api/v1/docs/search | none | Search the AxiTrace documentation |
.../{workspace_id}/analytics | reports:read | Any analytics question: metrics per day, week, month, channel, source, campaign, platform, product or lead status, with filters and a comparison |
.../{workspace_id}/analytics/schema | reports:read | The metrics, dimensions and filters .../analytics answers |
.../{workspace_id}/campaigns | reports:read | Ad campaigns with spend and revenue |
.../{workspace_id}/campaigns/click-cohorts | reports:read | Orders that followed the clicks of one day, counted by click date |
.../{workspace_id}/products | reports:read | Products ranked by revenue, units, growth or conversion rate |
.../{workspace_id}/products/{product_key} | reports:read | One product: KPIs, daily series, funnel, channels, campaigns and attribution models |
.../{workspace_id}/transactions | transactions:read | Orders, paged |
POST .../{workspace_id}/transactions/exclusions | transactions:write | Exclude orders from every statistic, or restore them |
.../{workspace_id}/integrations | integrations:read | Ad platform connection health and the tracking check |
.../{workspace_id}/integrations/{platform}/tracking | integrations:read | Tracking template and check of one ad platform |
.../{workspace_id}/custom-events | custom_events:read | Custom event definitions |
.../{workspace_id}/custom-events/{key} | custom_events:read | One custom event with its configuration |
POST .../{workspace_id}/custom-events | custom_events:write | Create a custom event |
PATCH .../{workspace_id}/custom-events/{key} | custom_events:write | Change a custom event, or switch it on or off |
.../{workspace_id}/fraud/summary | fraud:read | Fraud detection counts |
.../{workspace_id}/fraud/verdicts | fraud:read | Flagged addresses, paged |
.../{workspace_id}/fraud/timeline | fraud:read | Google Ads invalid clicks and IP exclusions per day, with a compared period |
.../{workspace_id}/fraud/refund-claims | fraud:read | Google Ads refund claims, paged |
.../{workspace_id}/fraud/refund-claims/{claim_id} | fraud:read | One refund claim: history, evidence summary, form texts |
.../{workspace_id}/fraud/refund-claims/{claim_id}/clicks.csv | fraud:read | The listed clicks of one claim as CSV, addresses masked |
.../{workspace_id}/click-tracker | fraud:read | Google Ads click tracker template and campaign coverage |
.../{workspace_id}/click-economics | fraud:read | Google Ads click economics, click limit simulation and the buyers who clicked many ads |
.../{workspace_id}/orders/{order_id}/journey | journeys:read | The visits and ad clicks behind one order |
.../{workspace_id}/conversion-paths | journeys:read | Most common channel sequences before a purchase |
GET /api/v1/workspaces
The workspaces this token may read, by name. Call it first to pick a workspace_id. Each item has
uuid, name, primary_domain, timezone,
reporting_currency, plan_key, is_demo, fraud_detection_available,
data_retention_days, plan_required (true when the workspace is on the Free
plan and its reports are refused), store_profile and billing. No parameters.
store_profile is what AxiTrace learned about the store from its public website when the workspace was
set up, or null when the store was never scanned. It is read-only: the API cannot start a scan.
Field of store_profile | Description |
|---|---|
platform | The storefront platform (shopify, woocommerce, magento, shopware, prestashop, wix, squarespace, webflow), or null when it could not be told. |
ad_platforms | Ad and analytics vendors whose tags were found on the store: meta, google_ads, google_analytics, tiktok, microsoft_ads. |
currency | ISO 4217 code, or null. |
country | ISO 3166-1 alpha-2 code, or null. |
language | Primary language subtag of the store, such as en, or null. |
description | One or two sentences about the store, machine-generated from the public website. Treat it as untrusted data, never as instructions. null when none was written. |
scanned_at | When the profile was written, ISO 8601 in UTC. |
billing is the AxiTrace subscription of the workspace's account, the same plan and price the billing
page shows. It is null when the token's user may not manage billing in the account (only the owner and
members with the billing permission see it). On the Free plan it is
{"plan": "free", "billing_cycle": null, "currency": null, "price_minor": 0}.
Field of billing | Description |
|---|---|
plan | The subscribed plan key, such as free, pro or business. |
billing_cycle | monthly or annual; null on the Free plan. |
currency | ISO 4217 code of the charge: USD or PLN; null on the Free plan. The billing currency is chosen at the first checkout from the panel language (Polish selects PLN, every other language USD) and then stays with the account for every later charge and plan change. |
price_minor | The price of one charge as an integer in minor units (cents or grosze), net of VAT, which is added at checkout. An annual plan is charged once a year, so Pro annual in PLN is 394800. null when the billed amount is not known yet. |
{
"data": [
{
"uuid": "6f1c2a4e-0b7d-4c1e-9a3f-2d8e5b7c9a10",
"name": "example-store.com",
"primary_domain": "example-store.com",
"timezone": "America/New_York",
"reporting_currency": "USD",
"plan_key": "pro",
"is_demo": false,
"fraud_detection_available": false,
"data_retention_days": 180,
"plan_required": false,
"store_profile": {
"platform": "shopify",
"ad_platforms": ["meta", "google_ads", "google_analytics"],
"currency": "USD",
"country": "US",
"language": "en",
"description": "You sell outdoor and travel gear online in the US and advertise on Meta and Google Ads.",
"scanned_at": "2026-10-31T09:12:44+00:00"
},
"billing": {
"plan": "pro",
"billing_cycle": "monthly",
"currency": "USD",
"price_minor": 11900
}
}
],
"meta": { "...": "..." },
"links": { "next": null }
}
GET /api/v1/workspaces/{workspace_id}/overview
Revenue, orders, average order value, ad spend, ROAS, sessions and conversion rate, with a revenue and spend series
(daily up to 92 days, weekly beyond). With profit fields the KPIs add
profit, poas, profit_coverage and profit_orders_left_out and each series point adds
profit.
| Parameter | Description |
|---|---|
range, from, to | The period. Default last_30_days. |
compare, compare_from | What the figures are compared with, see Comparisons. comparison then adds deltas: the change of revenue, orders, spend and ROAS (with profit fields, also of profit and poas), each {abs, pct, direction}. compare=model (with compare_model) needs a scope other than all and the Business or Custom plan; without a scope it is refused with invalid_range. |
scope | Whose figures. all (default): the whole store. paid: the six ad platforms that import spend (Google Ads, Meta, Microsoft Ads, TikTok, OpenAI Ads, Criteo). google_ads, facebook (Meta), microsoft_ads, tiktok, openai_ads, criteo_ads: one platform. A scope counts the orders and revenue credited to the platform's own source under the attribution model and the imported spend of its platforms, so its ROAS is the platform's return on spend; it has no sessions, conversion rate or profit. The answer adds scope, attribution_model and spend_coverage ({from, to}, the days with imported spend), and for paid paid_without_cost_revenue and paid_without_cost_orders: paid clicks of platforms without imported cost (Pinterest, X, LinkedIn, Snapchat, Reddit), kept out of the paid figures. Every value is accepted whatever the platforms connected now. Needs the report read model (invalid_request otherwise). Criteo spend, impressions and clicks come from Criteo; Criteo orders and revenue are AxiTrace's own click attribution, so Criteo's view-through conversions are not counted and Criteo's own ROAS will read higher. |
model | With a scope: the attribution model of the orders and revenue, default the workspace setting. Refused with invalid_range without a scope. |
compare_previous | Deprecated and ignored: the comparison is returned unless compare=none. |
GET /api/v1/workspaces/{workspace_id}/traffic-sources
Revenue, orders and sessions per traffic source (source_label), each order credited to exactly one
source by the workspace attribution model, as in the dashboard's sources table and the transactions list, with spend, ROAS and each source's share of revenue, plus totals (and profit, POAS and
cost coverage with the count of orders left out, per row and in the totals, with profit fields).
Ad spend is counted once: an ad platform's imported spend (and its ROAS) sits only on that platform's own row
(google_ads, facebook, microsoft_ads, tiktok, openai_ads, criteo_ads), which is
there even when the platform brought no tracked visit. Other rows of the same platform, such as
utm:google or ref:google.com, have spend and roas
null, so the rows' spend adds up to the totals and to the overview's ad spend. The same holds for
ad_spend by source on the analytics endpoint.
data.attribution_model names the workspace attribution model the orders are credited with
(first_click, last_click or data_driven, set in Workspace settings →
Attribution), data.workspace_attribution_model repeats it and data.attribution_note is
always null.
Each row also has two add-to-cart figures, so you can see which source brings carts that do not end in an order:
carts: the number of distinct visitors who added a product to the cart during the period, in a session that came from this source (the same sessions thesessionscolumn counts). A visitor is the merged AxiTrace profile, or the browser visitor id when there is no profile; a browser that signs in during the period is one visitor.carts_without_order: of those visitors, how many placed no order from their last such add-to-cart until the end of the period. Orders you excluded from statistics do not count as orders.
Every source row has a channel_group: paid_search, organic_search,
paid_social, paid_display, organic_social, email, referral,
affiliates, direct, ai_assistants or other. Sources from AI
assistants (ChatGPT, Perplexity, Gemini, Claude, Copilot and others) have ai_assistants. Only the
Unattributed row and the folded Other row have null. How each source is grouped is described in
Traffic sources and filters.
Both cart figures are whole numbers on every row (0 on the unattributed row, which has no sessions). They count distinct
visitors per row: a visitor who added to cart in sessions from two sources counts on both rows, so rows cannot be
summed and the totals do not include them. If the figures cannot be read for a request, the rest of the report is
still returned, both fields are null on every row, carts_unavailable is
true and carts_unavailable_reason says why in one sentence; a sort by either
field is then refused: with 503 busy or 503 report_unavailable while the cause is
temporary (retry a little later), or with 400 invalid_request when the period has too many distinct ad
tracking parameter combinations (choose a shorter period). The figures are refreshed at most every two minutes.
| Parameter | Description |
|---|---|
range, from, to | The period. |
compare, compare_from, compare_model | What the rows are compared with, see Comparisons and Attribution models. Every row then adds delta and delta_basis; with compare=model also compared, the row under the compared model. |
model | The attribution model of orders and revenue. Default the workspace setting. |
sort | revenue (default), orders, sessions, carts or carts_without_order, highest first. |
limit | Rows, 1 to 50 (default 50). |
GET /api/v1/workspaces/{workspace_id}/traffic
The Overview → Traffic page of the dashboard: the same four figures, chart and filter, read by the same code, so the API and the page always show the same numbers.
totals:visitors(unique visitors per day, summed over the days of the period, as on the page: a visitor who came on three days counts three times; for a one-day period, that day's unique visitors),pageviews,purchasesandrevenue(money in the reporting currency).series: one point per day (per hour for a one-day period), or per week starting on Monday withgrain=week. Each point hasfrom,to,hour(null except on hour points),days, the four figures andpartial(true for today, whose figures are not complete yet; an hour after the last complete one has null figures). With a comparison each point also hascompared, the point of the compared period the chart draws next to it.comparison: the compared period (mode,from,to,available), itstotalsanddeltas, the change of each figure as{abs, pct, direction}. Compared visitors of a one-day period arenull: hourly unique visitors do not add up to a day's.filter:nullwithout a filter; otherwise the applied values of each filter with their names (value_label),attribution_model, the model that credits orders to the filtered traffic, andvisitors_approximate(true when a filter has several values: a visitor seen under two of them may count twice).revenue_converted,missing_rates,other_currencies,data_through(the last complete hour of today) andrecalculating(a day is being recomputed) say how complete the figures are.
Without a filter, purchases and revenue are all tracked orders. With a filter they are the orders the workspace
attribution model credits to the filtered traffic. A filter is refused with 503 report_unavailable
while its data is still being prepared for the period (retry later) and for a workspace whose reports do not
support it yet: a filtered request is never answered with unfiltered figures. How sources, mediums, campaigns and
channel groups are defined is described in
Traffic sources and filters.
| Parameter | Description |
|---|---|
range, from, to | The period. Default last_30_days. |
compare, compare_from | What the figures are compared with, see Comparisons. compare=model is refused with invalid_range. |
channel_group | Comma-separated channel groups: paid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants, other. |
device, country | Comma-separated devices (mobile, desktop, tablet: the visitor's first-visit device) and visitor countries (two-letter codes such as PL); __not_set selects unknown values. See the filters. |
source, medium, campaign | Comma-separated values from GET .../traffic/filter-values, at most 50 per parameter. Several values of one parameter are OR, different parameters are AND. A comma or percent sign inside a value is written as %2C or %25 before the parameter is URL-encoded. |
grain | day (default) or week. A one-day period always has hour points. |
Example: how did Facebook traffic change over the last 14 days? Look the value up with
GET .../traffic/filter-values?level=source&q=facebook, then call
GET .../traffic?range=last_14_days&source=facebook and read data.totals and
data.comparison.deltas.visitors, compared with the 14 days before.
GET /api/v1/workspaces/{workspace_id}/traffic/filter-values
The values one filter of .../traffic can take in the period, as in the
dropdowns of the Traffic page filter bar: value (what the filter accepts), value_label
(its name), sessions in the period (highest first, at most 50) and, for campaigns, section:
ad for an ad platform campaign (value ref:<platform>:<campaign id>) or
utm for a UTM campaign (value utm:<text>).
Source values are keys such as facebook (Facebook and Instagram ads: a Meta campaign id, an ad click
id, or utm_source facebook, fb or meta with a paid medium, ig or instagram, or a Meta placement value
of {{site_source_name}} such as an, th or msg), facebook_organic
(unpaid Facebook visits: post, profile and Messenger links whose click id carries no ad, a Facebook tag with an
organic medium, or a facebook.com referrer), google_ads, tiktok,
direct, ref:<domain> (a referring site), email:<source> (a visit
tagged with an email medium) or utm:<source>; untagged
visits from other social sites are organic_social. __not_set selects visits without a medium or
campaign and __other the rarest values folded together.
| Parameter | Description |
|---|---|
level | Required: channel_group, source, medium, campaign, device or country. Countries are named in English. |
q | Only values whose value or name contains this text, case-insensitive (at most 200 characters). |
range, from, to | The period. Default last_30_days. |
channel_group, source, medium, campaign, device, country | Narrow the list to the traffic already chosen on the other filters, as on .../traffic. |
GET /api/v1/workspaces/{workspace_id}/traffic/breakdown
The figures of .../traffic for each value of one dimension, to see which
channel group, source, medium or campaign grew or dropped. Each row is exactly what .../traffic
answers with the same filter plus that one value: totals (visitors, pageviews, purchases, revenue)
and, with a comparison, compared and deltas; plus sessions of the value in
the period and conversion_rate_pct, purchases per 100 of those sessions. The rows are the values with
the most sessions in the period, ordered by sort.
Visitors of the rows do not add up to the total: a visitor seen under two values counts on both. Purchases and
revenue are credited to one value each by attribution_model, so they add up. While the filter data
is being prepared, or for a workspace whose reports do not support it yet, rows is empty,
filter_available is false and unavailable_reason says why.
| Parameter | Description |
|---|---|
dimension | Required: channel_group, source, medium, campaign, device or country. |
range, from, to, compare, compare_from | As on .../traffic. |
channel_group, source, medium, campaign, device, country | Filter every row, as on .../traffic. For example dimension=campaign&source=facebook lists the Facebook campaigns. |
sort | sessions (default), visitors, pageviews, purchases or revenue, highest first. |
limit | Rows, 1 to 25 (default 10). |
GET /api/v1/workspaces/{workspace_id}/campaign-participation
The Campaign participation report of the Orders section: how many orders a channel group, source, medium or
campaign took part in, whether or not the attribution model credited the order to it. The same answer as the MCP
tool get_campaign_participation.
participating: the orders of the period whose journey (the buyer's visits in the 365 days before the order,lookback_days) the selection touched anywhere, as the only, first, middle or last touch, with their full revenue (money in the reporting currency).credited: the orders and revenue the attribution model (model) credits to the same selection, the figuresGET .../trafficshows for that filter.roles:only(the selection was the only touch),first,middleandlast(the closing touch, the last visit that was not direct), each withordersandrevenue. An order counts in every role the selection played on its path (bothfirstandlastwhen the selection opened and closed it), so the roles can add up to more thanparticipating.shop_orders: every order of the period. The participation of several values overlaps, so summed over values it can exceed this number: never add participation up across values.daily: one point per day withdate,participating_orders,participating_revenue,credited_ordersandcredited_revenue.selectionechoes the values the figures are for;in_session_touches_sinceis the first day a campaign clicked again within one visit is counted (nullbefore the first one is stored, or when the date could not be read:notessays which);missing_ratesandrecalculatingsay how complete the figures are;notesexplains how to read them.comparison:nullunlesscompareasks for a comparison. Otherwise the compared period (mode,from,to,available) with the sameparticipating,credited,roles,shop_ordersanddailyfigures for it (each day with its own date), anddeltasof the participating and credited orders and revenue (abs,pct,nullwhen the compared value is zero, anddirection). A compared period that starts before the first tracked day hasavailable: falseandnullfigures.
Visits linked to the buyer only by IP address do not count as participation; the credited figures include whatever
the model credited. To list the participating orders themselves, call
GET .../transactions with the same values as
touched_channel_groups, touched_sources, touched_mediums and
touched_campaigns.
| Parameter | Description |
|---|---|
range, from, to | The period, as for the reports: a preset or a custom range of up to 180 days. Default last_30_days. |
model | The attribution model of the credited figures: first_click, last_click or data_driven. Default the workspace setting. |
compare, compare_from | The period the figures are compared with: none (the default), previous_period (the same number of days just before), week_earlier, year_earlier or custom with compare_from (as many days from that date). model is refused. |
channel_group | Comma-separated channel groups: paid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants, other. |
source, medium, campaign | Comma-separated values from GET .../traffic/filter-values, at most 50 per parameter. Several values of one parameter are OR, different parameters are AND. |
At least one of channel_group, source, medium and campaign is
required. device, country and the __other value are refused with
400 invalid_request: participation is not recorded per device or country.
Example: GET .../campaign-participation?range=last_30_days&source=google_ads
{
"data": {
"model": "last_click",
"selection": { "channel_groups": [], "sources": ["google_ads"], "mediums": [], "campaigns": [] },
"lookback_days": 365,
"participating": { "orders": 463, "revenue": { "amount": 91240.5, "currency": "PLN" } },
"credited": { "orders": 212, "revenue": { "amount": 40118.0, "currency": "PLN" } },
"roles": {
"only": { "orders": 58, "revenue": { "amount": 9120.0, "currency": "PLN" } },
"first": { "orders": 156, "revenue": { "amount": 31502.5, "currency": "PLN" } },
"middle": { "orders": 201, "revenue": { "amount": 42310.0, "currency": "PLN" } },
"last": { "orders": 190, "revenue": { "amount": 37004.0, "currency": "PLN" } }
},
"shop_orders": 1288,
"daily": [
{ "date": "2026-09-30", "participating_orders": 17, "participating_revenue": { "amount": 3310.0, "currency": "PLN" }, "credited_orders": 8, "credited_revenue": { "amount": 1502.0, "currency": "PLN" } }
],
"in_session_touches_since": "2026-09-12",
"missing_rates": false,
"recalculating": false,
"notes": "Participating orders are the orders of the range whose journey ...",
"comparison": null
},
"meta": {
"workspace_id": "0b6f3c1e-8a52-4a7e-9d51-2f1c4e9a7b30",
"range": { "from": "2026-09-01", "to": "2026-09-30", "timezone": "Europe/Warsaw", "preset": "last_30_days", "clamped": false },
"currency": "PLN",
"freshness": null,
"total_count": null,
"generated_at": "2026-10-01T08:41:03+00:00"
},
"links": { "next": null }
}
The values above are an illustration.
With &compare=previous_period, comparison is an object (abridged):
"comparison": {
"mode": "previous_period", "from": "2026-08-02", "to": "2026-08-31", "available": true, "model": null,
"participating": { "orders": 401, "revenue": { "amount": 80112.0, "currency": "PLN" } },
"credited": { "orders": 198, "revenue": { "amount": 37950.0, "currency": "PLN" } },
"roles": { "only": { "orders": 49, "revenue": { "amount": 7710.0, "currency": "PLN" } }, "...": "..." },
"shop_orders": 1190,
"daily": [ { "date": "2026-08-31", "participating_orders": 14, "participating_revenue": { "amount": 2790.0, "currency": "PLN" }, "credited_orders": 7, "credited_revenue": { "amount": 1290.0, "currency": "PLN" } } ],
"deltas": {
"participating_orders": { "abs": 62.0, "pct": 15.5, "direction": "up" },
"participating_revenue": { "abs": 11128.5, "pct": 13.9, "direction": "up" },
"credited_orders": { "abs": 14.0, "pct": 7.1, "direction": "up" },
"credited_revenue": { "abs": 2168.0, "pct": 5.7, "direction": "up" }
}
}
GET /api/v1/workspaces/{workspace_id}/klaviyo/performance
The Klaviyo performance report: the campaigns and flows of the period with Klaviyo's own figures and the
AxiTrace visits and orders they brought, matched by utm_id, campaign id or name. The same answer as
the MCP tool get_klaviyo_performance. It needs a Klaviyo connection; see the
Klaviyo integration guide for connecting one.
status: the connection state.not_connectedmeans the workspace has no Klaviyo connection and is an answer, not an error: the report is then empty. The other states areconnected,preparing(the traffic data does not cover the period yet),disconnected,reconnect_required,scope_missing,quota_pausedanderror.dataUntil: the last day Klaviyo statistics were synced,nullbefore the first sync.campaignsandflows: one entry per Klaviyo object withid,name,kind,channel,sendDate, the Klaviyo figures (recipients,delivered,opensUnique,clicksUnique,orders,revenue), the matched AxiTrace figures (visits,orders,revenue,nullwhen nothing matched) andmatch(statusmatchedorunmatched, themethodand thereason). A flow carries itsmessages. For flows and flow messages,opensUniqueandclicksUniqueare the sum of the daily unique figures of the range: a person who opens or clicks on several days counts once per day, so they can be higher than Klaviyo shows for the same range.unmatchedKlaviyo: the Klaviyo objects with statistics that no AxiTrace UTM campaign matched.otherUtm: theklaviyo-tagged UTM campaigns that no Klaviyo object claimed.conversionMetric: the Klaviyo metric whose conversions are read, ornull.trackingDetected:falsewhen Klaviyo sent mail but none of the period's visits carry a Klaviyo tag.attributionNoteKey: the key of the note on reading the two attributions.
Klaviyo's revenue is Klaviyo's own attribution and the AxiTrace revenue is the workspace attribution model:
they answer different questions, so read them side by side and never add them together. All money is a number in
reportingCurrency. Dates are calendar days in the workspace timezone, except sendDate,
which is the send date in the Klaviyo account timezone. The report returns aggregates and Klaviyo object names
only, never a recipient, an email address or any other personal data.
| Parameter | Description |
|---|---|
range, from, to | The period, as for the reports: a preset or a custom range of up to 180 days. Default last_30_days. |
kind | all (the default), campaigns or flows. The connection status, the unmatched Klaviyo objects and the klaviyo-tagged UTM campaigns come back either way. |
model | The attribution model of the AxiTrace figures: first_click, last_click or data_driven. Default the workspace setting. |
Example: GET .../klaviyo/performance?range=last_30_days&kind=campaigns&model=last_click
{
"data": {
"status": "connected",
"dataUntil": "2026-09-30",
"conversionMetric": { "id": "abc123", "name": "Placed Order", "integration": null, "kind": "native_store" },
"attributionNoteKey": "backend.klaviyo.performance.attribution_note",
"trackingDetected": true,
"reportingCurrency": "PLN",
"campaigns": [
{
"id": "camp_1",
"name": "Autumn newsletter",
"kind": "campaign",
"channel": "email",
"sendDate": "2026-09-12",
"klaviyo": { "recipients": 4200, "delivered": 4150, "opensUnique": 1900, "clicksUnique": 320, "orders": 48, "revenue": 9120.0 },
"axitrace": { "visits": 355, "orders": 41, "revenue": 8034.5 },
"match": { "status": "matched", "method": "utm_id", "reason": null },
"messages": []
}
],
"flows": [],
"unmatchedKlaviyo": [],
"otherUtm": []
},
"meta": {
"workspace_id": "0b6f3c1e-8a52-4a7e-9d51-2f1c4e9a7b30",
"range": { "from": "2026-09-01", "to": "2026-09-30", "timezone": "Europe/Warsaw", "preset": "last_30_days", "clamped": false },
"currency": "PLN",
"freshness": null,
"total_count": null,
"generated_at": "2026-10-01T08:41:03+00:00"
},
"links": { "next": null }
}
The values above are an illustration.
GET /api/v1/workspaces/{workspace_id}/channels/{group}/overview
The Organic search page of the dashboard, with the same numbers.
{group} is organic_search (the only channel served for now). The MCP tool
get_channel_overview returns the same answer. data has channel_group,
comparison, model, grain, rate_date (every amount, the comparison and
the year-earlier line included, is converted at the exchange rate of the last day of the range),
missing_rates and one object per requested section:
- overview:
page_state;kpis(sessions,orders,revenue,cr,aov,rpsrevenue per session,organic_shareof all revenue,new_customer_share, each withvalue,previous,delta_pct,directionandlow_sample);trend(points per day or ISO week,previousthe compared period andyoythe same dates a year earlier, both aligned day by day;yoyis null andyoy_availablefalse when no data is kept from a year earlier, and a day before the data floor has null figures;markersthe Google search update ranges);engines(google, bing, duckduckgo, other, andai_assistants, the AI assistant visits, which are not organic; every row haspreviousandsessions_delta_pct,orders_delta_pct,revenue_delta_pctwith their_direction);ads(daily Google Ads spend beside the organic sessions, the total withprevious,total_spend_delta_pctandtotal_spend_direction). - journey:
models(organic orders, revenue and share of revenue under first click, last click and data-driven;statelockedbelow the Business plan);opener(orders whose first click was organic search, by the channel group of their last click);lag(days from the first organic visit to the purchase, in buckets, with the median and mean);customers(new, returning, unknown);heatmap(countries with at least 30 organic sessions, the rest in Other, by device, with the conversion rate). - content:
products(top 10 by revenue; with profit fields alsoprofitandprofit_coverage);leads(the organic lead count, only withleads:read);landing(top 10 organic landing pages, the rest, andbrand, the share of sessions landing on the home page as an approximation of brand searches, withprevious,share_delta_pctandyear_earlier).
Every section has a state: ready, empty, low_sample (fewer than 30
sessions or 5 orders: shown, but read with care), error (that part could not be read; retry later),
locked, preparing (the data is still being built for the range) or
comparison_unavailable (the compared period starts before tracking did). Money is
{amount, currency}; ratios are 0 to 1.
| Parameter | Description |
|---|---|
range, from, to | The period. Default last_30_days. |
compare, compare_from | What the KPIs are compared with, see Comparisons. compare=model is refused with invalid_range: journey.models already shows the three models. |
model | The attribution model of the orders and revenue. Default the workspace setting. |
sections | Comma-separated overview, journey, content. Default all three. |
grain | day or week. Default day up to 92 days, week above. |
GET /api/v1/workspaces/{workspace_id}/store-health
The Store health section of Overview, with the same numbers, for the
whole store (every channel together). The MCP tool get_store_health returns the same answer.
data has window, comparison (mode, from,
to, available), grain (day up to 92 days, week
above), missing_rates, recalculating, demo, buckets (the local
days or the Mondays of the ISO weeks of the series), metrics and series.
metrics has ten figures by id: conversion_rate, aov,
revenue_per_session, add_to_cart_rate, engaged_session_rate,
avg_engagement_time (seconds), visits_before_order, returning_customer_share,
pages_per_session and sessions. Each has value, previous,
delta (the percent change, 1 decimal), state, reasons,
measured_since and median (only visits_before_order: the median visits, 101
meaning more than 100). Ratios are 0 to 1; the value and previous of aov and
revenue_per_session are {amount, currency}. series has, per id,
current (one value per bucket) and previous (the compared period laid over the buckets by
position, or null).
state is ready, low_sample (shown, but based on fewer than 30 sessions or 5
orders), not_tracked (the store sends no such signal), preparing (the data is still being
built for the range) or comparison_unavailable. reasons may hold
measured_since (the figure exists only since measured_since, so an earlier comparison has
no previous value) and active_time_not_measured. Every definition is on the
Store health page.
| Parameter | Description |
|---|---|
range, from, to | The period. Default last_30_days. |
compare, compare_from | What the figures are compared with, see Comparisons. compare=model is refused with invalid_range: no figure depends on the attribution model. |
GET /api/v1/workspaces/{workspace_id}/analytics
One endpoint for ad-hoc questions. Name the metrics, at most one dimension, filters and a comparison; the answer
comes from the same reports as the dashboard (the overview, the traffic sources table, campaigns, products, the
Traffic page and leads), so the numbers match what you see there. data.dataset names the report that
answered: without dataset it is the first of overview, sources,
campaigns, products, landing_pages, traffic, leads,
customers, order_paths that has every metric, the
dimension and every filter. A combination no report has is refused with 400 invalid_request and a
sentence listing what each one answers; ask two simpler questions instead.
- Metrics:
visitors,sessions,pageviews,orders,revenue,aov,conversion_rate_pct,ad_spend,roas,impressions,clicks,ctr_pct,cpc,cpa,carts,carts_without_order,units,product_views,add_to_carts,view_to_order_rate_pct,leads,converted_leads,lead_conversion_rate_pct. Lead metrics also needleads:read. - Dimensions:
day,week(from Monday),month,channel_group,source,medium,campaign,ad_platform,product,lead_status,lead_source_kind,device,country,landing_page(the path of the first page of a session, without the query string),search_engine(organic search per engine: google, bing, duckduckgo, other),customer_kind(new, returning, unknown),closer_channel_group(the last-click channel group of the orders organic search opened) andlag_bucket(days from the first organic visit to the purchase). Browser and city are not stored per day and are refused with the reason. - Organic search:
search_enginerows come from the sources report; thecustomersandorder_pathsreports come from the Organic search page, cover organic search only for now, needfilter[channel_group]=organic_searchand have no comparison. When some of their revenue had no exchange rate on the rate date,completeisfalseand a note names that date. - Rows:
value(the dimension value;nullfor totals),value_label,attributes,values,comparedanddeltas({abs, pct, direction}per metric).definitionssays how each requested metric is counted,rows_matchedcounts the rows beforelimitandcompleteisfalsewhen only the top rows of a report were read.
| Parameter | Description |
|---|---|
metrics | Required: 1 to 8 metrics, comma-separated. |
dimension | The dimension of the rows; leave it out for one totals row. |
filter[<dimension>] | Comma-separated values: keep the rows whose value is one of them. filter[<dimension>][contains]=text keeps the rows whose value or name contains the text. |
having[<metric>][gt|gte|lt|lte] | A threshold on a requested metric, applied to the rows read. |
range, from, to, compare, compare_from | The period and what it is compared with, see Comparisons. |
model | The attribution model of the sources, campaigns, products and customers reports. |
sort, order, limit | A requested metric or the dimension; asc or desc; 1 to 100 rows (default 25, or 100 for a date dimension). |
dataset | Force one report: overview, sources, campaigns, products, landing_pages, traffic, leads, customers or order_paths. |
Examples:
- Which sources grew revenue fastest this month?
metrics=revenue,orders&dimension=source&range=this_month&compare=previous_period, then readdeltas.revenue.pct. - Facebook campaigns with ROAS below 1 last month:
metrics=ad_spend,revenue,roas&dimension=campaign&filter[ad_platform]=facebook&having[roas][lt]=1&range=last_month. - Visitors and orders per week over the last 90 days against a year earlier:
metrics=visitors,orders&dimension=week&range=last_90_days&compare=year_earlier. - Products sold through AI assistants:
metrics=revenue,units&dimension=product&filter[channel_group]=ai_assistants. - Mobile against desktop:
metrics=visitors,orders,conversion_rate_pct&dimension=device. - Landing pages that bring orders from Facebook:
metrics=sessions,orders,revenue&dimension=landing_page&filter[source]=facebook&sort=revenue. - Organic search per engine:
metrics=sessions,orders,revenue&dimension=search_engine. - New against returning customers from organic search:
metrics=orders,revenue&dimension=customer_kind&filter[channel_group]=organic_search. - Who closed the orders organic search opened:
metrics=orders,revenue&dimension=closer_channel_group&filter[channel_group]=organic_search.
GET /api/v1/workspaces/{workspace_id}/analytics/schema
What .../analytics answers: every report (dataset) with its metrics, dimensions, filters and scope,
the definition of every metric and dimension, the dimensions that are not stored and the limits. Reads no data.
GET /api/v1/workspaces/{workspace_id}/campaigns
Ad campaigns of Facebook, Google Ads and TikTok, from data AxiTrace already stored (no ad platform is called for
the request). Every range, a preset or custom dates, has the same basis and the same fields, so two periods can be
compared field by field. As on the traffic sources report, data.attribution_model and
data.workspace_attribution_model name the workspace attribution model and
data.attribution_note is always null:
- Platform side:
spend,impressions,clicks,ctr_pct,cpc,platform_reported_conversionsandplatform_reported_revenue, from the stored daily ad platform reports. For Meta (facebook) they are the purchases and purchase value Ads Manager reports, under each ad set's attribution setting; they are usually higher thanorders, because Meta also credits ad views and clicks AxiTrace cannot see. Meta'sclicksalso count likes, comments and profile clicks;link_clicksholds only the clicks that opened your site (inline_link_clicks). It isnullfor every other platform, whoseclicksalready are clicks to your site. - AxiTrace side:
tracked_clicks,orders,revenue(revenue_basisis alwaysaxitrace_attributed),roas,cpaandaverage_order_value, credited with the workspace attribution model (data.attribution_model) exactly as on the dashboard campaign tables, one campaign per order. - Add-to-carts:
cartsis the number of distinct visitors who added a product to the cart during the period in a visit counted in the row'stracked_clicks(the same ad click signals and the same campaign matching);carts_without_orderis how many of them placed no order from their last such add-to-cart until the end of the period. Whole numbers on every row; on an untracked row they count the platform's ad visits that matched no single campaign. They count distinct visitors per row, so they are not part ofdata.totals. When they cannot be read they arenullon every row anddata.carts_unavailable/data.carts_unavailable_reasonsay why, as on the traffic sources report. Campaign add-to-carts are computed in the background: for a period whose figures are not ready yet (new data, or right after an AxiTrace update) the reason says they are being prepared (check back later), and asortby either field is refused with503 report_unavailable; retry later. Campaign add-to-carts cover the last 180 days: for a period that starts earlier the reason says to choose a later range, and asortby either field is refused with400 invalid_request. - Profit (only with profit fields):
profit,poas,profit_coverageandprofit_orders_left_outon every row and indata.totals, the profit attributed exactly likerevenue. - Each row also has
platform,campaign_id(the ad platform id),campaign_labelandstatus. A row withis_untrackedholds the orders of a platform's traffic that matched no single campaign.
data.totals and meta.total_count cover the whole range, not only the page.
data.platforms says per platform where the attribution (dashboard_snapshot,
live or read_model) and the platform figures came from.
| Parameter | Description |
|---|---|
range, from, to | The period. |
compare, compare_from, compare_model | What the campaigns are compared with, see Comparisons and Attribution models. Every row then adds delta and delta_basis; with compare=model also compared. |
model | The attribution model of tracked clicks, orders and revenue. Default the workspace setting. |
platform | facebook, google_ads, tiktok, microsoft_ads, openai_ads or criteo_ads; all connected platforms when omitted. Criteo spend, impressions and clicks come from Criteo; Criteo orders and revenue are AxiTrace's own click attribution, so Criteo's view-through conversions are not counted and Criteo's own ROAS will read higher. |
sort | spend (default), revenue, roas, orders, carts, carts_without_order, profit or poas, highest first. profit and poas need profit fields. |
cursor | From links.next; leave it out for the first page. |
limit | Campaigns per page, 1 to 200 (default 50). At most 2,000 campaigns per range. |
GET /api/v1/workspaces/{workspace_id}/products
The Products ranking of the dashboard: every product with sales or product views in the period, from the stored
daily product reports. Each row has rank, product_key (pass it to the
product endpoint), parent_key, product_label,
sku_label, category_label, brand_label, image_url and
product_url (https only), is_variant and variant_count, then
orders, units, revenue, views (product views),
add_to_carts, view_to_order_rate_pct and top_channel, the source that brought
most of its revenue. view_to_order_rate_pct is null without views and when
orders_exceed_views is true: orders follow the attribution model and some buyers had no tracked product
view in the range, so no rate above 100 is given.
- Revenue of a product is its share of each order's revenue after discounts, so the products add
up to the orders total. Orders, units and revenue follow the workspace attribution model
(
data.attribution_model); views and add-to-carts are counted per visit. - Ad spend is not split by product, so there is no product ROAS. The product endpoint shows each campaign's whole spend for context.
data.totalscovers every product of the period:revenue,units,products_sold,products_seen,ai_assistants_revenuewith its share, andrevenue_without_product_data(orders that carried no product lines).- With profit fields, rows and totals add
net_revenue.
| Parameter | Description |
|---|---|
range, from, to | The period. |
compare, compare_from, compare_model | What the products are compared with, see Comparisons and Attribution models. Every row and the totals then add delta and delta_basis; with compare=model rows also add compared, their revenue and units under the compared model (Business and Custom plans). |
model | The attribution model of orders and revenue. Default the workspace setting. |
channel_group | all (default); a channel group: paid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants (ChatGPT, Perplexity, Gemini, Claude, Copilot and other AI assistants) or other; or the paid traffic of one ad platform: facebook, google_ads, tiktok, microsoft_ads, openai_ads or criteo_ads. Every figure then counts only that traffic. |
view | all (default), sold or viewed_not_sold (viewed but not ordered in the period). |
sort | revenue (default), units, growth (revenue change against the compared period) or conversion_rate (orders per product view), highest first. |
page, per_page | Page 1 to 200; products per page, 1 to 100 (default 50). Follow links.next; meta.total_count counts the products of the period. |
q | Only products whose name, key or SKU contains this text (at most 100 characters). |
GET /api/v1/workspaces/{workspace_id}/products/{product_key}
One product of the list. URL-encode product_key; it may contain a slash. A key the workspace has never
tracked answers 404 product_not_found. The answer has:
product(as on a list row) andvariants(the variants of a Shopify parent product);kpis:orders,units,revenue,views,unique_viewers,add_to_cartsandview_to_order_rate_pct, withdeltaagainst the compared period;daily: revenue, units, orders and views per day of the period and of the compared period;funnel: product views, add-to-carts, cart sessions and orders per landing channel of the visit;channelsandcampaigns: who bought the product under the attribution model, each withorders,units,revenueandrevenue_share_pct. A campaign also hascampaign_spend, the whole campaign's spend in the period, shown for context only;models: on the Business and Custom plans, the product per channel underfirst_click,last_clickanddata_driven; every model adds up to the same totals.nullon other plans;product_cost: whether per-product ad cost is available and, when the product has any,product: itscost, the product ROAS of the Shopping and Performance Max campaigns that sold it (roas) and the cost perplatformsentry. A custom paid channel's entry hasplatformcustom_<uuid>, the channel name inplatform_labeland its ownroas: the revenue its traffic brought for the product divided by its cost for it,nullwhen the channel rules use a medium or campaign. List rows carry the sameproduct_cost(without the catalogroas) only for products that have product cost.
Parameters: range, from, to, compare, compare_from,
compare_model, model and channel_group, as on the list. The ad set and ad
breakdown of the dashboard is not part of the API.
GET /api/v1/workspaces/{workspace_id}/transactions
Orders, newest first: order_id, occurred_at, value (in the order currency),
source_label, medium_label, campaign_label, attribution_origin and
is_excluded, plus profit and cost_quality with
profit fields. touchpoint (the attributed touch) and
last_click_touchpoint (the closing touch) are the touchpoint objects the
Orders list shows. No buyer personal data. Paged, see Pagination.
| Parameter | Description |
|---|---|
range, from, to | The period. |
source | Only orders attributed to this source_label; direct for orders without a source. |
min_value | Only orders worth at least this amount. |
touched_channel_groups, touched_sources, touched_mediums, touched_campaigns | Only the orders first seen in the range whose journey (the buyer's visits in the 365 days before the order) these values touched anywhere, credited or not: the participating orders of the Campaign participation report. Comma-separated values of GET .../traffic/filter-values (OR within a parameter, AND across them); __other is refused. A campaign clicked again within one visit counts from the release that started recording such in-session clicks. |
touched_role | With a touched_* value: only the orders where it was the only touch, the first, a middle or the last (closing) touch. |
excluded | include (default): every order, the excluded ones with is_excluded true; exclude: only the orders that count in the statistics; only: only the orders excluded from the statistics. Applied before paging, so every page is full. Any other value is 400 invalid_request. |
cursor | From links.next; easier to follow that URL as it is. |
limit | Rows per page, 1 to 100 (default 100). |
POST /api/v1/workspaces/{workspace_id}/transactions/exclusions
Excludes orders from the statistics of the workspace, or restores them: the same change as the Exclude and
Restore actions of the Transactions list and the MCP tool set_order_exclusion. Use it for test
orders, duplicates, internal or fraudulent purchases. An excluded order stops counting in every report (revenue,
orders, products, channels, campaigns and campaign participation); it is never deleted and stays in
GET .../transactions with is_excluded true.
Restoring it counts it again. The affected days of the reports are recomputed in the background.
Needs the opt-in transactions:write scope (an extra box on the consent page, unticked on the
personal access token form) and a role that may edit the workspace settings; demo workspaces are read-only.
| Body field | Description |
|---|---|
action | exclude or restore. Required. |
order_ids | The order ids from GET .../transactions, 1 to 500, each at most 255 characters; duplicates count once. Required. The call applies to all of them or to none. |
The answer has workspace_id, action, changed (the orders whose state
changed) and unchanged (the orders that already were excluded, or not excluded). Calling it again
with the same body changes nothing. 422 validation_failed names order_ids or
action; 503 temporarily_unavailable means nothing was saved, either because the
statistics storage could not record the change or because another request changed some of these orders at the
same moment: retry in a minute.
POST /api/v1/workspaces/{workspace_id}/transactions/exclusions
{"action": "exclude", "order_ids": ["1042", "1043"]}
{"data": {"workspace_id": "...", "action": "exclude", "changed": ["1042"], "unchanged": ["1043"]}, ...}
GET /api/v1/workspaces/{workspace_id}/integrations
Every ad platform connection with status, test_mode, enabled_events_count,
events_sent, events_failed, last_error_label, last_sync_at and
updated_at, plus consent_signal: whether consent signals arrive as expected, and
ad_accounts: the ad account connections that import spend (Microsoft Ads, OpenAI Ads and Criteo), each with
platform, status, healthy, reconnect_required,
last_sync_at, last_sync_status and masked_key (an API key connection
only: the key prefix and its last four characters, null otherwise). A Criteo row appears only when the workspace has a
Criteo connection or Criteo traffic in the last 90 days; its status not_connected means Criteo
traffic is seen but no Criteo account is connected, and unknown (with error
presence_check_failed) means that could not be checked right now; ask again later. error is
null on every other row. Credentials are never returned. No parameters.
marketing_accounts lists the marketing (email) account connections, today Klaviyo, one entry per
workspace with platform, status (connected, disconnected,
reconnect_required or scope_missing), scopes (the granted OAuth
scopes), last_sync_at, last_sync_status, last_error (only the redacted
summary of the last failed sync, null otherwise) and account_name (the Klaviyo account name, or
its account id when it has no name). The list is empty when the workspace has no Klaviyo connection. No token
and no raw error text is ever returned.
A Klaviyo row in connections (the event-forwarding destination) also carries details:
create_profiles (whether new Klaviyo profiles are created), placed_order_mode
(on, off_native or pending: whether Placed Order is sent),
native_integration (the store's own Klaviyo integration, or null), oauth_status
(connected, error or the connection status) and skipped_today, the events
not sent today (UTC) with the reason: no_identity, no_profile,
lookup_unavailable, order_native, order_policy_pending and
stale, each an integer. Other platforms have no details key.
tracking lists the tracking check of every connected ad platform that imports spend (Google Ads, Meta,
Microsoft Ads, TikTok, OpenAI Ads, Criteo), and each ad_accounts row carries its own entry under
tracking: template (the URL parameters AxiTrace needs on every ad click, exactly as the
merchant pastes them), template_location (final_url_suffix, url_parameters or
query_string_template), status (tracked, campaigns_without_tracking,
not_detected, partially_checked when some live campaigns could not be checked,
cannot_check or not_enough_data), campaigns_without_tracking (live campaigns
missing the template), tracking_detected (false when the platform spent money and more
than half of at least 20 paid visits of the last window_days days cannot be tied to a campaign,
null when there is not enough to judge), visits and untracked_visits.
AxiTrace never changes an ad account: hand the template to the merchant to paste. Without an OpenAI Ads connection there is no
entry for it; its campaign query string template is utm_source=chatgpt&utm_medium=cpc&oai_campaign_id={campaign_id}&oai_ad_id={ad_id} (OpenAI
appends oppref by itself).
custom_channels lists every active custom paid channel with
platform (custom_<uuid>), platform_label,
spend_coverage_until (the last day with imported cost), missing_months (the complete
calendar months of the last three, workspace timezone, that have no imported cost; a month counts only when the
channel existed before it started, and the previous month only from the 3rd) and status
(ok, stale, or cannot_check with both fields null when the check could not
run). Empty without custom channels.
GET /api/v1/workspaces/{workspace_id}/custom-events
Custom event definitions: name_label, key_label, kind,
trigger_types, enabled, created_at and fired_last_30_days
(null when that count could not be read). No parameters.
GET /api/v1/workspaces/{workspace_id}/custom-events/{key}
One custom event with its whole configuration: key_label, name_label,
description_label, kind, enabled, throttle_mode,
throttle_seconds, properties, triggers (each with its id,
type, selector_label, props, hover_ms and
visible_ms), destinations, default_value, default_currency
and created_at. A deleted custom event answers 404 custom_event_not_found.
GET /api/v1/workspaces/{workspace_id}/fraud/summary
Business and Custom plans. Suspicious addresses on paid traffic, the paid sessions they caused (Google and other platforms apart), bot filter drops, blocked and whitelisted addresses, invalid clicks credited by Google and the state of automatic exclusion. See Fraud Detection for what the numbers mean.
The exclusions object describes the Google Ads IP exclusion slots: slots_total (the
account limit), foreign (your own exclusions found on the account, never touched),
pushed, queued (blocked addresses waiting for a free slot), displaced_7d
(exclusions replaced by a stronger address in the last 7 days),
median_time_to_exclusion_seconds_30d.fast and .hourly (median seconds from the last
fraudulent click to the exclusion over 30 days, per detection path; only exclusions pushed after that click and
not replaced since, in live mode; null without data), exclusions_measured_30d (how many
exclusions each median covers) and fast_path_active (null when it could not be read).
| Parameter | Description |
|---|---|
range | last_7_days or last_30_days (default). No other window. |
GET /api/v1/workspaces/{workspace_id}/fraud/verdicts
Business and Custom plans. Flagged addresses, newest signal first: address_masked (IPv4 /24, IPv6
/48), risk_level, score, rules_fired, network_class, session
counts, first_seen_at, last_signal_at, status and
google_ads_exclusion. Paged.
google_ads_exclusion says what Google Ads holds for the address now: status
(pushed, queued_capacity, displaced = replaced by a stronger address and
waiting for a slot, failed, pending, removing, not_pushable,
not_pushable_ipv6, not_pushable_google, or null when nothing is on the
account), pushed_at, displaced_at, detection_path (fast,
hourly, manual, backfill) and time_to_exclusion_seconds from
the last fraudulent click to the exclusion (null when not measured).
rules_fired lists reason codes in catalogue order: N network, T technical
browser tells, B behaviour, V velocity, G geography, W whitelist
rails. C1 to C6 are click-layer evidence from the
click tracker and add no points:
C1 click without a visit, C2 click and visit from different addresses, C3
implausible time to the visit, C4 different device at the visit, C5 click from a
datacenter, C6 replayed click id. See Fraud
Detection.
| Parameter | Description |
|---|---|
range | last_7_days or last_30_days (default). |
risk_level | high, medium or low; high and medium when omitted. |
cursor | From links.next. |
limit | Rows per page, 1 to 100 (default 100). |
GET /api/v1/workspaces/{workspace_id}/fraud/timeline
Business and Custom plans. For any range of up to 180 days, the Google Ads clicks and the invalid clicks Google itself filtered out (and did not charge) beside the automatic IP exclusions AxiTrace created and removed, per day, with the same totals for a compared period and the change of each figure:
google_ads:totals,compared_totalsanddeltasofclicks,invalid_clicks,invalid_click_rate_pct,impressions,cost,clicks_per_dayandinvalid_clicks_per_day;campaigns, the 50 campaigns with the most invalid clicks;last_synced_at. Every campaign of the account counts, paused and removed ones included, so the totals can be higher than a Google Ads view filtered to enabled campaigns.exclusions: the state now (in_google_ads_now,blocked_addresses_now,addresses_ever_blocked,first_exclusion_at,merchant_own_exclusions_now,slots_available_nowofgoogle_ads_limit) and the exclusionsaddedandremovedin the range and the compared period.days: every day of the range with the Google figures (per Google Ads account day, null when no data was synced) and the exclusion figures (per day in the workspace timezone, null when no sync ran).
invalid_click_rate_pct is invalid clicks divided by clicks plus invalid clicks, times 100: the
definition of the Google Ads column. Compare the rate rather than the count, which follows the number of clicks
bought. An excluded address no longer sees the ads, so its clicks appear neither in clicks nor in
invalid_clicks: exclusions target the clicks Google did not flag and charged for. Google keeps
revising the invalid clicks of recent days; last_synced_at says when the figures were read.
| Parameter | Description |
|---|---|
range / from, to | A preset or custom dates, as for the other reports (at most 180 days). Default last_30_days. |
compare | previous_period (default), week_earlier, year_earlier, custom with compare_from, or none. |
compare_from | First day of a custom comparison, YYYY-MM-DD. |
GET /api/v1/workspaces/{workspace_id}/fraud/refund-claims
Business and Custom plans (other plans answer 403 plan_required). The
Google Ads refund claims of the workspace,
newest first: claim_id, status (ready, submitted,
info_requested, replied, credited, rejected,
expired or withdrawn), is_open, google_customer_id,
period_from, period_to, click_count, unverified_click_count,
estimated_cost (null when no spend was available to price the clicks), currency, google_case_number, credit_amount,
credit_source and the created_at, submitted_at, credited_at,
closed_at and expires_at times. Read only: submitting a claim and recording Google's
answer happen in the panel. Paged.
| Parameter | Description |
|---|---|
status | One status, or several separated by commas; every status when omitted. |
from | YYYY-MM-DD: only claims whose period ends on or after this day. |
to | YYYY-MM-DD: only claims whose period starts on or before this day. |
cursor | From links.next. |
limit | Claims per page, 1 to 100 (default 100). |
GET /api/v1/workspaces/{workspace_id}/fraud/refund-claims/{claim_id}
Business and Custom plans. One claim: claim (the list fields), events (the history,
oldest first: type, at, by and details), summary
(listed, verified, unverified and landed clicks, clicks by source and by evidence code, ip_count,
top_ips with up to 20 addresses, campaigns, the daily table beside Google's
own clicks, conversions and invalid clicks, and the AxiTrace verdict figures) and form_texts (the
texts for Google's invalid clicks form). Every address is masked to its network, IPv4 /24 and IPv6 /48, in
top_ips and inside the form texts alike; the evidence pack with full addresses is downloaded in the
panel. summary is null and summary_error names the failure when the evidence
could not be read. A claim id the workspace does not have, including one of another workspace, answers
404 refund_claim_not_found.
GET /api/v1/workspaces/{workspace_id}/fraud/refund-claims/{claim_id}/clicks.csv
Business and Custom plans. The listed clicks of one claim as a CSV file (text/csv, streamed), one row
per click: click_id, click_id_kind, clicked_at_utc, ip (masked
to its network), ip_verified, user_agent, asn, asn_org,
country, campaign_id, ad_group_id, keyword,
match_type, network, device, landed,
landing_latency_ms, source, evidence_codes and
axitrace_verdict_score. No parameters.
GET /api/v1/workspaces/{workspace_id}/click-tracker
Every plan. The Google Ads tracking template of the workspace for the
click tracker (template, with the
workspace key filled in, pasted as the account-level tracking template: template_location is
tracking_template) and coverage: state (read,
not_connected or unavailable), account_template_kind,
campaigns_total, campaigns_using_click_tracker and per campaign campaign_id,
name, status, template_level (campaign, account or
none) and template_kind (click_tracker, other or
none), plus unverified_landing_hosts: the hosts of the final URLs of the account's ads
and asset groups that are not on a verified domain of the workspace. The click tracker refuses clicks to those
with 403 instead of redirecting, so verify them before pasting the template. Read live from the
connected Google Ads account; AxiTrace never changes it. The endpoint is available once the click tracker setup is switched on; until then it answers 404. No parameters.
GET /api/v1/workspaces/{workspace_id}/campaigns/click-cohorts
Every plan. Click-date cohorts of one ad campaign (platform and campaign_id, as
campaigns returns it) or of one non-paid traffic source (source, with an
optional medium, the values of the traffic filter). For each cohort day it follows the visitors who
clicked that day and the orders that came within days, so a spend spike can be judged by the sales its
clicks brought later, not only by the orders of the spike day. Orders are counted by the click date, not the
purchase date. Read at request time.
clicksandspend: the ad platform's own figures for the ad account's day (campaigns only).tracked_sessions,untracked_sessions(landings with the platform's click id but no campaign id, which belong to no campaign cohort) andcohort_buyers_eligible(the visitors followed).touch_based: every order of those visitors within the window, first seen after their click (orders,buyers,revenue,cumulativeorders by day since the click). An order can count in two cohort days when the buyer clicked on both. Manual attributions are not applied here.model_based: the orders the attribution model (model, default the workspace setting) credits to that day's touch of the campaign. Manually attributed orders are left out and counted inoverridden_orders; data-driven orders without a verdict yet are counted inpending_model_orders.status:complete, orprovisionalwhile day + days - 1 is today or later (more orders can still come).curve(the share of the window's orders that had come by each day after the click) andmedian_lag_daysuse complete cohort days only.- TikTok campaigns have no cohorts: TikTok visits and orders carry no campaign id (
statusnot_availablewith areason).
Query parameters: platform and campaign_id, or source and
medium; from and to (cohort days, YYYY-MM-DD in the workspace
timezone, at most 31 days, to not after today; default the 7 newest complete cohort days);
days (1 to 30, default 7) and model. A longer range or days outside 1 to 30
answers 400. Money is {amount, currency}. Same operation as MCP
get_click_cohorts.
GET /api/v1/workspaces/{workspace_id}/click-economics
Every plan. The Click Economics page of the panel as data, read from the analysis AxiTrace builds daily over up to
180 days ending yesterday (built_at, window; status is
not_available with a message until the first build). A paid click is a distinct Google
Ads click id (gclid) of one visitor; clicks are never counted per IP address and no IP address or visitor id is
returned. Buyers who had ordered before their first paid click of the window are left out
(counts.excluded_prior_buyers), so every figure is about first purchases.
clicks_before_first_purchase:median,mean,p90,p95,p99andshare_buying_after_one_click.conversion_rate_by_click_number: the chance to buy at the N-th paid click (merged buckets, with a 95% interval) againstbreak_even_conversion_rate.recommendation: the click limit recommendedwithout_cartandwith_cart(status,recommended_limit,net_saving_per_month,buyers_lost_share) atmerchant_inputs(gross margin, cost per click), andapplied_limits_now.simulation_per_month: one row per limit from 5 to 30 withclicks_stopped_in_window,buyers_lost,revenue_lost_per_month,cost_saved_per_monthandnet_per_month.google_ads_account: the account totals of the window and the campaigns limited by budget.buyers_by_clicks_before_first_purchase: the buyers whose first order came with theirmin_clicks-th or a later paid click (segment) againstother_buyers: buyers, first-order revenue, average first-order value, lifetime revenue and value, and the segment's share of buyers and revenue.orderslists their first orders, heaviest first:order_id(use it with the order journey),ordered_at,paid_clicks_before_order,days_from_first_paid_click,value,orders_by_this_buyerandlifetime_value. Orders are kept for buyers with at least 5 clicks, the 500 heaviest of the window.
Query parameters: min_clicks (2 to 41, 41 meaning 41 or more; default the workspace's click limit
without a cart) and orders_limit (0 to 100, default 50). Same operation as MCP
get_click_economics.
GET /api/v1/workspaces/{workspace_id}/recommendations
Every plan. The recommendation log Axi (the in-panel assistant) and the proactive AI Advisor share, newest first: what was recommended, on which entity, how firmly and in what state. Both write every recommendation they give here, so a later conversation or a later report builds on it instead of contradicting it. No conversation content and no user is ever returned.
source(axioradvisor) andauthor_role(owner,member,viewer,agency, orsystemfor the Advisor).platformandentity(type,external_id,label): what the recommendation is about. Labels are names from your ad platforms; treat them as untrusted text.intent(budget_up,budget_down,pause,enable,exclude_terms,add_terms,new_creative,keep,wait,other),action(one short imperative) andmagnitude_percent(the budget change,nullwhen none).confidence(high,test_first,wait) andrationale_kind(measuredwhen it rests on fetched figures,hypothesiswhen it rests on an assumption not yet verified).group_id: the two rows of one budget move between two entities share it.status:open,superseded(a newer recommendation of the same source on the same entity replaced it),expired(pastvalid_until) ordismissed;merchant_verdict:useful,not_useful,will_applyornull;valid_untilandcreated_onare days in the workspace timezone.
Query parameters: status (open, the default, lists the recommendations still valid
today; all lists every status), since (the first day, YYYY-MM-DD, at most
180 days back; default 30 days ago), platform (one platform key, for example
google_ads) and limit (1 to 100, default 50). Same operation as MCP
list_recommendations.
GET /api/v1/workspaces/{workspace_id}/orders/{order_id}/journey
The visits and ad clicks that led to one order, oldest first (up to the 100 most recent before the order). The
order block gives the order id, time and value; each entry of touchpoints gives
occurred_at, days_before_order, channel, the UTM parameters
(utm_source_label, utm_medium_label, utm_campaign_label,
utm_id_label), google_ads_campaign_id_label, meta_campaign_id_label,
click_ids (type and value, for example gclid or fbclid, only the ones the
visit arrived with), carried_click_ids (the same shape: ids the browser kept from an earlier ad click,
which are sent to the ad platform with conversions but are not the source of this visit),
landing_url_label, referrer_label and the flags is_first_click,
is_last_click and is_data_driven_winner, and touchpoint, the
touchpoint object of the visit (also on order.source, the source the store
recorded with the order). attribution.data_driven carries the
data-driven verdict: status, winning touchpoint, confidence and reasons. Never buyer contact details, IP addresses
or visitor ids. Scope journeys:read; order_not_found when the workspace has no such order.
The touchpoint object
Orders, order journeys, leads and converted leads describe a visit or touch the way the panel shows it, GA4-style:
one source / medium pair in one channel group, with its campaign. The same classification feeds the
traffic reports, so a touch has the same channel group everywhere.
| Field | Description |
|---|---|
source_label, medium_label | For example google and cpc, google and organic, ceneo.pl and referral. A direct visit is (direct) and (none), a tag without a medium has (not set), a manual attribution to organic search, referral or email has the source (manual). These tokens are the same in every language. |
source_medium_label | Both in one string: google / cpc. |
channel_group, channel_group_label | paid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants or other, and its English name. |
platform | The ad platform of a paid touch (google_ads, microsoft_ads, openai_ads, criteo_ads, facebook, tiktok, x_ads, pinterest_ads, linkedin_ads, snapchat_ads, reddit_ads), otherwise null. |
campaign_name_label, campaign_id_label | The campaign: its name as synced from the ad platform when the touch carries a campaign id the platform named, else the UTM campaign; and the platform's campaign id when there is one. Null when unknown. |
inferred | True for a touch inferred from another visitor on the same IP address. |
GET /api/v1/workspaces/{workspace_id}/conversion-paths
For the orders of the range, the sequence of the buyer's visits in the 365 days before each order, with
consecutive repeats merged, for example meta_paid -> google_paid -> direct. Returns
paths (steps, orders, share of orders, revenue, average days to convert, average visits), a
first_to_last matrix, and the distributions touch_count_distribution and
days_to_convert_distribution. Orders without any tracked visit form the path untracked.
| Parameter | Description |
|---|---|
range, from, to | As for the reports: a preset or a custom range of up to 180 days. |
path_by | channel (default: google_paid, meta_organic, email, direct...) or platform (google, meta, direct...). |
limit | Most paths to return, 1 to 100 (default 25). |
min_orders | Leave out paths with fewer orders (default 1). |
Never call the API from a browser page or a mobile app with your token in it: anyone could copy it and read your reports. Call it from your server, a script or a scheduled job.
Tracking check
The tracking check of the Connections page for one ad platform. It is read only: AxiTrace never changes anything
in an ad account, on any platform. The merchant pastes the template into their ad account themselves. The MCP
twin is get_tracking_setup_status.
GET /api/v1/workspaces/{workspace_id}/integrations/{platform}/tracking
Scope integrations:read. platform is google_ads, facebook,
microsoft_ads, tiktok, openai_ads or criteo_ads. Answers connected,
scheme (template, template_location, owned_keys),
tracking (as in .../integrations, null when not
connected) and next_step.
Surveys, questions, templates and rewards
REST API v1 and MCP support every survey builder field, template content, question editing, targeting, scheduling, preview, publication, rewards and results. See the complete Survey API and MCP reference for endpoints, tools, nested JSON schemas, an example and the first-response content lock. Explicitly grant surveys:read for definitions and visitor-written results, and surveys:write for authoring.
Leads, notes, statuses and lead orders
REST API v1 and MCP read the Leads page with every filter, one lead with its notes and conversion, the lead KPIs, statuses and sources, and add notes, change statuses, exclude leads and create or link the order of a lead. See the Leads API and MCP reference. Explicitly grant leads:read, lead_contacts:read for contact details and leads:write for changes.
Creating and changing custom events
With the custom_events:write scope a token can set up
custom events: create them, change them and switch them on or off. A write goes through exactly the same checks as the
Custom Events page of the dashboard, and AxiTrace starts accepting the event within seconds. Custom events cannot
be deleted through the API, because a deleted key can never be used again: delete them in the dashboard.
- Opt-in.
custom_events:writeis never granted by default. On the consent page it is an extra box the user must tick, and on the personal access token form it starts unticked. Tokens created before it existed stay read-only. - Edit right. The token's user needs a role that may edit the workspace settings (the owner, or
a team member with edit rights); otherwise a write answers
403 workspace_read_only. Demo workspaces are read-only too. - Plan. Custom events are included in the Pro, Business and Custom plans, each with a limit of
active custom events. A create or a switch-on above the limit answers
403 plan_required. - Destinations must be platforms connected to the workspace (see
.../integrations). - A write that breaks a rule answers
422 validation_failedwith every field to fix inerrors, and saves nothing.
POST /api/v1/workspaces/{workspace_id}/custom-events
Creates a custom event and answers 201 with the saved event (as GET .../custom-events/{key}
returns it), result: "created" and registry_synced (false means saved, and
live within 20 minutes). The JSON body:
| Field | Meaning |
|---|---|
key | Required. What your code sends with trackCustom() and what reports show: a lowercase letter, then lowercase letters, numbers and underscores, up to 40 characters. Cannot be changed later. |
display_name | Required. The readable name, up to 80 characters. |
description | Optional note, up to 255 characters. |
kind | engagement (default) or conversion. Only conversion events can be sent to Google Ads and LinkedIn and carry a value. Cannot be changed later. |
throttle_mode, throttle_seconds | once_per_page_view, once_per_session, every_n_seconds (with throttle_seconds, 1 to 3600) or unlimited. Defaults to the kind's default. |
properties | Up to 10 declared properties, each {"name", "type", "label"} with type string, number or boolean. |
triggers | Up to 5 no-code triggers, each {"type", "selector", "props", "hover_ms", "visible_ms"}: type is click, hover or visible, selector a CSS selector. props fill declared properties from the element: {"property", "source", "attr"} with source data, href or closest. Leave it empty for an event sent only from code. |
destinations | An object keyed by platform (facebook_capi, tiktok_events_api, google_analytics_4, google_ads, reddit_capi, openai_ads, pinterest_capi, linkedin_capi, microsoft_ads) with the fields that platform takes, the same ones as the forwarding section of the dashboard form. The OpenAPI document lists them per platform. |
default_value, default_currency | Conversion events only: the value used when the event carries none. |
curl -X POST https://axitrace.com/api/v1/workspaces/WORKSPACE_ID/custom-events \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "size_guide_open",
"display_name": "Size guide opened",
"triggers": [{"type": "click", "selector": "button.size-guide"}],
"destinations": {"google_analytics_4": {"event_name": "size_guide_open"}}
}'
PATCH /api/v1/workspaces/{workspace_id}/custom-events/{key}
Changes a custom event. Send only the fields to change, with the same names as for creating it, plus
enabled (true switches the event on and counts against the plan limit, false
switches it off). A field left out keeps its value; properties, triggers and
destinations, when sent, replace the stored list or object whole, so read the event first. To keep an
existing trigger, send its id. Send description as an empty string to remove it.
key and kind cannot be changed. Answers the saved event with result
(updated or unchanged) and registry_synced.
Custom paid channels and their costs
A custom paid channel is a channel AxiTrace cannot sync, such as a comparison site (Favi, Ceneo, Idealo) or an
affiliate network, whose costs the merchant imports. Its traffic rules claim the sessions that belong to it; its
imported spend counts in the Paid scope of the overview, on the traffic source row of its site, in the campaigns
list (platform custom_<uuid>) and in Products. Every write goes through the same
checks as the Channels page and the import page of the dashboard, needs settings:write and a role that
may edit the workspace, and answers 403 workspace_read_only on a demo workspace. Each endpoint has an
MCP twin with the same name as its operation id.
- Rules:
{"v": 1, "groups": [[condition, ...], ...]}. A session matches when every condition of one group matches; the groups are OR. Every group has exactly onesitecondition (equals: a domain,*.domainor autm_sourcevalue), optionallymedium(equalsorin) andcampaign(equals,starts_withorcontains); at most 10 groups of 5 conditions. Native ad platforms are refused. Channels are matched inpositionorder; the first matching channel wins. A rule change applies to all history at once. - Costs are stored net. A channel with gross amounts converts them with its VAT rate; an import
may say
amount_kindgrosswithvat_percent. - A new import of the same days replaces the older one (listed in
spend.replaced); undo brings it back. - Reported conversions (
reported_conversions,reported_conversion_value) are what the channel claims itself and never AxiTrace orders or revenue.
GET /api/v1/workspaces/{workspace_id}/custom-channels
settings:read. Every channel, active ones first in first-match order, then the archived ones:
channel_id, platform, status, name, position,
rules, sites, currency, amounts_are_gross,
vat_rate_bps and preset. GET .../custom-channels/{channel_id} returns one.
POST /api/v1/workspaces/{workspace_id}/custom-channels
Creates a channel (201) from name, rules, currency and
optionally amounts_are_gross, vat_percent and preset. Preview the rules first
with POST .../custom-channels/rules/preview (reports:read, nothing saved): sessions,
orders and revenue the rules would match over the last 30 days, the top campaigns and keys, and traffic an earlier
channel already claims. POST .../custom-channels/{channel_id}/rules/preview previews new rules of an
existing channel.
curl -X POST https://axitrace.com/api/v1/workspaces/WORKSPACE_ID/custom-channels \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Favi",
"currency": "PLN",
"rules": {"v": 1, "groups": [[{"field": "site", "operator": "equals", "value": "favi.pl"}]]}
}'
PATCH /api/v1/workspaces/{workspace_id}/custom-channels/{channel_id}
Changes name, rules (replaced whole), currency,
amounts_are_gross, vat_percent or position (0 first; the others keep their
order). status archived or active archives or restores the channel, in a
request of its own. The answer has the channel before and after.
GET /api/v1/workspaces/{workspace_id}/custom-channels/{channel_id}/report
reports:read, with range or from and to, and model.
The figures of the channel tab: totals, campaigns and days with sessions, orders,
revenue, spend (net, in currency, the reporting currency),
spend_reported and spend_allocated (spend imported for the whole channel is shared out by
sessions per day), roas_covered, orders_covered and revenue_covered (only the days with
imported costs), cpa (spend over orders_covered), cost_per_click or cost_per_session, and the channel's reported
conversions. spend_coverage_until and months_without_spend show how far the costs reach;
preparing is true while the traffic of the range is still being prepared.
POST /api/v1/workspaces/{workspace_id}/custom-channels/{channel_id}/spend-imports
Previews by default: nothing is written unless ?preview=false (or
"preview": false); then 201 with batch. mode
range takes from, to and amount, spread evenly over the days;
mode rows takes up to 5,000 rows, each with day (or
from and to), amount, and optionally campaign (empty is spend of
the whole channel), currency, clicks, reported_conversions and
reported_conversion_value. Campaign labels are paired with the channel's campaigns by earlier
pairings, exact name and the learned prefix only; labels left unpaired become spend-only campaigns and are listed
in unpaired. spend is the preview of the import page: totals before and after VAT, days,
campaigns, replaced imports, warnings, errors and applicable. There is no file upload: import files
in the dashboard. GET on the same path lists the newest 100 imports (settings:read).
curl -X POST "https://axitrace.com/api/v1/workspaces/WORKSPACE_ID/custom-channels/CHANNEL_ID/spend-imports?preview=false" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "range", "from": "2026-10-01", "to": "2026-10-31", "amount": "1250.00", "note": "Invoice 2026/10/118"}'
DELETE /api/v1/workspaces/{workspace_id}/custom-channels/{channel_id}/spend-imports/{batch_id}
Reverts one import and restores what it replaced. An import that a newer import of the same days replaced cannot
be undone before that newer one (422).