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.
{
"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,sub1tosub15, landing pages, creatives, SmartLinks, time, money and status, plus fraud, lead, call, mobile and SKAN registries. granularityisnone,minute,hour,day,week,month,quarteroryear; buckets are computed intimezone.currencyconverts 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, andattribution_date(clickorconversion).- The result is
columns(key,label,type),rows(dimension cells carry names; ids are inrow_keys),totals, an optionalcompareblock, andnext_cursorfor 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; partnersPOST /partner/reports/sku, advertisersPOST /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/exportstarts an asynchronous export tocsv,tsvorjson; the finished job returns a presigned link to the file in the exports bucket (DT_REPORTING_S3_*).xlsxandparquetsingle-report exports are not available yet; for Parquet use the data locker./reports/schedulese-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.
| Dataset | Content | Default grain |
|---|---|---|
clicks | Every click with geography, device, flags, fraud and money. | hourly |
conversions | Current state of each conversion, restated when statuses change. | daily |
events | Mobile and in-app events. | hourly |
postback_logs, postback_in_logs | Outgoing and incoming postback logs. | hourly |
agg_hourly | Pre-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.jsonper 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/backfillwrites history over a date range (up to 366 days; one backfill per dataset at a time);GET /data-locker/status,/partitionsand/backfillsshow progress and failures.- Retention deletes partitions older than the configured days.
- The poller runs every
DT_LOCKER_INTERVAL(default 60 s);DT_LOCKER_DISABLE=1stops it. Local MinIO needsDT_ALLOW_PRIVATE_DESTINATIONS=1.
BI connectors
- BigQuery and Snowflake:
/connectorsloads locker partitions into your warehouse (BigQuery through load jobs, Snowflake through a stage andCOPY). A connector's configuration and credentials must pass an offline dry-run before it is saved (POST /connectors/{id}/test, withlive=truefor an authentication probe);POST /connectors/{id}/runloads what has not been loaded yet andGET /connectors/{id}/runsshows history. Credentials are sealed withDT_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(pasteCode.gs,Sidebar.htmlandappsscript.json). Enter the API base URL and an API key withreports:read; pick dimensions and metrics fromGET /reports/sheets-schemaand relative dates likeyesterdayor7d_ago. The key stays in the user's script properties. - Looker Studio: a community connector in
integrations/looker-studiothat reads the same schema andPOST /reports/query. - Any other BI tool can read the locker files or call
POST /reports/querywith an API key.