# Billing

Charge for hosted use with Stripe: an own-key plan, monthly model credits and top-ups.

## Plans

Billing stays off unless `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` are both set (see
`.env.example`). Enabling billing offers a monthly own-key plan for a personal model connection or
monthly model credits in USD: $10, $20, $50, $100, $200, or $500. One-time top-ups use the same
amounts. Monthly credits expire at the end of their service period; one-time top-ups do not expire.
The hosted computer is included in both paths, and monthly credits include the own-key plan.

`BYOK_TIERS` in `@orkbot/contracts` sets the own-key plan's tiers: Standard, Pro and Max, each with
a monthly and yearly price, a monthly computer allowance and a computer size. New organizations
start on Standard; existing subscribers keep the price they signed up at. A `null` Standard monthly
price makes the plan free, hides the other tiers, and ends paid own-key subscriptions at their
period end; a `null` yearly price offers that tier monthly only, and a `null` monthly price on Pro
or Max hides it. Billing reads a plan's tier from its price's lookup key, which Ork creates as
`orkbot_byok_<tier>_<monthly|yearly>_<cents>` (Standard's keys name no tier), so keep those keys
on any price you create by hand. Before selling a tier, make sure the sandbox account can run its
computer size; a computer that cannot start at its size starts at its current one.

Subscribers can change tier from Billing at the same interval, and monthly subscribers can switch
to yearly, which charges the yearly price less the unused part of the month. A tier change charges
the prorated difference now, and a downgrade credits the unused difference to the next invoice.
A downgrade is refused while this period's computer use is above the smaller tier's allowance.
Both are offered only to a paid-up plan that is not cancelling and has no monthly credits; the
yearly switch also only when it saves money. Billing reads a plan's period and tier from its price
on every sync, so a switch made in the Stripe billing portal also shows correctly. Portal plan
switching skips those checks and allows both directions, including yearly back to monthly with its
own proration, so enable it only if that is acceptable; the yearly price exists after the first yearly checkout or switch. Runs check
access on the server, so a lapsed organization's bots and routines stop until it subscribes.

## Model credits

Configure the managed default model through the existing model connection settings. The default
OpenRouter model is Luna 6; customers can choose another available model, with its usage deducted
at model cost. In credit mode, the configured hosted provider uses Ork's key even when a personal
key for that provider is saved. Switching back to BYOK restores that connection. Personal connections
to other providers continue to use their own keys and never debit Ork credits. Hosted calls reserve an upper cost estimate atomically before starting, then settle
to reported or catalog-priced usage. Unknown usage retains its reservation for reconciliation.

`STRIPE_PRICE_ID` is optional and only needed to display the legacy seat price. Existing live seat
subscriptions retain their access and billing portal. Upgrades charge the prorated difference
immediately and add the amount collected to the credit balance; a failed payment leaves the plan
unchanged. Downgrades lower the next renewal without charging now or removing credits.
Credit purchases require settled payments; webhook
replays do not add credits twice, and refunds reduce the remaining account balance.

Monthly funds are spent before top-ups. Each grant has an audited allocation lot, so reservations
retain their funding source across renewal: unused monthly holds expire when released, while
purchased top-ups remain available. Prorated upgrades share the original period end. Expiration
creates a financial journal delta and runs before balance reads, reservations, settlements, and
background recovery. Unclassified legacy balances are preserved as non-expiring funds.

## Accounting

Postgres owns accounting. `billing_audit_events` is an append-only journal enforced by database
triggers; credit entries, usage rows, and balances are projections. Each managed call shares one
`callId` across reservation, durable provider receipt, usage, and settlement. Provider generation
IDs, token buckets, cost provenance, and pricing snapshots are retained. Usage and settlement
commit together, and journal/usage history survives clearing messages or deleting a bot or
organization. Corrections append evidence rather than rewriting earlier journal events. Existing
rows receive explicit baseline events when the migration runs; earlier history cannot be recovered.

The API/worker reconciler recovers saved receipts and, when supported by the configured provider,
fetches authoritative generation costs for interrupted calls and estimates. Retries use leases and
idempotent settlement. Unverifiable calls retain their holds and become `needs_review` after six
attempts. An hourly, paginated audit rebuilds drifted balance caches from the journal and records
repairs. Investigate `Model credit hold requires review` logs using the shared call ID; do not turn
missing provider evidence into a zero-cost settlement.

OpenRouter inference requests include the normal Ork user ID in `user`, `HTTP-Referer`
(`https://orkbot.run`), and `X-OpenRouter-Title` (`Ork`). These values are sent only to OpenRouter's
HTTPS inference endpoints, never to compatible custom hosts. Recovery uses the existing configured
deployment credential; no Redis or external accounting service is required.

## Stripe setup

The adapter pins Stripe API `2026-09-30.endive`. Configure webhook destinations for your own account,
using snapshot payloads on the same API version. Subscribe to all `customer.subscription` events,
`customer.deleted`, `checkout.session.completed`, `checkout.session.async_payment_succeeded`,
`invoice.paid`, `invoice.payment_failed`, `charge.refunded`, `charge.dispute.created`, and
`charge.dispute.closed`. Credit Checkout restricts payment methods to cards. New subscriptions
explicitly retain classic billing mode so API upgrades do not change invoice/proration behavior.

For local testing, forward Stripe test events to `/api/v1/billing/webhook`. Use the signing secret
from `stripe listen` and test API credentials; keep them in an ignored environment file.

## Support chat

`CRISP_WEBSITE_ID` on the API shows a [Crisp](https://crisp.chat) support chat on the paywall.
