What each conversion cost you, and what it earned back
Conversions tell you what worked. Spend tells you what it cost. Add what you spent per channel (and, if you like, per campaign) and the Attribution report adds a Spend column, a Cost / event column for every event, and ROAS for events that carry a value. GeoSwap doesn't connect to ad platforms, so spend comes from you: type it in, upload a CSV, or push it from a warehouse with the API.
Open Attribution and click Spend. You get a grid with one row per channel (or channel and campaign) and one column per week. Click Add row, pick a platform (or Custom with a channel and an optional name), optionally type the campaign's utm_campaign, fill in the weeks and save.
Exporting from an ad platform or a spreadsheet? Use Upload CSV on the Spend page. GeoSwap checks the whole file first and shows what would change, or every line that needs fixing. Nothing is imported until the file is clean, and uploading the same file twice changes nothing.
period,channel,campaign,amount,currency
2026-10-05,Paid search,spring,1250.00,
2026-10,Paid social,,4000,
2026-10-12,Direct mail,fall-postcard,800,| Column | What goes in it |
|---|---|
period | A Monday as YYYY-MM-DD for that week (weeks run Monday–Sunday, UTC), or a month as YYYY-MM. Also accepted as week, week_start, month or date. |
platform | Optional. A built-in ad platform (Meta, Google Ads, Microsoft Ads, TikTok, LinkedIn, Reddit, Pinterest, Snapchat or X) or Custom. Empty means Custom. A platform row can leave channel empty: it books to that platform's own channel. |
name | Optional, Custom rows only (up to 60 characters): what the row is called, like Local radio. |
channel | One of the channel names in the report, like Paid search, Paid social or Direct mail. Case doesn't matter. Required for Custom rows; a built-in platform's channel is fixed, so a different one is an error. |
campaign | Optional. The utm_campaign the money belongs to, matched ignoring case. Leave it empty for channel spend that isn't tied to one campaign. |
amount | Digits with up to 2 decimals, like 1250.00 or 1,250.00, without a currency symbol. 0 removes that week or month. Also accepted as spend or cost. |
currency | Optional. If present it must match the workspace's spend currency. |
POST /v1/spend takes the same columns as the CSV, with currency once at the top level instead of per row, up to 5,000 rows per request, all or nothing. Sending the same body twice changes nothing, so a nightly sync can resend its whole window. dry_run: true checks it without writing, and GET /spend reads it back. Both need an API key with the spend:write scope.
curl -X POST https://api.geoswap.co/v1/spend \
-H "Authorization: Bearer $GEOSWAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"rows": [
{
"period": "2026-10-12",
"channel": "Paid search",
"campaign": "fall_promo",
"amount": 1200.5
},
{
"period": "2026-10",
"channel": "Direct mail",
"amount": "4,000.00"
}
]
}'Field details: API reference.
Spend shows in Overview's headline and top channels, on Channels (grouped by channel or campaign, and in Compare models switched to ROAS), and in the channel and campaign CSV exports (with spend, <event>_cost and <event>_roas columns). Spend is per channel and campaign, so there is no cost per short link.
The report shows every number in your workspace's reporting currency. If your spend is in a different currency, each week is converted at the European Central Bank reference rate for its Monday (or the last business day before it), so ROAS compares like with like. The Spend page keeps your amounts as you entered them.
Overview, Channels, Journeys and looking up a conversion, page by page.