Record ad spend and see ROI
Turn a link's revenue into profit, ROI, ROAS, and CPA by giving it a cost model.
A link's revenue is tracked automatically. Its cost is not — you tell the system what you're spending, and the Performance card pairs the two into a bottom line.
Set the cost
POST /api/admin/links/cost:
| Field | Notes |
|---|---|
link_key | The cost_key the list row carried. |
cost_model | flat, cpc, cpa, or cpm. |
amount_cents | Used by flat — a lump sum for the window, not prorated. |
rate_cents | Used by cpc / cpa / cpm. |
platform, period_start, period_end, note | Bookkeeping fields. |
This upserts one spend row per (link, platform, period_start) — posting again for the same slot
updates it rather than adding a duplicate.
How each model prices the window
| Model | Cost |
|---|---|
flat | amount_cents, clamped ≥ 0. |
cpc | rate_cents × clicks |
cpa | rate_cents × subscribers (acquisition = a new subscriber) |
cpm | round(rate_cents × impressions / 1000) |
An unrecognized model contributes 0 cost.
What comes out
| Metric | Formula | None when |
|---|---|---|
profit_cents | revenue − cost | never — see below |
roi_pct | (revenue − cost) / cost × 100 | cost == 0 |
roas_bps | revenue / cost × 10000 (basis points; divide by 100 for a "×" figure) | cost == 0 |
cpc_cents | cost / clicks | clicks == 0 |
cpa_cents | cost / subscribers (the realised CAC — not the cpa input rate) | subscribers == 0 |
epc_cents | revenue / clicks | clicks == 0 |
margin_pct | profit / revenue × 100 | revenue == 0 |
profit_cents is deliberately not clamped at zero. When ad spend exceeds revenue, profit is
negative — the system shows you the real loss instead of hiding it at "$0".
has_cost is true only once cost_cents > 0 — that's the flag the panel uses to light up ROI at
all, instead of showing "0%" for a link nobody has priced yet.
What's next
If ROI stays stuck on "needs a price" after setting a cost, see Troubleshooting.