Skip to content
Docs

Billing and reporting

Reporting and exports

The reporting engine, report queries, masking for partner and advertiser portals, exports and schedules, and Google Sheets and Looker Studio connectors.

All reports come from one engine (services/reporting, behind the API) over ClickHouse. The dashboard, the report builder, the portals, exports and the API call the same engine, so the same question gives the same number everywhere.

The query

POST /reports/query takes a ReportQuery; POST /reports/summary returns totals only and POST /reports/variance compares periods.

json
{
  "from": "2026-10-01", "to": "2026-10-18", "timezone": "Europe/Berlin", "currency": "USD",
  "granularity": "day",
  "dimensions": ["offer", "partner", "country"],
  "metrics": ["clicks", "conversions", "payout", "revenue", "profit"],
  "filters": [{ "dimension": "country", "op": "in", "values": ["US", "CA"] }],
  "metric_filters": [{ "metric": "clicks", "op": "gt", "value": 100 }],
  "sort": [{ "by": "revenue", "dir": "desc" }],
  "limit": 500,
  "options": { "compare_to": "previous_period", "remove_zero_rows": true }
}
  • The catalogue of dimensions and metrics (over 50 dimensions and 50 metrics, with aliases) is served by GET /reports/sheets-schema. It covers offers, partners, advertisers, goals, geography, device, sources, sub1 to sub15, landing pages, creatives, SmartLinks, time, money and status, plus fraud, lead, call, mobile and SKAN registries.
  • granularity is none, minute, hour, day, week, month, quarter or year; buckets are computed in timezone.
  • currency converts every row with the exchange rates in force (fx_rates); without it rows keep their own currency. In your tenant currency the report adds up the amounts stored on each conversion at the rate of its day, so totals equal the sum of the conversions.
  • options: remove_zero_rows, ignore_fail_traffic, only_fail_traffic, include_on_hold, status_scope, compare_to, and attribution_date (click or conversion).
  • The result is columns (key, label, type), rows (dimension cells carry names; ids are in row_keys), totals, an optional compare block, and next_cursor for paging. Money and percentages are six-decimal strings, counts are integers.
  • The planner picks the fast pre-aggregated table (agg_hourly) when the requested dimensions allow and falls back to the raw tables for rare dimensions, minute granularity or click-date attribution.
  • Restatements are honest. A conversion that changes status moves the numbers of its original day, not today's, and variance reports show the difference.

Logs use the same engine: POST /logs/{type}/search (clicks, conversions, outgoing and incoming postbacks) with keyset paging and a field chooser; GET /logs/{type}/{id} returns the inspector detail, for a conversion its history, postbacks and the inbound request.

SKU and fail-traffic reports

  • SKU report (Insights ▸ SKU report, POST /reports/sku; partners POST /partner/reports/sku, advertisers POST /advertiser/reports/sku): the basket lines of conversions priced per SKU, by SKU (optionally by offer, partner or day) with lines, conversions, quantity, sale amount and approved / pending / rejected payout and revenue by each conversion's current status. A currency column is always part of the key: amounts in different currencies are never added. Partners see payout, advertisers see revenue (their cost).
  • Fail-traffic report (Insights ▸ Fail-traffic report, POST /reports/fail-traffic, network only): clicks that were not accepted and went to a fail-traffic target or fallback, by originating offer, reason and destination, with clicks, unique clicks and the conversions those clicks still produced (gross, approved, rejected, payout, revenue).
  • Media cost: partners allowed to pass cost= on clicks (partner page ▸ Media cost) feed media cost and profit into every report; the cost is converted at the click day's rate.

Masking in portals

POST /partner/reports/query and POST /advertiser/reports/query run the same query with a mask. Partners never see revenue, profit or advertiser columns; advertisers see only what the tenant's visibility settings allow (the partner id only when visibility.partner_id). Masks remove columns and force the entity filter; the numbers that remain are unchanged.

