1---2title: Costs and margins3audience: developer4---56# Costs and margins78How applications budget a tenant's relay usage, interpret fuel meters and9control storage growth. Fuel prices the relay's measured resources; hosting10expenses determine the service's margins. The two serve different purposes.11[Understanding fuel](02-understanding-fuel.md) covers allowances and top-ups12for relay owners.1314## Budget from the relay's meters1516`GET /fuel` returns current usage, allowances, rates and balance without17authentication. The Health tab shows the same information. Applications read18these values from the relay instead of hard-coding the public service's19prices, since another deployment can set different rates and allowances.2021| Fuel line | Usage and allowance fields | What the meter includes |22|---|---|---|23| Events stored | `eventBytes`, `freeEventBytes` | Allocated SQLite storage, including relay metadata, events and SQLite-backed Git repositories |24| Files stored | `mediaBytes`, `freeMediaBytes` | Uploaded files, dumps, imports and mirrored site files |25| Time awake | `activeMs`, `freeActiveMs` | An estimate from relay entrypoints, including client requests, jobs and alarms |26| Rows written | `rowsWritten`, `freeRowsWritten` | SQLite rows written by event storage and the Git object backend |2728`rates` gives storage prices in sats per GB-month, awake time in sats per29hour and writes in sats per million rows. Balance and credited or charged30amounts use millisatoshis: `balanceMsats / 1000` is the balance in sats.31Storage above its allowance is charged daily, pro rata. Awake time and rows32use the usage accumulated in `month` against their monthly allowances.3334`bytesIn`, `bytesOut` and `rowsRead` are diagnostics with no fuel price.35A request can still increase awake time or stored data even when its traffic36is unpriced. Sites, Marmot and Git share the existing allowances; enabling a37feature creates neither a separate allowance nor a request surcharge. Git38repositories use the relay's SQLite allowance. The configured39public rates remain separate: SQLite events are 400 sats per GB-month and R240files are 30 sats per GB-month. The storage path alone does not establish41which option costs less for a repository.4243The meters describe application accounting, not a complete provider bill.44Awake time is estimated from entrypoint gaps capped at ten seconds, rather45than timing each job to completion. Git object reads and writes report their46SQLite row work to the same meter. Usage is flushed in batches, so it is not a47durable record of every request. Applications use the reported balance48and `outOfFuel` for admission status rather than reconstructing charges from49traffic, event counts or wall-clock timings.5051## How features consume the budget5253| Workload | Resource use | Application choice |54|---|---|---|55| Events and Marmot messages | Event storage and database writes; encrypted envelopes still occupy space | Restrict admitted writers and set retention for kinds that do not need permanent history |56| Blossom files, dumps and imports | File storage alongside database metadata | Bound uploads and remove stored copies that are no longer needed |57| Sites | Manifests occupy event storage; mirrored files occupy file storage; remote fetches and mirrors wake the relay | Reuse unchanged files and choose whether the relay mirrors remote content |58| Git | Repositories occupy SQLite storage as compressed chunks, refs, receipts and indexes; clone, fetch and push wake the relay | Budget for repository history and ref updates, not just the checked-out files |59| Pulls, backfills and scheduled dumps | Jobs wake the relay and can add events, rows and files without a connected client | Use once-only jobs for one-time transfers and remove recurring jobs that no longer serve the application |6061[Relay templates](../relay-templates/README.md) provide starting policies.62[Data and names](04-data-and-names.md) describes retention, dumps and jobs.63Feature limits still apply below the fuel allowance: a positive balance does64not increase a maximum upload size or repository cap.65The limits and admission rules are in66[GRASP-01 Git hosting](22-grasp-01-git-hosting.md) and67[HTTP reference](14-http-reference.md).6869## Account for retained Git storage7071Git storage has no fixed allocation per repository. It grows with compressed72objects, chunks, refs, transaction receipts and metadata, including ref-only73updates. Removing a branch, hiding an announcement or expiring a PR does not74remove its immutable history. Repositories retain these bytes in SQLite75and they count toward `eventBytes`. There is no automatic Git garbage76collector.7778Git publication stores compressed object chunks, refs and retry receipts in79SQLite. Native SQL transactions publish them atomically, so a retry receipt80and its ref changes commit with the accepted objects. The physical database81meter includes indexes and metadata as well as compressed chunks. A logical82Git byte total is diagnostic and is not added to the fuel meter a second time.8384The relay owner can inspect one hosted repository with the `gitstorage`85management method. It takes the repository owner's hex public key and the86repository identifier. The repository owner can differ from the relay owner.87The report identifies the backend and gives compressed payload bytes, raw88object bytes and metadata bytes for a SQLite repository. Those diagnostics89explain the SQLite total; they are not additional fuel lines.90[Scripts and agents](13-scripts-and-agents.md) describes signing the call;91[HTTP reference](14-http-reference.md) defines its responses.9293| Report field | Interpretation |94|---|---|95| `backend` | `sqlite`, the repository's object and ref backend |96| `objects` | `count`, `rawBytes`, `compressedBytes` and `metadataBytes` for retained objects |97| `refs` | Number of stored refs |98| `receipts` | Number of retained retry receipts |99| `physicalDatabaseBytes` | The relay's complete SQLite size, including Git, events, indexes and metadata |100101The object byte fields explain the repository's logical and compressed102payloads. `physicalDatabaseBytes` is shared by all relay data and is the value103used by `eventBytes`; it must not be added to `objects.compressedBytes`. The104report changes no stored objects, refs, receipts or fuel rates.105106Inventory is a manual diagnostic, not a health poll. It reads bounded SQLite107metadata and holds the relay's admission owner while it runs, so competing Git108requests and mutations receive a retry response. The 60-second cooldown limits109repeated scans in a live instance and resets when that instance restarts. It is110not a recommended polling interval.111112`operations` reports zero R2 gets and lists for the SQLite backend. The SQL113metadata queries still contribute rows to the ordinary SQLite row meter. The114diagnostic fields are not a second storage charge or a separate inventory fee.115116## Handle limits without multiplying work117118A client checks the response reason before retrying. A transient admission119refusal can clear; a storage or repository limit requires a change in the120workload or available budget.121122| Condition | Client behavior |123|---|---|124| `outOfFuel` | Show the relay's allowance and balance; stop repeated writes until the budget permits them |125| Busy relay or rate limit (`429`) | Honor `Retry-After` or the response's `retryAfter` when supplied, and back off |126| Upload or repository limit | Keep the rejected operation visible to the user; a top-up does not raise a hard limit |127| Incomplete inventory | Keep the last complete report labelled with its capture time; an error does not mean zero usage or free storage |128129Fuel exhaustion does not delete data. Ordinary reads remain available under130the read policy, but Git requests and remote site fetches also require fuel.131Already stored site files remain readable under their access rules. Client132interfaces distinguish these cases instead of treating every refusal as a133network error.134135## Margins for deployments136137For developers hosting the service, `npm run margin` compares configured fuel138rates with provider costs. It reads `wrangler.jsonc`, uses the provider rates139recorded in the script and either fetches a bitcoin price or accepts an140explicit scenario:141142```143npm run margin144npm run margin -- --btc 60000145npm run margin -- --btc 60000 --target 0.40 --relays 5000146```147148The explicit bitcoin prices are scenario inputs, not quotes. For cost `c`149dollars per unit, price `p` sats per unit and bitcoin price `B` dollars,150revenue is `p * B / 100000000` and gross margin is `1 - c / revenue`.151A target margin `m` requires `price = cost / (1 - m)` in the same currency.152The script defaults to a 33% target and a 20% floor. It exits with code 1 when153a priced line falls below target; the weekly workflow records the result and154opens or refreshes an issue.155156Per-line margins do not establish a deployment margin. Provider allowances157are shared across the account, while tenants receive the relay allowances.158Free usage, R2 operations, platform requests, CPU, KV, observability and payment159costs also consume the service's revenue. A small stored dataset can generate160many reads or writes. The script's fleet shapes are illustrative assumptions;161actual workload mix and provider usage determine whether revenue covers the162deployment.163164Provider prices and included allocations are documented under165[Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/),166[Durable Object pricing](https://developers.cloudflare.com/durable-objects/platform/pricing/),167[R2 pricing](https://developers.cloudflare.com/r2/pricing/) and168[KV pricing](https://developers.cloudflare.com/kv/platform/pricing/).169[Hosting bind.ws](11-hosting-bindws.md) describes deployment configuration.170Running the margin script changes no tenant rates; a rate change requires a171configuration change and deployment, and appears in the relay's `/fuel`172response.