View as Markdown

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"
    }
  ]
}
FieldNotes
channelMerchant label; stored as custom:influencers (slugified)
event_dateYYYY-MM-DD
currencyBecomes account_id in the natural key (multi-currency safe)
spendDecimal 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
}
FieldValues
categorysoftware | tools | va | marketing | other
frequencymonthly | weekly | yearly | one_time | per_order | percent_of_revenue
counts_as_ad_spendWhen 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.