Cost import & sync

Daily spend per campaign via CSV, the costs API or automatic sync from Google Ads, Meta, TikTok and Apple Search Ads — the basis of CPI and ROAS.

Updated 2026-09-02

Costs API#

Shell
curl -X POST https://app.whichclick.is/api/v1/costs \
  -H "Authorization: Bearer wc_live_…" -H "Content-Type: application/json" \
  -d '{"rows":[{"campaign_external_id":"2098765432","day":"2026-09-01","spend":1240.5,"currency":"USD","impressions":91000}]}'
# {"ok":true,"upserted":1}

Rows are upserted on (campaign, day); up to 5000 per call. Reference campaigns by WhichClick campaign_id or by the ad platform's id in campaign_external_id. Rows for unknown campaigns are skipped (not an error) — create the campaign first or set its externalId.

CSV import#

Integrations → Cost import accepts campaign_external_id,day,spend,currency,impressions with the same semantics.

Automatic sync#

Connect a platform under Integrations; credentials are encrypted at rest (CREDENTIALS_KEY) and the scheduler (pnpm scheduler or your cron) pulls the previous day every morning, backfilling 7 days.

PlatformSourceGranularity
Google AdsGAQL campaign report (metrics.cost_micros, impressions)Campaign / day
MetaMarketing API Insights (spend, impressions)Campaign / day
TikTokReporting API (spend, impressions)Campaign / day
Apple Search AdsCampaign reports (localSpend, impressions)Campaign / day
AppLovinAdvertiser reporting (r.applovin.com/report)Campaign / day
Unity AdsStats API v2 acquisitions reportCampaign / day
ironSource (LevelPlay)Advertiser reporting API v2Campaign / day
MintegralAdvertiser reporting APICampaign / day

Sync runs are listed with status and row counts; failures raise an alert.

Metrics derived from cost#

MetricFormula
CPCspend / valid clicks
CPIspend / attributed installs
CPAspend / conversions
ROAS(conversion value + purchase, subscription and ad-revenue events) / spend
Saved spend (Protect)invalid clicks × average CPC

Costs are shown in the currency you send; no FX conversion is applied. Synced rows always carry the currency reported by the partner API — a row without one aborts the run instead of assuming USD.