AxiTrace REST API v1 reference: JSON endpoints for workspaces, overview KPIs, traffic sources, campaigns, transactions, integration health, custom events and fraud detection, plus creating and changing custom events, surveys and survey templates. OAuth 2.1 or personal access tokens, response envelope, error codes, pagination, rate limits and versioning.

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.

Not the event ingestion API

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

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.

ItemValue
GrantAuthorization code with PKCE (S256 only). No client credentials, password or implicit grant.
Authorization server metadatahttps://axitrace.com/.well-known/oauth-authorization-server
Protected resource metadatahttps://axitrace.com/.well-known/oauth-protected-resource/api/v1
Authorizehttps://axitrace.com/api/oauth/authorize
Token and refreshhttps://axitrace.com/api/oauth/token (form-encoded body)
Revocationhttps://axitrace.com/api/oauth/revoke
Client registrationA Client ID Metadata Document (an https URL as client_id), or dynamic registration at https://axitrace.com/api/oauth/register
Resource indicatorresource=https://axitrace.com/api/v1 on the authorize and token requests
LifetimesAccess 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

ScopeGrants
workspaces:readThe names and settings of the selected workspaces
reports:readOverview, traffic, traffic sources, organic search, campaigns and products
transactions:readOrders with their value and attributed source, without buyer personal data
integrations:readThe health of the ad platform connections
custom_events:readCustom event definitions
fraud:readFraud detection summaries and verdicts, with addresses masked
journeys:readThe visits and ad clicks (with click ids and UTM parameters) that led to each order, and conversion paths
profit:readProfit, 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:writeCreate custom events and change their name, triggers, properties, forwarding and on/off state. Opt-in, see Creating and changing custom events.
surveys:readSurvey definitions, templates and results, including visitor-written answers. Opt-in.
surveys:writeCreate/edit surveys, questions and templates; manage publication and rewards. Opt-in. See Survey API and MCP.
leads:readLeads 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:readAdds lead contact details, the inquiry message, visitor data and note text, for a role that may see personal data. Opt-in.
leads:writeAdd 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:readThe 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:readThe workspace settings by section and the notes of the Axi assistant. Opt-in. See Settings API and MCP.
settings:writeChange 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:writeChange 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:readRead-only live queries of the connected Meta, Google Ads and TikTok accounts. Opt-in. See Live Ad Queries API and MCP.
transactions:writeExclude 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; null when it is unknown.
  • poas: profit divided by the same ad spend roas divides; null without spend, and always null for 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 outside profit and outside the profit_coverage denominator, so one known order out of ten shows a coverage of 1 with 9 orders left out: read the two together. null when profit is null.
  • Transactions add only profit and cost_quality (actual, partial, estimated or not_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 timezone in the workspace list); timestamps are ISO 8601.
  • Ranges: range is one of today, 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 send from and to (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_days is the N full days ending yesterday: on 2026-10-09, last_7_days is 2026-10-02 to 2026-10-08 (see Versioning). this_week runs from Monday to today, last_week from Monday to Sunday of the previous week. today, this_week and this_month count 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 only last_7_days or last_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 for today and this_week), week_earlier (the same days one week earlier; the default for today and this_week), year_earlier (the same dates one year earlier), custom (as many days from compare_from, ending before today) or none. The answer carries a comparison object {"mode", "from", "to", "available", "model"}, or null for none. available is false when 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_basis complete_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=model with compare_model puts the same days under another model next to each row (compared, with the change in delta); it needs the Business or Custom plan. The overview accepts model and compare=model only with a scope other than all: 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.clamped is true.
  • 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"
}
StatuscodeMeaning and what to do
400invalid_requestA parameter is malformed (for example a bad cursor or limit below 1). Fix the request.
400invalid_rangeThe 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.
400range_too_largeThe custom range is longer than 180 days. Choose a shorter one. (Some workspaces answer this case with invalid_range; handle both.)
401unauthorizedNo token was sent.
401invalid_tokenThe token is malformed, expired or revoked. Create a new token or reconnect.
403insufficient_scopeThe token lacks the scope this endpoint needs.
403plan_requiredThe workspace is on the Free plan, or a fraud endpoint or compare=model was used for a plan below Business.
404workspace_not_foundThe workspace does not exist or the token does not cover it. Check GET /api/v1/workspaces.
403workspace_read_onlyA write was refused: the token's user may view the workspace but not edit its settings, or it is a demo workspace.
404order_not_foundThe workspace has no order with that id. Take order ids from GET .../transactions.
404survey_not_found / survey_template_not_foundThe survey or template does not exist in the selected workspace.
404leads_unavailableThe Leads module is not enabled for this account yet.
404lead_not_found / lead_note_not_found / lead_status_not_foundThe lead, the note replied to or the status does not exist in the selected workspace. Take ids from GET .../leads and GET .../lead-statuses.
404custom_channel_not_foundNo 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.
409lead_conflictThe 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.
404product_not_foundThe workspace has never tracked a product with that key, or product reports are not available yet. Take keys from GET .../products.
404custom_event_not_foundThe workspace has no custom event with that key (or only a deleted one). Take keys from GET .../custom-events.
422validation_failedA write breaks a rule. errors maps every field to fix (for example triggers.0.selector) to its messages; nothing was saved.
429rate_limitedA rate limit was reached. Wait for Retry-After seconds.
503busyToo many live reports are running for this workspace. Retry after Retry-After seconds.
503report_unavailableThe report could not be built right now. Retry in a few minutes.
503temporarily_unavailableLead data could not be read or saved right now; nothing was changed. Retry in a minute.
503api_unavailableThe 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

