Design — Unit economics & cost model
- Date: 2026-05-10 (refresh for ADR-0059 v2.1)
- Sourced from: ADR-0015 D8 (per-query cost), ADR-0018 (cost ceilings), Cloudflare/Anthropic/Cohere public pricing pages
- Conversion rate used: 35 THB / 1 USD
- Status: Source of truth for pricing decisions. Updated whenever a stack component changes price (Anthropic, Cohere, Cloudflare) or a pricing ADR is accepted.
- Pricing model output: ADR-0038 — Pricing & SKU Model (Accepted 2026-05-03), as amended by ADR-0059 — Pricing v2.1 (Proposed 2026-05-10, additive parts shipped). This doc is the cost model; the pricing ADRs are the price models that use it as input.
This doc answers: what does it cost us to serve one query / one document / one tenant, and what is the gross margin per plan? It is the input to ADR-0038. Pricing decisions made without consulting this doc are not legitimate.
1. Per-query cost (the dominant variable cost)
Section titled “1. Per-query cost (the dominant variable cost)”A single chat turn fans out across the RAG pipeline (ADR-0004). Costs:
| Component | Unit price | Per-query usage | Cost |
|---|---|---|---|
| Embedding (bge-m3, Workers AI) | $0 within free tier | 1 query embed | ~$0 |
| Vectorize query | $0.01 / 1M dims queried | 50 chunks × 1024 dims = 51K | ~$0.0000005 |
| D1 reads (history, chunks, tenant) | $0.001 / 1M reads | ~20 reads | ~$0.00000002 |
| D1 writes (msg, telemetry) | $1 / 1M writes | ~5 writes | ~$0.000005 |
| KV reads/writes (rate limit, presence) | $0.50–5 / 1M | ~3 ops | ~$0.000002 |
| Workers requests | $5 / 10M | 1 | ~$0.0000005 |
| Cohere Rerank v3.5 | $2 / 1k searches | 1 | $0.002 |
| LLM — Haiku 4.5 (default) | $1/MTok in, $5/MTok out | 2.5K in + 120 out | $0.003 |
| Total (Haiku, default path) | ~$0.005 |
Sources: ADR-0015 §D8, Anthropic + Cohere + Cloudflare price pages 2026-05.
Two LLM calls add up: if the query rewriter fires (not gated by ADR-0018 §2c), add ~$0.001. If contextual retrieval misses cache, that’s ingest cost, not query cost.
Sonnet 4.5/4.6 path (llm_route override): LLM cost rises to ~$0.009 → total ~$0.011/query. Roughly 2.2× more expensive than Haiku.
2. Per-document ingest cost (one-time, amortized)
Section titled “2. Per-document ingest cost (one-time, amortized)”Per ADR-0004 + ADR-0018 §2a:
| Component | Unit price | Per-doc usage | Cost |
|---|---|---|---|
| Contextual retrieval (Haiku) | $0.0015 / chunk (1K in + 100 out) | ~10 chunks/doc | $0.015 |
| Embedding (bge-m3) | free tier | 10 chunks | $0 |
| Vectorize ingest | included in storage | 10 vectors | $0 |
| D1 + R2 writes | negligible | one doc | <$0.0001 |
| Total | ~$0.015 / doc, one-time |
Storage is essentially free at our scale: 10 chunks × 4 KB Vectorize + ~10 KB D1 + ~1 MB R2 = under $0.0001/doc/month.
Important caveat (ADR-0099). Contextual retrieval is hard-gated at markdown length < 100K chars in apps/app/src/lib/rag/ingest.ts:189. Any document above that size silently skips the contextualization step — the $0.0015/chunk line above only fires on small docs. This is why raising per-plan upload caps to 5/10/25/50 MB (ADR-0099) did not blow up ingest spend: large files were already bypassing the expensive step. The implication is product quality, not cost: large uploads ingest cheaply but retrieve worse than their smaller counterparts. A follow-up ADR will rework contextual retrieval to scale beyond 100K chars with a per-tenant monthly ingest-cost budget enforced before contextualization fires; at that point the $0.0015/chunk figure becomes load-bearing again (roughly $1.50–$5.00 per 50 MB doc at typical chunk counts) and the per-plan budget needs to be modeled here.
3. Per-tenant fixed cost (regardless of usage)
Section titled “3. Per-tenant fixed cost (regardless of usage)”| Item | $/tenant/month |
|---|---|
| Allocated Workers Paid base ($5/mo ÷ tenant count) | <$0.10 at >50 tenants |
| Sentry events allotment | <$0.05 |
| Resend transactional email allotment | <$0.05 |
| Stripe billing fees | 2.9% + ฿10 per invoice |
| Total fixed overhead per active tenant | ~$0.20 |
Inactive tenants (0 queries/month) cost ~$0 — D1+R2 storage is the only ongoing draw, and a dormant tenant uses kilobytes.
4. Gross margin per plan (LLM is the dominant cost)
Section titled “4. Gross margin per plan (LLM is the dominant cost)”Plan limits per billing.ts:31-36. Note: limits in code are interpreted as monthly (error message uses เดือน); marketing page advertises daily. This is a known mismatch — see §7.
At 100% quota utilization, Haiku path:
Section titled “At 100% quota utilization, Haiku path:”| Plan | Price ฿ | Price $ | Queries/mo | LLM cost | Ingest cost (full doc cap) | GM % | GM $ |
|---|---|---|---|---|---|---|---|
| Free | 0 | 0 | 100 | $0.50 | 20 × $0.015 = $0.30 | -∞ | -$1 |
| Team | 3,900 | $111 | 2,000 | $10 | 500 × $0.015 = $7.50 (one-time) | ~91% | $101 |
| Business | 19,900 | $569 | 10,000 | $50 | 5,000 × $0.015 = $75 (one-time) | ~91% | $519 |
| Enterprise (typical contract) | 40,000 | $1,143 | unbounded | risk | unbounded | unknown | unknown |
Ingest is one-time, so on a steady-state monthly P&L only LLM cost recurs. Free is the loss leader; ~$1/month CAC per Free tenant is acceptable.
At 100% quota, doubled conversation length, Sonnet upgrade:
Section titled “At 100% quota, doubled conversation length, Sonnet upgrade:”Worst realistic case: long answers + Sonnet route (Enterprise customer pushes for higher quality).
| Plan | LLM cost | GM % |
|---|---|---|
| Team | 2,000 × $0.011 × 2 = $44 | ~60% |
| Business | 10,000 × $0.011 × 2 = $220 | ~61% |
| Enterprise (10K queries example) | $220 | 81% |
Margin floor stays comfortably above 50% in stress case for Team/Business. Enterprise has no floor — a tenant doing 100K queries/mo on Sonnet costs $2,200 against a $1,143 contract. Negative margin. See §6.
4b. Per-resolution margin (ADR-0038 billable unit, ADR-0059 amended)
Section titled “4b. Per-resolution margin (ADR-0038 billable unit, ADR-0059 amended)”ADR-0038 changes the billable unit from query → resolution (one conversation that ended successfully). A resolution averages 3–5 queries (turns × tool calls; see ADR-0017), so per-resolution LLM cost ≈ $0.015–0.025 on the Haiku path. On Sonnet (~2.2× LLM cost) per-resolution cost is ≈ $0.033–0.055.
Margin at full included quota, 4 queries/resolution average, all-Haiku path. Numbers reflect the ADR-0059 SKU table:
| Plan | Price ฿/mo | Included res | LLM cost @ Haiku | Overage rate | GM % at included |
|---|---|---|---|---|---|
| Free | 0 | 100 | $2 | hard cap | -$2 (CAC) |
| Team | 3,900 | 1,000 | $20 | ฿4/res | ~82% |
| Business | 19,900 | 5,000 | $100 | ฿3/res | ~82% |
| Growth (ADR-0059 §6) | 34,900 | 12,000 | $240 | ฿2.50/res | ~76% |
| Enterprise | Contact sales | 25,000+ | $500 | ฿2/res | ~64% (negotiable) |
Overage economics: at ฿3 (~$0.086) per Business overage resolution and $0.020 LLM cost per resolution, overage GM is ~77%. Growth’s ฿2.50 overage at the same cost = ~77%. Enterprise’s ฿2 overage = ~71%. Overage is healthy revenue, not loss-leader.
Sonnet (ADR-0059 §5) — 2× meter, not 2× overage. Sonnet resolutions count 2× toward the included pool, then bill at the standard plan overage rate (no separate doubling). A Business customer using 100% Sonnet exhausts the included pool at 2,500 resolutions — same revenue, ~10% higher LLM cost ($110 vs $100), still ~81% GM. Cleaner customer mental model than the old “free Sonnet under quota, 2× overage above”; closes the margin-erosion edge case where heavy-Sonnet usage stayed under cap forever.
Team seat add-on (ADR-0059 §2) — near-100% GM line item. Each ฿800/seat/month carries ~$0 marginal cost (one D1 membership row, Workers requests are below threshold). A Team customer at max add-on (5 extra) pays an additional ฿4,000/mo at >99% GM, taking the all-in to ฿7,900/mo while the cap is still 60% below Business — protects the Business upgrade incentive at the upper end of the Team band.
If avg turns/resolution doubles (long compliance Q&A), GM drops ~10pp at every tier. Still safe across all four paid tiers.
5. What we charge for vs. what we don’t
Section titled “5. What we charge for vs. what we don’t”Meter (pay scales with this) — per ADR-0038 + ADR-0059:
- Resolutions / month (primary; Sonnet routes count 2× per ADR-0059 §5)
- API queries / month (secondary; 1 API query = 0.2 resolution equivalent)
- Documents stored
- Seats (admin users); Team gets a per-seat add-on at ฿800/seat for seats 6–10 (ADR-0059 §2)
- Workspaces (per ADR-0039, pending)
Floor (free at every plan, capped only for abuse prevention):
- Allowed origins (CORS domains)
- API requests (counted as queries when they hit
/api/chat; otherwise free) - Widget loads / page views
- D1+R2 storage under generous thresholds (no plan currently hits caps)
- Conversations stored (retention enforced, count not metered)
The principle: charge on the axis that scales LLM spend (queries) or perceived value (docs, seats, workspaces). Don’t invent meters on free-to-serve resources — customers smell it.
6. Danger zones
Section titled “6. Danger zones”6a. Enterprise has no usage cap
Section titled “6a. Enterprise has no usage cap”Status: addressed by ADR-0038 (Proposed) — Enterprise gets 25K included resolutions + ฿2 overage with negotiated quotas_json overrides per contract; AI Gateway hard cost cap remains belt-and-suspenders.
PLAN_META.enterprise.queries = -1 (billing.ts:35). One enterprise customer running 200K queries/mo on Sonnet costs $4,400 in LLM spend against a ~$1,143 baseline contract.
6b. Free-tier abuse via tenant chaining
Section titled “6b. Free-tier abuse via tenant chaining”A bad actor could create N Free tenants, each consuming up to $1/mo, to extract free LLM access. Partially mitigated by:
- Email-based signup (one tenant per verified email)
- Anonymous rate limit 5/h (ADR-0018)
- Free monthly cap of 100 queries
Not yet mitigated:
- Same-org multi-account creation (need device fingerprint or domain-based dedup)
- Programmatic tenant creation via API (need CAPTCHA or admin approval)
6c. Sonnet upgrade gate
Section titled “6c. Sonnet upgrade gate”Status: closed. ADR-0038 §4 gated Sonnet to Business+; ADR-0059 §5 replaced the “double the overage rate” rule with “Sonnet resolutions count 2× toward the included pool” (sonnetWeightedResolutionCount() in billing.ts). Heavy-Sonnet usage now visibly draws down quota instead of staying free under cap.
tenants.llm_route defaults to 'gateway-anthropic' (Haiku); switching to a Sonnet route requires a Business+ tier (gated in getEffectiveQuotas).
6d. Long-context blow-up
Section titled “6d. Long-context blow-up”If conversation history grows unbounded, messages_in rises with each turn. Currently capped by ADR-0017 sliding window, but window size is not plan-gated. Worth verifying that Free tier window ≤ 8 turns to keep tokens-per-turn bounded.
7. Known mismatches to fix before next pricing change
Section titled “7. Known mismatches to fix before next pricing change”| Problem | Found in | Status |
|---|---|---|
PLAN_META.queries is monthly in code, but pricing page advertises daily (15× more generous) |
billing.ts:33-34 vs pricing.astro:36,59 | Resolved by ADR-0038 (Proposed) — billing unit = monthly resolutions; daily soft cap remains as abuse layer per ADR-0018 |
| Daily soft caps in ADR-0018 (500/5,000) ≠ monthly hard caps in code (2,000/10,000) | rate-limit.ts vs billing.ts | Resolved by ADR-0038 §1 — two-layer model documented: daily = abuse, monthly = billing |
Cache-Control envelope assumes ~50 tenants; recompute fixed-cost share at 5, 50, 500 |
§3 above | Open — refresh when tenant count crosses milestones |
No per-tenant quotas_json override yet |
Schema | Resolved by ADR-0038 — Schema additions (Proposed); shipped during ADR-0038 implementation |
8. Sensitivity table (quick lookups)
Section titled “8. Sensitivity table (quick lookups)”If LLM cost changes by 2×, these are the resulting Team/Business margins (queries-only, full quota):
| LLM $/query | Team GM % | Business GM % |
|---|---|---|
| $0.005 (Haiku, current) | 91% | 91% |
| $0.010 (Haiku × 2 — long convs) | 82% | 82% |
| $0.011 (Sonnet) | 80% | 81% |
| $0.020 | 64% | 65% |
| $0.030 | 46% | 47% |
| $0.050 | 10% | 12% |
Survival floor: ~$0.045/query. Above this, Business plan goes underwater at full utilization. Any future LLM choice must be benchmarked against this number.
9. What this doc does NOT cover
Section titled “9. What this doc does NOT cover”- Sales & marketing CAC — not yet measurable; will be added once we have ≥10 paying customers.
- Support cost per ticket — currently zero (founder-led). Will become real at scale.
- Compliance overhead for regulated tenants (SOC 2 audit cost amortization, DPO time) — Enterprise-only, modeled separately when first such contract signs.
- Annual discount math (PRD mentions 20%) — should be computed against this doc’s GM floor before honored.
10. Update protocol
Section titled “10. Update protocol”This doc must be updated when any of the following changes:
- Anthropic, Cohere, or Cloudflare changes a price we depend on
- We change
llm_routedefault or add a new model - We change plan quotas in
PLAN_META - Tenant count crosses 10 / 50 / 500 (fixed-cost share recomputes)
- We sign an Enterprise contract with custom quotas (record the negotiated floor)
- A pricing ADR is accepted (refresh §4b, §5, §6 cross-references)
- Pilot mode ends (first paying customer signs up — ADR-0038 §6 protocol kicks back in, grandfathering math becomes load-bearing)
Owner: founder. Reviewer: any engineer touching billing.ts or rate-limit.ts.