Saved reports, exports and schedules

  • Save and reload definitions with /reports/saved. (Per-user ownership of saved reports needs the user header from the auth layer and may show tenant-wide in some deployments.)
  • POST /reports/export starts an asynchronous export to csv, tsv or json; the finished job returns a presigned link to the file in the exports bucket (DT_REPORTING_S3_*). xlsx and parquet single-report exports are not available yet; for Parquet use the data locker.
  • /reports/schedules e-mails the export link on a cron schedule (daily, weekly or any cron expression) to a list of recipients.

Funnels

Reports → Funnel follows clicks through a sequence of steps: the click, a goal (by goal id or key), any conversion, or an SDK in-app event. A click counts at a step when it reached every earlier step in order within the window (24 hours to 90 days; ClickHouse windowFunnel). The result shows the clicks at each step, the share of the previous step and of step 1, overall and broken down by offer, partner, advertiser, country, device type, OS, source or sub 1 (read from the first step). Export CSV downloads the same table.

The API is POST /reports/funnel with from, to, steps, optional window_hours, breakdown, filters and timezone; add ?format=csv for CSV. The date range applies to the first step; later steps may happen up to the window after it. Goal and conversion steps count approved and pending conversions; test traffic and invalid clicks are left out. Portal masks and the account-manager restriction apply to every step.

Show the API request

Every report in the report builder (admin and both portals) has API request in its header: the exact POST …/reports/query call behind the view on screen, its JSON body and a ready-to-run curl command. Export an API key as METRIXED_API_KEY (admin: Settings → API keys with the reports read scope; partners and advertisers: their account page) and the command returns the same rows, so a script or BI tool can pull exactly what you built.

Data locker

The data locker continuously writes your raw data as Parquet files to your own bucket, for warehouses and data science.

DatasetContentDefault grain
clicksEvery click with geography, device, flags, fraud and money.hourly
conversionsCurrent state of each conversion, restated when statuses change.daily
eventsMobile and in-app events.hourly
postback_logs, postback_in_logsOutgoing and incoming postback logs.hourly
agg_hourlyPre-aggregated hourly facts by offer, partner, geography and sub ids.daily

Configure it in Insights, Exports & data locker or with PUT /data-locker/config: provider (S3, GCS through its S3-compatible API, or MinIO), endpoint, bucket, prefix, access key and secret (the secret is write-only and sealed), datasets, retention days and lag minutes. Layout and guarantees:

  • Files are written per closed partition, with a _manifest.json per dataset listing files, row counts, schema hash and schema version. Schema changes only add columns, and the version increases.
  • Mutable datasets (conversions, agg_hourly) re-check the last 72 hours and rewrite a partition when statuses changed, so your warehouse converges to the same numbers as the reports.
  • POST /data-locker/backfill writes history over a date range (up to 366 days; one backfill per dataset at a time); GET /data-locker/status, /partitions and /backfills show progress and failures.
  • Retention deletes partitions older than the configured days.
  • The poller runs every DT_LOCKER_INTERVAL (default 60 s); DT_LOCKER_DISABLE=1 stops it. Local MinIO needs DT_ALLOW_PRIVATE_DESTINATIONS=1.

BI connectors

  • BigQuery and Snowflake: /connectors loads locker partitions into your warehouse (BigQuery through load jobs, Snowflake through a stage and COPY). A connector's configuration and credentials must pass an offline dry-run before it is saved (POST /connectors/{id}/test, with live=true for an authentication probe); POST /connectors/{id}/run loads what has not been loaded yet and GET /connectors/{id}/runs shows history. Credentials are sealed with DT_SECRETS_KEY. These two connectors have been verified against fakes in this release, not against live accounts.
  • Google Sheets: an Apps Script add-on in integrations/google-sheets (paste Code.gs, Sidebar.html and appsscript.json). Enter the API base URL and an API key with reports:read; pick dimensions and metrics from GET /reports/sheets-schema and relative dates like yesterday or 7d_ago. The key stays in the user's script properties.
  • Looker Studio: a community connector in integrations/looker-studio that reads the same schema and POST /reports/query.
  • Any other BI tool can read the locker files or call POST /reports/query with an API key.