LimitKeyAllowance
Per tokenOne personal access token or OAuth connection120 requests per minute and 5,000 per day
Per workspaceAll tokens reading one workspace, together120 requests per minute (the workspace list does not count)
Live reports per workspaceReports read live rather than from a snapshot: custom ranges, transactions, fraud20 per minute
OAuth token endpointClient IP address60 requests per minute
OAuth dynamic registrationClient IP address10 per hour and 50 per day
OAuth revocationClient IP address60 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-After on a 429 (and on 503 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 deprecated in the OpenAPI document and keep working until a removal date announced in the changelog, at least 90 days ahead. Deprecated now: comparison.baseline on the overview (read comparison.from and comparison.to) and the compare_previous parameter, which is ignored.
One exception in v1 (2026-10-09)

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.

EndpointScopeReturns
/api/v1/workspacesworkspaces:readThe workspaces the token covers
.../{workspace_id}/overviewreports:readHeadline KPIs and a series
.../{workspace_id}/traffic-sourcesreports:readRevenue, orders, sessions and add-to-carts per source
.../{workspace_id}/trafficreports:readThe Traffic page: visitors, pageviews, purchases and revenue over time, filtered by channel group, source, medium or campaign
.../{workspace_id}/traffic/filter-valuesreports:readThe values each Traffic filter can take, with their sessions
.../{workspace_id}/traffic/breakdownreports:readThe Traffic figures and their change per channel group, source, medium or campaign
.../{workspace_id}/campaign-participationreports:readThe orders a channel group, source, medium or campaign took part in, credited or not, beside what the model credits to it
.../{workspace_id}/klaviyo/performancereports:readKlaviyo campaigns and flows with their Klaviyo figures and the AxiTrace visits and orders they brought
.../{workspace_id}/channels/{group}/overviewreports:readThe 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-healthreports:readThe 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/activityai_credits:readThe AI credit balance of the account and every credit spent and added
.../{workspace_id}/settingssettings:readThe workspace settings by section
PATCH .../{workspace_id}/settings/{section}settings:writeChange data collection, attribution, the timezone or the lead sources
POST .../{workspace_id}/product-labels/rule-sets, .../templates/apply, .../refreshsettings:writeSave Product Labels rules, apply a starter template, refresh the labels now
PATCH .../{workspace_id}/fraud/settings, POST .../fraud/blocks, .../fraud/whitelistfraud:writeChange Fraud Detection, block, unblock or whitelist an IP address
POST .../{workspace_id}/ads/live-queryads_live:readA read-only live query of a connected Meta, Google Ads or TikTok account
.../{workspace_id}/assistant-memorysettings:read, PUT: settings:writeThe notes the Axi assistant keeps about the workspace
.../{workspace_id}/advisor/...reports:read, changes: settings:writeAI Advisor reports, schedule, analyses (paid in AI credits) and feedback
.../{workspace_id}/recommendationsreports:readThe recommendations Axi and the AI Advisor gave the workspace
/api/v1/docs/searchnoneSearch the AxiTrace documentation
.../{workspace_id}/analyticsreports:readAny analytics question: metrics per day, week, month, channel, source, campaign, platform, product or lead status, with filters and a comparison
.../{workspace_id}/analytics/schemareports:readThe metrics, dimensions and filters .../analytics answers
.../{workspace_id}/campaignsreports:readAd campaigns with spend and revenue
.../{workspace_id}/campaigns/click-cohortsreports:readOrders that followed the clicks of one day, counted by click date
.../{workspace_id}/productsreports:readProducts ranked by revenue, units, growth or conversion rate
.../{workspace_id}/products/{product_key}reports:readOne product: KPIs, daily series, funnel, channels, campaigns and attribution models
.../{workspace_id}/transactionstransactions:readOrders, paged
POST .../{workspace_id}/transactions/exclusionstransactions:writeExclude orders from every statistic, or restore them
.../{workspace_id}/integrationsintegrations:readAd platform connection health and the tracking check
.../{workspace_id}/integrations/{platform}/trackingintegrations:readTracking template and check of one ad platform
.../{workspace_id}/custom-eventscustom_events:readCustom event definitions
.../{workspace_id}/custom-events/{key}custom_events:readOne custom event with its configuration
POST .../{workspace_id}/custom-eventscustom_events:writeCreate a custom event
PATCH .../{workspace_id}/custom-events/{key}custom_events:writeChange a custom event, or switch it on or off
.../{workspace_id}/fraud/summaryfraud:readFraud detection counts
.../{workspace_id}/fraud/verdictsfraud:readFlagged addresses, paged
.../{workspace_id}/fraud/timelinefraud:readGoogle Ads invalid clicks and IP exclusions per day, with a compared period
.../{workspace_id}/fraud/refund-claimsfraud:readGoogle Ads refund claims, paged
.../{workspace_id}/fraud/refund-claims/{claim_id}fraud:readOne refund claim: history, evidence summary, form texts
.../{workspace_id}/fraud/refund-claims/{claim_id}/clicks.csvfraud:readThe listed clicks of one claim as CSV, addresses masked
.../{workspace_id}/click-trackerfraud:readGoogle Ads click tracker template and campaign coverage
.../{workspace_id}/click-economicsfraud:readGoogle Ads click economics, click limit simulation and the buyers who clicked many ads
.../{workspace_id}/orders/{order_id}/journeyjourneys:readThe visits and ad clicks behind one order
.../{workspace_id}/conversion-pathsjourneys:readMost 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_profileDescription
platformThe storefront platform (shopify, woocommerce, magento, shopware, prestashop, wix, squarespace, webflow), or null when it could not be told.
ad_platformsAd and analytics vendors whose tags were found on the store: meta, google_ads, google_analytics, tiktok, microsoft_ads.
currencyISO 4217 code, or null.
countryISO 3166-1 alpha-2 code, or null.
languagePrimary language subtag of the store, such as en, or null.
descriptionOne 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_atWhen 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 billingDescription
planThe subscribed plan key, such as free, pro or business.
billing_cyclemonthly or annual; null on the Free plan.
currencyISO 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_minorThe 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.

ParameterDescription
range, from, toThe period. Default last_30_days.
compare, compare_fromWhat 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.
scopeWhose 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.
modelWith a scope: the attribution model of the orders and revenue, default the workspace setting. Refused with invalid_range without a scope.
compare_previousDeprecated 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 the sessions column 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.

ParameterDescription
range, from, toThe period.
compare, compare_from, compare_modelWhat 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.
modelThe attribution model of orders and revenue. Default the workspace setting.
sortrevenue (default), orders, sessions, carts or carts_without_order, highest first.
limitRows, 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, purchases and revenue (money in the reporting currency).
  • series: one point per day (per hour for a one-day period), or per week starting on Monday with grain=week. Each point has from, to, hour (null except on hour points), days, the four figures and partial (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 has compared, the point of the compared period the chart draws next to it.
  • comparison: the compared period (mode, from, to, available), its totals and deltas, the change of each figure as {abs, pct, direction}. Compared visitors of a one-day period are null: hourly unique visitors do not add up to a day's.
  • filter: null without 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, and visitors_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) and recalculating (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.

ParameterDescription
range, from, toThe period. Default last_30_days.
compare, compare_fromWhat the figures are compared with, see Comparisons. compare=model is refused with invalid_range.
channel_groupComma-separated channel groups: paid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants, other.
device, countryComma-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, campaignComma-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.
grainday (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.

ParameterDescription
levelRequired: channel_group, source, medium, campaign, device or country. Countries are named in English.
qOnly values whose value or name contains this text, case-insensitive (at most 200 characters).
range, from, toThe period. Default last_30_days.
channel_group, source, medium, campaign, device, countryNarrow 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.

ParameterDescription
dimensionRequired: channel_group, source, medium, campaign, device or country.
range, from, to, compare, compare_fromAs on .../traffic.
channel_group, source, medium, campaign, device, countryFilter every row, as on .../traffic. For example dimension=campaign&source=facebook lists the Facebook campaigns.
sortsessions (default), visitors, pageviews, purchases or revenue, highest first.
limitRows, 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 figures GET .../traffic shows for that filter.
  • roles: only (the selection was the only touch), first, middle and last (the closing touch, the last visit that was not direct), each with orders and revenue. An order counts in every role the selection played on its path (both first and last when the selection opened and closed it), so the roles can add up to more than participating.
  • 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 with date, participating_orders, participating_revenue, credited_orders and credited_revenue.
  • selection echoes the values the figures are for; in_session_touches_since is the first day a campaign clicked again within one visit is counted (null before the first one is stored, or when the date could not be read: notes says which); missing_rates and recalculating say how complete the figures are; notes explains how to read them.
  • comparison: null unless compare asks for a comparison. Otherwise the compared period (mode, from, to, available) with the same participating, credited, roles, shop_orders and daily figures for it (each day with its own date), and deltas of the participating and credited orders and revenue (abs, pct, null when the compared value is zero, and direction). A compared period that starts before the first tracked day has available: false and null figures.

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.

ParameterDescription
range, from, toThe period, as for the reports: a preset or a custom range of up to 180 days. Default last_30_days.
modelThe attribution model of the credited figures: first_click, last_click or data_driven. Default the workspace setting.
compare, compare_fromThe 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_groupComma-separated channel groups: paid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants, other.
source, medium, campaignComma-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_connected means the workspace has no Klaviyo connection and is an answer, not an error: the report is then empty. The other states are connected, preparing (the traffic data does not cover the period yet), disconnected, reconnect_required, scope_missing, quota_paused and error.
  • dataUntil: the last day Klaviyo statistics were synced, null before the first sync.
  • campaigns and flows: one entry per Klaviyo object with id, name, kind, channel, sendDate, the Klaviyo figures (recipients, delivered, opensUnique, clicksUnique, orders, revenue), the matched AxiTrace figures (visits, orders, revenue, null when nothing matched) and match (status matched or unmatched, the method and the reason). A flow carries its messages. For flows and flow messages, opensUnique and clicksUnique are 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: the klaviyo-tagged UTM campaigns that no Klaviyo object claimed.
  • conversionMetric: the Klaviyo metric whose conversions are read, or null. trackingDetected: false when 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.

ParameterDescription
range, from, toThe period, as for the reports: a preset or a custom range of up to 180 days. Default last_30_days.
kindall (the default), campaigns or flows. The connection status, the unmatched Klaviyo objects and the klaviyo-tagged UTM campaigns come back either way.
modelThe 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, rps revenue per session, organic_share of all revenue, new_customer_share, each with value, previous, delta_pct, direction and low_sample); trend (points per day or ISO week, previous the compared period and yoy the same dates a year earlier, both aligned day by day; yoy is null and yoy_available false when no data is kept from a year earlier, and a day before the data floor has null figures; markers the Google search update ranges); engines (google, bing, duckduckgo, other, and ai_assistants, the AI assistant visits, which are not organic; every row has previous and sessions_delta_pct, orders_delta_pct, revenue_delta_pct with their _direction); ads (daily Google Ads spend beside the organic sessions, the total with previous, total_spend_delta_pct and total_spend_direction).
  • journey: models (organic orders, revenue and share of revenue under first click, last click and data-driven; state locked below 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 also profit and profit_coverage); leads (the organic lead count, only with leads:read); landing (top 10 organic landing pages, the rest, and brand, the share of sessions landing on the home page as an approximation of brand searches, with previous, share_delta_pct and year_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.

ParameterDescription
range, from, toThe period. Default last_30_days.
compare, compare_fromWhat the KPIs are compared with, see Comparisons. compare=model is refused with invalid_range: journey.models already shows the three models.
modelThe attribution model of the orders and revenue. Default the workspace setting.
sectionsComma-separated overview, journey, content. Default all three.
grainday 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.

ParameterDescription
range, from, toThe period. Default last_30_days.
compare, compare_fromWhat 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 need leads: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) and lag_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_engine rows come from the sources report; the customers and order_paths reports come from the Organic search page, cover organic search only for now, need filter[channel_group]=organic_search and have no comparison. When some of their revenue had no exchange rate on the rate date, complete is false and a note names that date.
  • Rows: value (the dimension value; null for totals), value_label, attributes, values, compared and deltas ({abs, pct, direction} per metric). definitions says how each requested metric is counted, rows_matched counts the rows before limit and complete is false when only the top rows of a report were read.
ParameterDescription
metricsRequired: 1 to 8 metrics, comma-separated.
dimensionThe 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_fromThe period and what it is compared with, see Comparisons.
modelThe attribution model of the sources, campaigns, products and customers reports.
sort, order, limitA requested metric or the dimension; asc or desc; 1 to 100 rows (default 25, or 100 for a date dimension).
datasetForce 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 read deltas.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_conversions and platform_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 than orders, because Meta also credits ad views and clicks AxiTrace cannot see. Meta's clicks also count likes, comments and profile clicks; link_clicks holds only the clicks that opened your site (inline_link_clicks). It is null for every other platform, whose clicks already are clicks to your site.
  • AxiTrace side: tracked_clicks, orders, revenue (revenue_basis is always axitrace_attributed), roas, cpa and average_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: carts is the number of distinct visitors who added a product to the cart during the period in a visit counted in the row's tracked_clicks (the same ad click signals and the same campaign matching); carts_without_order is 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 of data.totals. When they cannot be read they are null on every row and data.carts_unavailable / data.carts_unavailable_reason say 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 a sort by either field is refused with 503 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 a sort by either field is refused with 400 invalid_request.
  • Profit (only with profit fields): profit, poas, profit_coverage and profit_orders_left_out on every row and in data.totals, the profit attributed exactly like revenue.
  • Each row also has platform, campaign_id (the ad platform id), campaign_label and status. A row with is_untracked holds 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.

ParameterDescription
range, from, toThe period.
compare, compare_from, compare_modelWhat the campaigns are compared with, see Comparisons and Attribution models. Every row then adds delta and delta_basis; with compare=model also compared.
modelThe attribution model of tracked clicks, orders and revenue. Default the workspace setting.
platformfacebook, 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.
sortspend (default), revenue, roas, orders, carts, carts_without_order, profit or poas, highest first. profit and poas need profit fields.
cursorFrom links.next; leave it out for the first page.
limitCampaigns 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.totals covers every product of the period: revenue, units, products_sold, products_seen, ai_assistants_revenue with its share, and revenue_without_product_data (orders that carried no product lines).
  • With profit fields, rows and totals add net_revenue.
ParameterDescription
range, from, toThe period.
compare, compare_from, compare_modelWhat 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).
modelThe attribution model of orders and revenue. Default the workspace setting.
channel_groupall (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.
viewall (default), sold or viewed_not_sold (viewed but not ordered in the period).
sortrevenue (default), units, growth (revenue change against the compared period) or conversion_rate (orders per product view), highest first.
page, per_pagePage 1 to 200; products per page, 1 to 100 (default 50). Follow links.next; meta.total_count counts the products of the period.
qOnly 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) and variants (the variants of a Shopify parent product);
  • kpis: orders, units, revenue, views, unique_viewers, add_to_carts and view_to_order_rate_pct, with delta against 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;
  • channels and campaigns: who bought the product under the attribution model, each with orders, units, revenue and revenue_share_pct. A campaign also has campaign_spend, the whole campaign's spend in the period, shown for context only;
  • models: on the Business and Custom plans, the product per channel under first_click, last_click and data_driven; every model adds up to the same totals. null on other plans;
  • product_cost: whether per-product ad cost is available and, when the product has any, product: its cost, the product ROAS of the Shopping and Performance Max campaigns that sold it (roas) and the cost per platforms entry. A custom paid channel's entry has platform custom_<uuid>, the channel name in platform_label and its own roas: the revenue its traffic brought for the product divided by its cost for it, null when the channel rules use a medium or campaign. List rows carry the same product_cost (without the catalog roas) 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.

ParameterDescription
range, from, toThe period.
sourceOnly orders attributed to this source_label; direct for orders without a source.
min_valueOnly orders worth at least this amount.
touched_channel_groups, touched_sources, touched_mediums, touched_campaignsOnly 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_roleWith a touched_* value: only the orders where it was the only touch, the first, a middle or the last (closing) touch.
excludedinclude (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.
cursorFrom links.next; easier to follow that URL as it is.
limitRows 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 fieldDescription
actionexclude or restore. Required.
order_idsThe 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).

ParameterDescription
rangelast_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.

ParameterDescription
rangelast_7_days or last_30_days (default).
risk_levelhigh, medium or low; high and medium when omitted.
cursorFrom links.next.
limitRows 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_totals and deltas of clicks, invalid_clicks, invalid_click_rate_pct, impressions, cost, clicks_per_day and invalid_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_now of google_ads_limit) and the exclusions added and removed in 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.

