Costs
Two shapes: per-day channel spend (facts) and recurring expenses (specs). Both feed the same metrics layer as the dashboard.
POST /v1/costs/spend
Scope: costs:write. Batch: 1-500 rows.
Daily channel spend lands in ad_insights_daily. Channel labels are auto-namespaced to custom:<label> so they never collide with native integrations (meta, google, ...).
{
"rows": [
{
"channel": "Influencers",
"event_date": "2026-07-12",
"currency": "DKK",
"spend": "1500.00",
"impressions": 10000,
"clicks": 320,
"campaign_id": "summer",
"campaign_name": "Summer"
}
]
}
| Field | Notes |
|---|---|
channel | Merchant label; stored as custom:influencers (slugified) |
event_date | YYYY-MM-DD |
currency | Becomes account_id in the natural key (multi-currency safe) |
spend | Decimal string; push "0" to delete/restate a day |
Optional: impressions, clicks, conversions, conversion_value, campaign/adset/ad ids and names.
Expenses
Thin API over the existing custom costs substrate, keyed by client external_id. UI-created rows (external_id null) are never touched.
Scope: costs:write for all methods.
GET /v1/costs/expenses
List API-managed expenses for the shop.
POST /v1/costs/expenses
Upsert by external_id (idempotent retries).
{
"external_id": "retainer-2026",
"name": "Agency retainer",
"category": "marketing",
"amount": "5000.00",
"currency": "DKK",
"frequency": "monthly",
"started_on": "2026-01-01",
"ended_on": null,
"counts_as_ad_spend": true
}
| Field | Values |
|---|---|
category | software | tools | va | marketing | other |
frequency | monthly | weekly | yearly | one_time | per_order | percent_of_revenue |
counts_as_ad_spend | When true, charge lands in blended ad spend (ROAS/CAC) |
PATCH /v1/costs/expenses/{external_id}
Full-field update of an existing expense. Body shape matches POST; path is the natural key.
DELETE /v1/costs/expenses/{external_id}
Hard-delete the API-managed row. 204 on success, 404 if missing.