Hopp til innholdet

DocumentationCosts per customerMarkdown

What Snakkbar costs: per customer, and the report for the cost portal

Snakkbar runs on four things that cost money: Vercel (the app, its functions and builds), Supabase (database and files), the AI Gateway (every model call) and the domain. The system owner sees what each customer costs under Systemeier → Kunder (cost and result beside revenue) and Systemeier → Kostnader (a month by service and customer, and the fixed costs). The same figures go to the cost portal that gathers every product's costs.

Where each cost comes from

CostSourcePer customer
AI GatewayThe cost the gateway reports on every call (providerMetadata.gateway.cost), metered in tenant_usageExact: the website assistant, Pixel, other calls (translation, portraits) and embeddings
Vercel, the project's usageVercel's billing API (FOCUS export, GET /v1/billing/charges), filtered to the Snakkbar project, fetched nightly into infra_costsSplit by each customer's server time
Vercel, buildsThe same, services whose name has «Build»Equally over the customers that exist that day: deploys are no customer's doing
SupabaseFixed costs (plan share, compute), set in Systemeier → Kostnader: Supabase has no invoice APIHalf by server time, half by data (the customer's rows and files)
Domain and other subscriptionsFixed costsEqually, unless set otherwise

Fixed costs have an amount in USD or NOK per month or year, a start and (optionally) an end. A year counts as 1/365 a day, a month as 12/365. Each says how it is split: equal, usage (server time) or data (half server time, half data). The migration seeds estimates (Supabase Micro compute, a quarter of the Pro plan after its compute credit, the domain at Vercel's renewal price, a quarter of the Vercel Pro seat): adjust them to the invoices.

USD becomes NOK at Norges Bank's latest rate (server/economics.ts, the same rate as the model costs).

The keys

  • Server time: every request to /api/v1 that belongs to a customer (a visitor's, an operator's or a server API key's) is timed by meterRequests (server/request-meter.ts); a streamed answer counts until its last byte. Counted per instance and written every 15 seconds as tenant_usage.api_requests and api_ms. Requests that belong to no customer (the public site, the jobs) are not counted, so the project's cost is split over what customers used.
  • Data: the nightly job measures each customer's rows in every table with a tenant_id (pg_column_size, so embeddings count) and their files in Storage (tenant_data_bytes). A day without a measurement uses the latest one before it.
  • A day with no measured use falls back to equal shares. A day without any customer leaves the cost unallocated (customer null), so the total always adds up.

The split is in server/cost-allocation.ts, pure and unit-tested (server/test/cost-allocation.test.ts).

The nightly job

GET /api/v1/internal/costs (Vercel Cron, 04:17 UTC, CRON_SECRET), also Hent nå in Systemeier → Kostnader and POST /api/v1/platform/costs/sync with a platform key:

  1. Fetches the last four days of Vercel charges (recent days are revised) and replaces them in infra_costs. Needs SNAKKBAR_VERCEL_TOKEN: a Vercel access token for the team that can read billing. Without it the job skips this step and Kostnader says so.
  2. Measures each customer's data for today.
  3. Sends the last seven days' report to the cost portal, when COSTS_PORTAL_URL and COSTS_PORTAL_TOKEN are set (POST, Authorization: Bearer <token>, the report as the body).
Variable
SNAKKBAR_VERCEL_TOKENVercel token with access to the team's billing
SNAKKBAR_VERCEL_TEAMTeam id (default the Sevenfold team)
SNAKKBAR_VERCEL_PROJECTProject id when VERCEL_PROJECT_ID is not set (default the Snakkbar project)
COSTS_PORTAL_URL, COSTS_PORTAL_TOKENWhere the nightly report goes

The report: sevenfold-costs/v1

GET /api/v1/platform/costs?from=2026-11-01&to=2026-11-30 with a platform key (Authorization: Bearer sk_platform_…, made under Systemeier → Tilbakemeldinger) or as a signed-in system owner. Default: the last 30 days; at most 399 days. The nightly push sends the same body.

json
{
  "schema": "sevenfold-costs/v1",
  "product": { "id": "snakkbar", "name": "Snakkbar" },
  "from": "2026-11-01",
  "to": "2026-11-30",
  "generatedAt": "2026-12-01T04:17:12.000Z",
  "currency": "NOK",
  "exchangeRate": { "usdNok": 10.12, "date": "2026-11-30", "source": "norges-bank" },
  "lines": [
    { "date": "2026-11-01", "customer": { "id": "amatec", "name": "Amatec" }, "provider": "ai-gateway", "service": "Website assistant", "basis": "direct", "amount": 1.4231, "amountUsd": 0.140623 },
    { "date": "2026-11-01", "customer": { "id": "amatec", "name": "Amatec" }, "provider": "vercel", "service": "Fluid Provisioned Memory", "basis": "usage", "amount": 0.6012, "amountUsd": 0.059407 },
    { "date": "2026-11-01", "customer": { "id": "amatec", "name": "Amatec" }, "provider": "supabase", "service": "Supabase compute (Micro)", "basis": "data", "amount": 1.6633, "amountUsd": 0.164358 },
    { "date": "2026-11-01", "customer": { "id": "amatec", "name": "Amatec" }, "provider": "domain", "service": "Domene snakkbar.app", "basis": "equal", "amount": 0.2079, "amountUsd": 0.020548 }
  ],
  "revenue": [{ "date": "2026-11-01", "customer": { "id": "amatec", "name": "Amatec" }, "amount": 32.26 }],
  "sources": {
    "vercel": { "configured": true, "project": "prj_…", "fetchedThrough": "2026-11-30" },
    "fixedCosts": [{ "name": "Domene snakkbar.app", "provider": "domain", "amount": 15, "currency": "USD", "per": "year", "split": "equal", "startsOn": "2026-10-12", "endsOn": null }]
  }
}
  • lines: one per day, customer, provider and service. amount is NOK, amountUsd the same in USD. provider is vercel, supabase, ai-gateway, domain or other.
  • basis: direct (metered for that customer), usage, data, equal (how a shared cost was split), unallocated (no customer that day).
  • revenue: the subscription price accrued per day (NOK), per customer.
  • The portal should treat a report as the truth for its product and dates: a later report for the same days replaces the earlier lines (Vercel revises recent days).

Other products can send the same format with their own product.id; the portal adds what no single product sees on its own, such as the Vercel team's Pro seat and the Supabase organisation's plan, and splits them between products.