ParameterDescription
range / from, toA preset or custom dates, as for the other reports (at most 180 days). Default last_30_days.
compareprevious_period (default), week_earlier, year_earlier, custom with compare_from, or none.
compare_fromFirst 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.

ParameterDescription
statusOne status, or several separated by commas; every status when omitted.
fromYYYY-MM-DD: only claims whose period ends on or after this day.
toYYYY-MM-DD: only claims whose period starts on or before this day.
cursorFrom links.next.
limitClaims 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.

  • clicks and spend: 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) and cohort_buyers_eligible (the visitors followed).
  • touch_based: every order of those visitors within the window, first seen after their click (orders, buyers, revenue, cumulative orders 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 in overridden_orders; data-driven orders without a verdict yet are counted in pending_model_orders.
  • status: complete, or provisional while 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) and median_lag_days use complete cohort days only.
  • TikTok campaigns have no cohorts: TikTok visits and orders carry no campaign id (status not_available with a reason).

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, p99 and share_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) against break_even_conversion_rate.
  • recommendation: the click limit recommended without_cart and with_cart (status, recommended_limit, net_saving_per_month, buyers_lost_share) at merchant_inputs (gross margin, cost per click), and applied_limits_now.
  • simulation_per_month: one row per limit from 5 to 30 with clicks_stopped_in_window, buyers_lost, revenue_lost_per_month, cost_saved_per_month and net_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 their min_clicks-th or a later paid click (segment) against other_buyers: buyers, first-order revenue, average first-order value, lifetime revenue and value, and the segment's share of buyers and revenue. orders lists 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_buyer and lifetime_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 (axi or advisor) and author_role (owner, member, viewer, agency, or system for the Advisor).
  • platform and entity (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) and magnitude_percent (the budget change, null when none).
  • confidence (high, test_first, wait) and rationale_kind (measured when it rests on fetched figures, hypothesis when 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 (past valid_until) or dismissed; merchant_verdict: useful, not_useful, will_apply or null; valid_until and created_on are 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.

FieldDescription
source_label, medium_labelFor 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_labelBoth in one string: google / cpc.
channel_group, channel_group_labelpaid_search, organic_search, paid_social, paid_display, organic_social, email, referral, affiliates, direct, ai_assistants or other, and its English name.
platformThe 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_labelThe 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.
inferredTrue 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.

ParameterDescription
range, from, toAs for the reports: a preset or a custom range of up to 180 days.
path_bychannel (default: google_paid, meta_organic, email, direct...) or platform (google, meta, direct...).
limitMost paths to return, 1 to 100 (default 25).
min_ordersLeave out paths with fewer orders (default 1).
Keep tokens on the server

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:write 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 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_failed with every field to fix in errors, 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:

FieldMeaning
keyRequired. 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_nameRequired. The readable name, up to 80 characters.
descriptionOptional note, up to 255 characters.
kindengagement (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_secondsonce_per_page_view, once_per_session, every_n_seconds (with throttle_seconds, 1 to 3600) or unlimited. Defaults to the kind's default.
propertiesUp to 10 declared properties, each {"name", "type", "label"} with type string, number or boolean.
triggersUp 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.
destinationsAn 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_currencyConversion 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 one site condition (equals: a domain, *.domain or a utm_source value), optionally medium (equals or in) and campaign (equals, starts_with or contains); at most 10 groups of 5 conditions. Native ad platforms are refused. Channels are matched in position order; 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_kind gross with vat_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).