docs / finance

Finance reporting

Four primitives — product, subscription, invoice, expense — plus four read-only finance views give you MRR, ARR, ROAS, CAC, and blended-CAC over any window. Everything is agent-recorded and lives in one local SQLite database.

Primitives

  • product — what you sell. Captured once, referenced by subscriptions and invoices.
  • subscription — recurring revenue, optionally tied to a counterparty. Stores mrrCents denormalized so SUM(mrrCents) WHERE status IN ('active','trialing') is O(1).
  • invoice — a single bill. Tied to a subscription (recurring bill) or standalone, with an optional free-text counterparty.
  • expense — money out. Fields: amountCents, category (ads|infra|contractor|software|tax|fees|salary|...),vendor, source (manual|stripe|bank|google_ads|meta_ads|...),sourceRef (import dedup key).

Summary · MRR · ARR

krabs finance summary --from 2026-05-01T00:00:00Z --to 2026-05-31T23:59:59Z
krabs finance mrr
krabs finance expenses-by-category --from 2026-05-01T00:00:00Z --to 2026-05-31T23:59:59Z

Same three calls over HTTP: GET /v1/finance/summary, GET /v1/finance/mrr, GET /v1/finance/expenses-by-category. Over MCP: finance_summary, finance_mrr, finance_expenses_by_category.

Funnel · ROAS · CAC

finance.funnel aggregates revenue and ad spend over a window and computes:

  • ROAS = paid revenue ÷ ad spend (return on ad spend). Null when ad spend is zero.
  • CAC = ad spend ÷ new customers (customer acquisition cost from paid). Null when nothing converted.
  • Blended-CAC = total expenses ÷ new customers. Includes infra, contractor, software, etc. — the all-in cost per customer.
$ krabs finance funnel --from 2026-05-01T00:00:00Z --to 2026-05-31T23:59:59Z
{
  "period": { "from": "2026-05-01T00:00:00Z", "to": "2026-05-31T23:59:59Z" },
  "revenue":   { "paid_cents": 4821000, "currency": "USD" },
  "ad_spend":  {
    "total_cents": 850000, "currency": "USD",
    "by_source": [
      { "source": "meta_ads",   "total_cents": 620000 },
      { "source": "google_ads", "total_cents": 230000 }
    ]
  },
  "new_customers":      14,
  "roas":               5.67,
  "cac_cents":          60714,
  "blended_cac_cents":  89821
}
how ‘new customers’ is computed
A new customer is a counterparty whose first paid invoice or active subscription falls inside the window. If nothing converted in the window, CAC stays null.

Ingesting ad spend

Ad spend lives in the expense table. Two fields decide where it shows up in the funnel breakdown:

  • category must be "ads".
  • source selects the platform: meta_ads, google_ads, tiktok_ads, etc. Use manual for one-off entries.
krabs expense create \
  --amount-cents 4500 \
  --currency USD \
  --category ads \
  --source meta_ads \
  --source-ref "act_1234567:2026-05-16" \
  --vendor "Meta Ads · Campaign X" \
  --occurred-at 2026-05-16T00:00:00Z

sourceRef is the import dedup key. Re-running the ingestion on the same day with the same key returns the existing row instead of inserting a duplicate. That makes the daily-cron pattern safe.

Meta Ads CLI

Meta shipped an official Ads CLI on 2026-04-29, designed for AI agents — predictable commands, JSON output, defined exit codes. It ships as a Python package (Python 3.12+). krabs does not bundle a Meta-specific adapter; instead the agent (or a small daily cron) calls Meta's CLI, pipes the insights through jq, and records each line as a krabs expense.

# 0. Install Meta's official Ads CLI (one-time)
pip install meta-ads-cli
meta --version

# 1. Pull last 7 days of spend per campaign as JSON
meta ads insights get \
  --date-preset last_7d \
  --fields spend,impressions,campaign_id,date_start,date_stop \
  --format json > /tmp/meta.json
# 2. For each row, record it in krabs (Bash loop, or have your agent do it)
jq -c '.[]' /tmp/meta.json | while read -r row; do
  # Meta returns spend as a decimal-string in account currency — convert to cents
  spend_cents=$(echo "$row" | jq -r '(.spend | tonumber * 100 | floor)')
  campaign=$(echo "$row"    | jq -r '.campaign_id')
  date=$(echo "$row"        | jq -r '.date_start')

  krabs expense create \
    --amount-cents "$spend_cents" \
    --currency USD \
    --category ads \
    --source meta_ads \
    --source-ref "${campaign}:${date}" \
    --vendor "Meta Ads · ${campaign}" \
    --occurred-at "${date}T00:00:00Z"
done

sourceRef = campaignId:date is the dedup key. Re-running the same window is safe — krabs returns the existing row on collision instead of inserting a duplicate.

Same recipe for Google (google-ads-python), TikTok, LinkedIn — change --source to google_ads | tiktok_ads | linkedin_ads. As long as the row lands with category="ads" and a non-manual source, the funnel breakdown lights up automatically.

Open source, self-hosted

All finance endpoints, MCP tools, and CLI commands are part of the open-source core. You run the single account on your own box — same primitives over MCP, HTTP, and CLI, same agent skill. There is no cloud version and no third-party billing integration.

Edit this page on GitHub →last updated 2026-05-17 · v0.5.0