Prices are what g1t pays plus 20%, from the first second
Sandbox time is charged at cost plus 20% from its first second, like everything else in the price book; the 500 free minutes per workspace and the 138% markup that paid for them are gone. Runs on a workspace's own model provider no longer carry a $0.10 fee: they pay only their sandbox time. The price change is recorded with its reason, and the pricing page works out a markup-only change from the price. Docs, pricing page, billing page and sudo say it plainly.
19 files+166−1610/19 viewed
| 317 | 317 | ||
| 318 | 318 | A workspace pays for the g1t agents that work on its repositories, after | |
| 319 | 319 | they run: each run is charged what AI Gateway priced its model requests | |
| 320 | − | at, plus 20%, and its sandbox by the second past the free minutes. See | |
| 320 | + | at, plus 20%, and its sandbox by the second, at cost plus 20%. See | |
| 321 | 321 | [Usage and billing](/guides/usage-and-billing/) for how prices are set and | |
| 322 | 322 | the limits on usage not yet paid for. | |
| 323 | 323 | The workspace's **Usage** page shows what its agents have cost, by day, |
| 85 | 85 | ||
| 86 | 86 | ## What it costs | |
| 87 | 87 | ||
| 88 | − | Your providers bill you for the models. g1t charges a flat **$0.10 per | |
| 89 | − | run** for its orchestration, plus the run's | |
| 90 | − | [sandbox time](/guides/usage-and-billing/#sandbox-time). A | |
| 91 | − | change, a review, a revision, a catch-up and a plan are each a run. The | |
| 92 | − | statement marks these runs "on your own model provider" and names the | |
| 93 | − | model and provider; the Usage page shows what they cost at the provider, | |
| 94 | − | as the harness estimated it, beside what g1t charged. See | |
| 95 | − | [Usage and billing](/guides/usage-and-billing/). | |
| 88 | + | Your providers bill you for the models. g1t charges only each run's | |
| 89 | + | [sandbox time](/guides/usage-and-billing/#sandbox-time), at what it costs | |
| 90 | + | g1t plus 20%, by the second. A change, a review, a revision, a catch-up | |
| 91 | + | and a plan each run in a sandbox, and each sandbox is a line on the | |
| 92 | + | statement. See [Usage and billing](/guides/usage-and-billing/). | |
| 96 | 93 | ||
| 97 | − | Workspaces still need credit to start agents, for the fee. | |
| 94 | + | That sandbox time counts toward the workspace's usage limit like any | |
| 95 | + | other. | |
| 98 | 96 | ||
| 99 | 97 | ## Your keys never reach a sandbox | |
| 100 | 98 |
| 43 | 43 | gets **$1 of model cost on g1t's own models**, free, until **October 22, | |
| 44 | 44 | 2026** (11:59 PM Pacific). That is roughly 10 to 25 agent runs: changes, | |
| 45 | 45 | reviews and revisions. The allowance pays for the model part of those | |
| 46 | − | runs; everything else, such as sandbox time past the free minutes, is | |
| 47 | − | charged as usual. | |
| 46 | + | runs; everything else, such as sandbox time, is charged as usual. | |
| 48 | 47 | ||
| 49 | 48 | - Mission control and **Settings → Integrations** show what is left. | |
| 50 | 49 | - When it is used up, agents and workflow runs stop starting, and the | |
| 73 | 72 | it, plus 20%. A small change costs a few cents. | |
| 74 | 73 | ||
| 75 | 74 | Work a workspace routes to [its own model providers](/guides/models/) is | |
| 76 | − | paid for at those providers instead. Each such run here is a flat $0.10 | |
| 77 | − | for g1t's orchestration, plus its [sandbox time](#sandbox-time). | |
| 75 | + | paid for at those providers instead. Such a run is charged here only for | |
| 76 | + | its [sandbox time](#sandbox-time), like any other sandbox. | |
| 78 | 77 | ||
| 79 | 78 | The charge goes to the workspace that owns the repository, whoever | |
| 80 | 79 | assigned the issue. That is why only members of a workspace can put g1t | |
| 84 | 83 | ||
| 85 | 84 | g1t passes its own costs through. Everything a workspace uses costs g1t | |
| 86 | 85 | money first, at Cloudflare or a model provider, and is charged at that | |
| 87 | − | cost plus a set markup. There is no seat price, and nothing is bundled to | |
| 88 | − | hide what it costs. The live prices are on | |
| 86 | + | cost plus 20%. The 20% pays for running g1t and for building and keeping | |
| 87 | + | up its features; g1t is not trying to make money on top of that. There is | |
| 88 | + | no seat price, and nothing is bundled to hide what it costs. The live prices are on | |
| 89 | 89 | [g1t.sh/pricing](https://g1t.sh/pricing), straight from the price book | |
| 90 | 90 | billing charges from. | |
| 91 | 91 | ||
| 105 | 105 | against what was used: Containers against the seconds containers ran, | |
| 106 | 106 | Workers for Platforms per request and per CPU millisecond. When a cost | |
| 107 | 107 | moves by 2% or more, the price book moves with it, since each price is | |
| 108 | − | its cost times its markup, and the change is listed on the pricing page | |
| 108 | + | its cost plus its markup, and the change is listed on the pricing page | |
| 109 | 109 | with the reason. A measurement far from the current cost (more than 4× | |
| 110 | 110 | either way) is not adopted, only logged, so one odd day cannot reprice | |
| 111 | 111 | anything. | |
| 113 | 113 | | | Markup | | |
| 114 | 114 | | --- | --- | | |
| 115 | 115 | | Models | 20% | | |
| 116 | − | | Deploy builds, app requests, CPU and apps | 20% | | |
| 117 | − | | Sandbox time | 138%, which also pays for the orchestration around each sandbox and everyone's free minutes | | |
| 116 | + | | Sandbox time | 20% | | |
| 117 | + | | Deploy builds | 20% | | |
| 118 | + | | App requests, CPU and apps | 20% | | |
| 119 | + | | Custom domains past the plan | 20% | | |
| 118 | 120 | ||
| 119 | 121 | ## Sandbox time | |
| 120 | 122 | ||
| 126 | 128 | ||
| 127 | 129 | | | | | |
| 128 | 130 | | --- | --- | | |
| 129 | − | | Free each month | 500 minutes (calendar month, UTC) | | |
| 130 | − | | Past that | about $0.002 a minute, by the second | | |
| 131 | 131 | | What it costs g1t | about $0.0009 a minute (Containers, standard-1, at the CPU sandboxes really use) | | |
| 132 | + | | What you pay | that plus 20%, about $0.0011 a minute, by the second from the first | | |
| 132 | 133 | ||
| 133 | 134 | Both follow what Cloudflare bills, so they move; today's exact figures are | |
| 134 | 135 | on [g1t.sh/pricing](https://g1t.sh/pricing). See | |
| 138 | 139 | charges them by the second on its own plan. | |
| 139 | 140 | ||
| 140 | 141 | Each sandbox is one line on the [statement](#the-statement), such as | |
| 141 | − | *Checks on acme/api#12: 3m 12s of sandbox time*, with whether it fell | |
| 142 | − | within the free minutes. | |
| 142 | + | *Checks on acme/api#12: 3m 12s of sandbox time*. | |
| 143 | 143 | ||
| 144 | 144 | ## Usage limits | |
| 145 | 145 | ||
| 203 | 203 | with its PDF and emailed as a receipt: | |
| 204 | 204 | ||
| 205 | 205 | - **When each month closes**, an invoice for what the workspace owes, | |
| 206 | − | itemised: agents on g1t's models, runs on your own model provider, | |
| 207 | − | sandbox time, and deployments past the plan. Credit you paid in advance | |
| 206 | + | itemised: agents on g1t's models, sandbox time, and deployments past | |
| 207 | + | the plan. Credit you paid in advance | |
| 208 | 208 | is taken off as *Paid in advance*; anything left unpaid from before is | |
| 209 | 209 | added. | |
| 210 | 210 | - **When the workspace nears its ceiling** mid-month, the same, sooner. | |
| 341 | 341 | | Line | What it holds | | |
| 342 | 342 | | --- | --- | | |
| 343 | 343 | | Agent runs | Runs on g1t's model provider: the model's cost plus the margin. | | |
| 344 | − | | Runs on your own model provider | The run fee for runs billed by your own provider. | | |
| 344 | + | | Runs on your own model provider | Older months only: the flat fee runs on your own provider used to carry. | | |
| 345 | 345 | | Sandbox time | Each sandbox's time, memory and disk. | | |
| 346 | 346 | | Deployments | Builds and apps beyond the allowance. | | |
| 347 | 347 | | Payments | Card payments and invoices paid. | | |
| 368 | 368 | - **g1t's agents, for any workspace with its own model provider:** connect | |
| 369 | 369 | an Anthropic key or endpoint under [Integrations](/guides/models/) and the | |
| 370 | 370 | workspace's agents, acceptance checks and merge queue work at once. Your | |
| 371 | − | provider bills you for the models; g1t charges nothing for now, and $0.10 | |
| 372 | − | a run once pricing starts. | |
| 371 | + | provider bills you for the models; g1t charges nothing for now, and only | |
| 372 | + | each run's sandbox time once pricing starts. | |
| 373 | 373 | - **g1t's hosted models:** while payments are in test mode, every | |
| 374 | 374 | workspace can use g1t's own models on | |
| 375 | 375 | [the free allowance](#the-free-allowance) ($1 each, until October 22), |
| 163 | 163 | deployments: "Deployments", | |
| 164 | 164 | builds: "Builds", | |
| 165 | 165 | plans: "Plans", | |
| 166 | − | orchestration: "Orchestration", | |
| 167 | 166 | }; | |
| 168 | 167 | ||
| 169 | 168 | export function kindLabel(kind: string): string { |
| 15 | 15 | export const CHANGELOG: ChangelogEntry[] = [ | |
| 16 | 16 | { | |
| 17 | 17 | date: "2026-10-05", | |
| 18 | + | title: "Cost plus 20%, on everything", | |
| 19 | + | about: | |
| 20 | + | "Sandbox time is charged at cost plus 20% from the first second, with no free minutes. Runs on your own model provider no longer carry a $0.10 fee: they pay only their sandbox time.", | |
| 21 | + | href: "/pricing", | |
| 22 | + | }, | |
| 23 | + | { | |
| 24 | + | date: "2026-10-05", | |
| 18 | 25 | title: "Catch up in seconds", | |
| 19 | 26 | about: | |
| 20 | 27 | "Catch up with main merges it in at once when the two changed different files. When they overlap, the merge box shows g1t-agent at work, step by step.", |
| 10 | 10 | export function meta(args: Route.MetaArgs) { | |
| 11 | 11 | return page(args, { | |
| 12 | 12 | title: "Pricing · g1t", | |
| 13 | − | description: "g1t passes its costs through: what Cloudflare and model providers charge g1t, plus a set markup. No seats.", | |
| 13 | + | description: "g1t passes its costs through: what Cloudflare and model providers charge g1t, plus 20%. No seats.", | |
| 14 | 14 | }); | |
| 15 | 15 | } | |
| 16 | 16 | ||
| 35 | 35 | const HOW = [ | |
| 36 | 36 | { | |
| 37 | 37 | title: "Our cost, passed through", | |
| 38 | − | body: "Every sandbox second, build, app request and model token costs g1t money at Cloudflare or a model provider. Each is metered and charged at that cost plus a set markup. Use a little, pay a little.", | |
| 38 | + | body: "Every sandbox second, build, app request and model token costs g1t money at Cloudflare or a model provider. Each is metered and charged at that cost plus 20%, from the first second and the first request. Use a little, pay a little.", | |
| 39 | 39 | }, | |
| 40 | 40 | { | |
| 41 | 41 | title: "Prices follow costs, by themselves", | |
| 42 | 42 | body: "Model runs are charged at what Cloudflare's AI Gateway priced each request at, so a provider's price change reaches you the same day. Every day, each Cloudflare cost is checked against what Cloudflare billed g1t; when one moves, its price moves with it, and the change is listed below.", | |
| 43 | 43 | }, | |
| 44 | 44 | { | |
| 45 | + | title: "The 20% is the overhead", | |
| 46 | + | body: "It pays for running g1t and for building and keeping up the features you use. The same 20% on everything, and nothing bundled in.", | |
| 47 | + | }, | |
| 48 | + | { | |
| 45 | 49 | title: "No seats, ever", | |
| 46 | 50 | body: "Add as many people and agents as you like. A workspace pays for what it uses, and for the features it turns on.", | |
| 47 | 51 | }, | |
| 61 | 65 | return ( | |
| 62 | 66 | <main className="mx-auto max-w-4xl px-4 py-12"> | |
| 63 | 67 | <p className="text-sm font-medium text-accent">Pricing</p> | |
| 64 | − | <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">What it costs us, plus a markup</h1> | |
| 68 | + | <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">What it costs us, plus 20%</h1> | |
| 65 | 69 | <p className="mt-3 max-w-2xl text-muted"> | |
| 66 | 70 | g1t runs on Cloudflare and model providers, and passes those costs through. The numbers on this page are the | |
| 67 | 71 | live price book g1t charges from. | |
| 120 | 124 | <p className="font-medium">{price.title}</p> | |
| 121 | 125 | <p className="text-xs text-faint"> | |
| 122 | 126 | {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"} | |
| 123 | − | {price.meter === "sandbox_second" && " · 500 minutes free each month"} | |
| 124 | 127 | </p> | |
| 125 | 128 | </td> | |
| 126 | 129 | <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td> | |
| 131 | 134 | </tbody> | |
| 132 | 135 | </table> | |
| 133 | 136 | </div> | |
| 134 | − | <p className="mt-3 text-xs text-faint"> | |
| 135 | − | Sandbox time carries more markup than the rest: it pays for the orchestration around each sandbox, and the | |
| 136 | − | free minutes everyone gets. | |
| 137 | − | </p> | |
| 138 | 137 | ||
| 139 | 138 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Features</h2> | |
| 140 | 139 | <p className="mt-1 text-sm text-muted">Turned on per workspace with a monthly plan. Never free.</p> | |
| 158 | 157 | <ul className="mt-4 divide-y divide-line rounded-xl border border-line"> | |
| 159 | 158 | {book.changes.map((change) => { | |
| 160 | 159 | const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter; | |
| 161 | − | const up = change.newCostMicros > change.oldCostMicros; | |
| 160 | + | // What you pay moved: the cost, the markup, or both. | |
| 161 | + | const before = change.oldCostMicros * (100 + (change.oldMarkupPercent ?? change.markupPercent)); | |
| 162 | + | const after = change.newCostMicros * (100 + change.markupPercent); | |
| 163 | + | const up = after > before; | |
| 162 | 164 | return ( | |
| 163 | 165 | <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm"> | |
| 164 | 166 | <p> | |
| 165 | 167 | <span className="font-medium">{title}</span>{" "} | |
| 166 | 168 | <span className={up ? "text-warn" : "text-accent"}> | |
| 167 | − | {up ? "up" : "down"} {Math.abs((change.newCostMicros / change.oldCostMicros - 1) * 100).toFixed(1)}% | |
| 169 | + | {up ? "up" : "down"} {Math.abs((after / before - 1) * 100).toFixed(1)}% | |
| 168 | 170 | </span> | |
| 169 | 171 | </p> | |
| 170 | 172 | <p className="mt-0.5 text-xs text-faint"> | |
| 175 | 177 | })} | |
| 176 | 178 | </ul> | |
| 177 | 179 | ) : ( | |
| 178 | − | <p className="mt-2 text-sm text-muted">None yet. When a cost moves, it is listed here with why.</p> | |
| 180 | + | <p className="mt-2 text-sm text-muted">None yet. When a price moves, it is listed here with why.</p> | |
| 179 | 181 | )} | |
| 180 | 182 | ||
| 181 | 183 | <a |
| 267 | 267 | <p className="mt-1 max-w-2xl text-sm text-muted"> | |
| 268 | 268 | g1t agents that work on this workspace's repositories are paid for from | |
| 269 | 269 | its account: what the model cost, plus {account.marginPercent}%. Every sandbox, for agents, checks, the merge | |
| 270 | − | queue and workflows, is metered by the second past 500 free minutes a month. Credit added here pays usage in | |
| 270 | + | queue and workflows, is metered by the second, from the first. Credit added here pays usage in | |
| 271 | 271 | advance; the usage limit above decides whether work starts. | |
| 272 | 272 | </p> | |
| 273 | 273 | ||
| 356 | 356 | <h3 className="font-medium">How it is charged</h3> | |
| 357 | 357 | <ul className="mt-2 list-disc space-y-1.5 pl-4 text-muted"> | |
| 358 | 358 | <li> | |
| 359 | − | Usage is charged after it runs, to the workspace that owns the repository: agents at what the model cost | |
| 360 | − | plus {account.marginPercent}%, and sandbox time, builds and apps at what they cost g1t plus a set markup. | |
| 359 | + | Usage is charged after it runs, to the workspace that owns the repository: models, sandbox time, builds | |
| 360 | + | and apps at what they cost g1t plus {account.marginPercent}%, which pays for running and building g1t. | |
| 361 | + | Nothing is bundled in. | |
| 361 | 362 | </li> | |
| 362 | 363 | <li> | |
| 363 | 364 | Each month closes with an itemised invoice, charged to the card on file. Near your limit, g1t sends one | |
| 368 | 369 | <Link to={`/${slug}/-/integrations`} className="text-fg hover:underline"> | |
| 369 | 370 | Integrations | |
| 370 | 371 | </Link> | |
| 371 | − | , the provider bills you for the model and each run here is {dollars(account.orchestrationFeeMicros)}, plus | |
| 372 | − | its sandbox time. | |
| 372 | + | , the provider bills you for the model, and a run here is charged only its sandbox time. | |
| 373 | 373 | </li> | |
| 374 | 374 | <li>No seats: add as many people and agents as you like.</li> | |
| 375 | 375 | </ul> |
| 71 | 71 | hostedOpen: access.hosted, | |
| 72 | 72 | trial: access.trial, | |
| 73 | 73 | routes: routes.ok ? routes.value : [], | |
| 74 | − | feeMicros: account.ok ? account.value.orchestrationFeeMicros : 100_000, | |
| 75 | 74 | free: account.ok ? Boolean(account.value.status.free) : false, | |
| 76 | 75 | marginPercent: account.ok ? account.value.marginPercent : 20, | |
| 77 | 76 | }; | |
| 179 | 178 | } | |
| 180 | 179 | ||
| 181 | 180 | export default function WorkspaceIntegrations({ loaderData, actionData }: Route.ComponentProps) { | |
| 182 | − | const { slug, role, connections, deliveries, repos: repoNames, adding, feeMicros, free, marginPercent, hostedOpen, trial, routes } = | |
| 181 | + | const { slug, role, connections, deliveries, repos: repoNames, adding, free, marginPercent, hostedOpen, trial, routes } = | |
| 183 | 182 | loaderData; | |
| 184 | 183 | const owner = role === "owner"; | |
| 185 | 184 | const busy = useNavigation().state === "submitting"; | |
| 238 | 237 | <p className="mt-3 text-xs text-faint"> | |
| 239 | 238 | On your own providers, their bills are yours.{" "} | |
| 240 | 239 | {free | |
| 241 | − | ? `g1t charges nothing while it is being built out; once pricing starts, ${dollars(feeMicros)} a run for the sandbox and orchestration.` | |
| 242 | − | : `g1t charges ${dollars(feeMicros)} a run for the sandbox and orchestration.`}{" "} | |
| 240 | + | ? `g1t charges nothing while it is being built out; once pricing starts, only each run's sandbox time, at cost plus ${marginPercent}%.` | |
| 241 | + | : `g1t charges only each run's sandbox time, at cost plus ${marginPercent}%.`}{" "} | |
| 243 | 242 | Keys go only from g1t's model proxy to the provider: the agent's | |
| 244 | 243 | sandbox holds a token that dies with the run. | |
| 245 | 244 | </p> |
| 215 | 215 | as it uses, with each | |
| 216 | 216 | kind of work routed to one of them or to g1t's hosted models | |
| 217 | 217 | (`PUT /workspaces/{workspace}/model-routes`). Those providers bill the | |
| 218 | − | workspace; g1t charges nothing while it is being built out (later, $0.10 | |
| 219 | − | a run). Sandboxes never hold a key. | |
| 218 | + | workspace; g1t charges nothing while it is being built out (later, only | |
| 219 | + | each run's sandbox time, at cost plus 20%). Sandboxes never hold a key. | |
| 220 | 220 | - **Alerts** (`sentry`, `datadog`, `webhook`): each problem opens one issue | |
| 221 | 221 | in a chosen repository, optionally with an agent put on it at once. | |
| 222 | 222 | Senders sign requests to `https://api.g1t.sh/hooks/{integration}`. |
| 74 | 74 | pub status: Status, | |
| 75 | 75 | /// What is added to a run's cost, in percent. | |
| 76 | 76 | pub margin_percent: u32, | |
| 77 | − | /// What a run on the workspace's own model provider is charged: g1t's | |
| 78 | − | /// sandbox and orchestration, with the model paid for elsewhere. | |
| 79 | − | pub orchestration_fee_micros: i64, | |
| 80 | 77 | /// The card g1t charges as the workspace nears its limit and when a | |
| 81 | 78 | /// month closes, if one is on file. | |
| 82 | 79 | #[serde(default)] | |
| 152 | 149 | /// For usage: the model, by its public name. | |
| 153 | 150 | pub model: Option<String>, | |
| 154 | 151 | /// For usage: `g1t` when g1t paid the model provider, `workspace` when | |
| 155 | − | /// the workspace's own account did and only orchestration is charged. | |
| 152 | + | /// the workspace's own account did. Runs on the workspace's own | |
| 153 | + | /// provider pay only their sandbox time now, so only older entries | |
| 154 | + | /// are `workspace`. | |
| 156 | 155 | #[serde(default = "g1t")] | |
| 157 | 156 | pub billed_to: String, | |
| 158 | 157 | /// For a top-up: the username of whoever paid. | |
| 367 | 366 | pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000; | |
| 368 | 367 | } | |
| 369 | 368 | ||
| 370 | − | /// Sandbox time: every sandbox g1t starts for a workspace (agents, | |
| 371 | − | /// reviews, checks, the merge queue, workflow jobs) is metered by the | |
| 372 | − | /// second. Deploy builds are charged by the Deployments plan instead. | |
| 373 | − | pub mod sandbox_allowance { | |
| 374 | − | /// Free each calendar month (UTC): 500 minutes. | |
| 375 | − | pub const FREE_SECONDS: i64 = 30_000; | |
| 376 | − | /// What one second costs g1t (Cloudflare Containers, standard-1), | |
| 377 | − | /// rounded up. Recorded with every entry. | |
| 378 | − | pub const COST_MICROS_PER_SECOND: i64 = super::deployments_allowance::MICROS_PER_BUILD_SECOND; | |
| 379 | − | /// What one second past the free minutes is charged: $0.003 a minute. | |
| 380 | − | pub const MICROS_PER_SECOND: i64 = 50; | |
| 381 | − | } | |
| 382 | − | ||
| 383 | 369 | /// `record_sandbox`: how long one sandbox ran for a workspace, reported by | |
| 384 | − | /// the runner when it stops. Recorded once per `reference`, with what it | |
| 385 | − | /// cost g1t; seconds past the month's free minutes are charged at | |
| 386 | − | /// `sandbox_allowance::MICROS_PER_SECOND`, unless `FREE_WHILE_BUILDING`. | |
| 370 | + | /// the runner when it stops. Every sandbox g1t starts for a workspace | |
| 371 | + | /// (agents, reviews, checks, the merge queue, workflow jobs) is metered by | |
| 372 | + | /// the second, from the first: recorded once per `reference`, with what it | |
| 373 | + | /// cost g1t, and charged at the price book's `sandbox_second` price unless | |
| 374 | + | /// `FREE_WHILE_BUILDING`. Deploy builds are charged by the Deployments plan | |
| 375 | + | /// instead. | |
| 387 | 376 | /// Returns `Outcome<bool>`: false if that reference was recorded before. | |
| 388 | 377 | #[derive(Debug, Serialize, Deserialize)] | |
| 389 | 378 | #[serde(rename_all = "camelCase")] | |
| 548 | 537 | pub old_cost_micros: f64, | |
| 549 | 538 | pub new_cost_micros: f64, | |
| 550 | 539 | pub markup_percent: u32, | |
| 540 | + | /// The markup before, when the change was to the markup rather than | |
| 541 | + | /// to the cost. Absent when the markup stayed `markup_percent`. | |
| 542 | + | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 543 | + | pub old_markup_percent: Option<u32>, | |
| 551 | 544 | pub reason: String, | |
| 552 | 545 | pub created_at: String, | |
| 553 | 546 | } | |
| 826 | 819 | #[serde(rename_all = "camelCase")] | |
| 827 | 820 | pub struct StatementLine { | |
| 828 | 821 | /// Agent runs, Sandbox time, Deployments, Payments, Credits from g1t, | |
| 829 | − | /// Refunds, Runs on your own model provider. | |
| 822 | + | /// Refunds, and, for older entries, Runs on your own model provider. | |
| 830 | 823 | pub kind: String, | |
| 831 | 824 | pub count: u32, | |
| 832 | 825 | /// Charges positive; money in (payments, credits) negative. | |
| 1296 | 1289 | use super::*; | |
| 1297 | 1290 | ||
| 1298 | 1291 | #[test] | |
| 1292 | + | fn an_account_carries_no_run_fee() { | |
| 1293 | + | let account = Account { | |
| 1294 | + | workspace: "acme".into(), | |
| 1295 | + | balance_micros: 0, | |
| 1296 | + | status: Status { enabled: true, live: false, free: false }, | |
| 1297 | + | margin_percent: 20, | |
| 1298 | + | card: None, | |
| 1299 | + | }; | |
| 1300 | + | let json = serde_json::to_value(account).unwrap(); | |
| 1301 | + | let mut keys: Vec<&str> = json.as_object().unwrap().keys().map(String::as_str).collect(); | |
| 1302 | + | keys.sort_unstable(); | |
| 1303 | + | assert_eq!(keys, ["balanceMicros", "card", "marginPercent", "status", "workspace"]); | |
| 1304 | + | } | |
| 1305 | + | ||
| 1306 | + | #[test] | |
| 1307 | + | fn a_price_change_says_when_the_markup_moved() { | |
| 1308 | + | let change = PriceChange { | |
| 1309 | + | meter: "sandbox_second".into(), | |
| 1310 | + | old_cost_micros: 21.0, | |
| 1311 | + | new_cost_micros: 21.0, | |
| 1312 | + | markup_percent: 20, | |
| 1313 | + | old_markup_percent: Some(138), | |
| 1314 | + | reason: "Sandbox time is now charged at cost plus 20% from the first second".into(), | |
| 1315 | + | created_at: "2026-10-05T00:00:00Z".into(), | |
| 1316 | + | }; | |
| 1317 | + | assert_eq!(serde_json::to_value(&change).unwrap()["oldMarkupPercent"], 138); | |
| 1318 | + | let cost_only = PriceChange { old_markup_percent: None, ..change }; | |
| 1319 | + | assert!(serde_json::to_value(&cost_only).unwrap().get("oldMarkupPercent").is_none()); | |
| 1320 | + | } | |
| 1321 | + | ||
| 1322 | + | #[test] | |
| 1299 | 1323 | fn features_are_named_as_the_site_sends_them() { | |
| 1300 | 1324 | assert_eq!( | |
| 1301 | 1325 | serde_json::to_value(Feature::Deployments).unwrap(), |
| 759 | 759 | | Kind | What the workspace does | Example | | |
| 760 | 760 | | --- | --- | --- | | |
| 761 | 761 | | **Free** | Nothing | Hosting code, issues, pull requests, review, the merge queue, bringing your own agent over MCP | | |
| 762 | − | | **Card on file** | Adds a card; pays only for what it uses | Previews, g1t's agents, workflow minutes past the free ones | | |
| 762 | + | | **Card on file** | Adds a card; pays only for what it uses | Previews, g1t's agents, workflow minutes | | |
| 763 | 763 | | **Activation** | Turns a feature on for a monthly fee that includes an allowance; usage past it is metered | Production deployments, Security and quality | | |
| 764 | 764 | | **Usage only** | Nothing up front; every unit is metered | Model tokens, build minutes, storage past the free amount | | |
| 765 | 765 | ||
| 825 | 825 | ||
| 826 | 826 | | Meter | Free each month | Then | Margin | For comparison | | |
| 827 | 827 | | --- | --- | --- | --- | --- | | |
| 828 | − | | Sandbox minutes | 500 | $0.003 / minute | 2.5× | GitHub Actions $0.008 / minute (Linux 2-core); Vercel builds $0.0035 / CPU-minute | | |
| 828 | + | | Sandbox minutes | None (2026-10-05: charged from the first second) | Cost + 20%, by the second | 1.2× | GitHub Actions $0.008 / minute (Linux 2-core); Vercel builds $0.0035 / CPU-minute | | |
| 829 | 829 | | App requests | 1 million with Deployments | $0.50 / million | 1.7× | Vercel $0.60 / million invocations | | |
| 830 | 830 | | App CPU | 3 million ms with Deployments | $0.04 / million ms | 2× | Vercel active CPU about $0.036 / million ms | | |
| 831 | 831 | | Apps | 10 with Deployments | $0.05 / app-month | 2.5× | | | |
| 832 | 832 | | Storage | 1 GB | $1.00 / GB-month | 2× | GitHub LFS $0.07 / GB, but repositories are free there | | |
| 833 | 833 | | Git operations | 10,000 | $0.30 / 1,000 | 2× | | | |
| 834 | 834 | | g1t's models | | Cost + 20% | 1.2× | The provider's own price | | |
| 835 | − | | Your own model provider | | $0.10 / run, plus its sandbox minutes | | | | |
| 835 | + | | Your own model provider | | Only its sandbox minutes (the $0.10 run fee was dropped 2026-10-05) | | | | |
| 836 | 836 | | **Deployments** activation | | $5 / month, with the allowances above | covers Workers for Platforms' $25 / month across workspaces | Vercel Pro $20 per seat | | |
| 837 | 837 | | **Security and quality** activation (later) | | $10 / month; fixes as agent usage | | GitHub Advanced Security $49 per committer | | |
| 838 | 838 | ||
| 1213 | 1213 | between steps, and at the end), and recording sessions from anyone's own | |
| 1214 | 1214 | Claude Code (`curl -fsSL https://g1t.sh/install/claude.sh | sh`). Integrations are | |
| 1215 | 1215 | in: a workspace's own model provider (Anthropic or any Anthropic-compatible | |
| 1216 | − | endpoint, reached through a model proxy so no sandbox holds a key, for a | |
| 1217 | − | flat orchestration fee), alerts from Sentry, Datadog and signed webhooks | |
| 1216 | + | endpoint, reached through a model proxy so no sandbox holds a key; its | |
| 1217 | + | runs pay only their sandbox time), alerts from Sentry, Datadog and signed webhooks | |
| 1218 | 1218 | that open one issue per problem and can start an agent, and Jira and Linear | |
| 1219 | 1219 | tickets that agents read, people import, and that hear back. Racing a | |
| 1220 | 1220 | set number of agents on one issue is dropped: choosing how many agents to |
| 32 | 32 | status: BillingStatus; | |
| 33 | 33 | /** What is added to a run's cost, in percent. */ | |
| 34 | 34 | marginPercent: number; | |
| 35 | − | /** What a run on the workspace's own model provider is charged instead. */ | |
| 36 | − | orchestrationFeeMicros: number; | |
| 37 | 35 | /** The card charged near the limit and when a month closes, if one is saved. */ | |
| 38 | 36 | card?: { brand: string; last4: string; expMonth: number; expYear: number } | null; | |
| 39 | 37 | }; | |
| 325 | 323 | oldCostMicros: number; | |
| 326 | 324 | newCostMicros: number; | |
| 327 | 325 | markupPercent: number; | |
| 326 | + | /** The markup before, when the change was to the markup rather than the cost. */ | |
| 327 | + | oldMarkupPercent?: number; | |
| 328 | 328 | reason: string; | |
| 329 | 329 | createdAt: string; | |
| 330 | 330 | }; | |
| 479 | 479 | invoices(workspace: string, viewer: Viewer): Promise<Result<WorkspaceInvoice[]>>; | |
| 480 | 480 | /** | |
| 481 | 481 | * How long a sandbox ran for a workspace, reported when it stops. Its | |
| 482 | − | * cost is always recorded; seconds past the month's free minutes are | |
| 483 | − | * charged. False if `reference` was recorded before. | |
| 482 | + | * cost is recorded and every second is charged, from the first. False if | |
| 483 | + | * `reference` was recorded before. | |
| 484 | 484 | */ | |
| 485 | 485 | recordSandbox(usage: { | |
| 486 | 486 | workspace: string; |
| 1 | + | -- Sandbox time is charged at cost plus 20% from the first second, like | |
| 2 | + | -- everything else in the price book. There are no free minutes, so the | |
| 3 | + | -- monthly count that measured them goes. | |
| 4 | + | ||
| 5 | + | -- A change can be to the markup rather than to the cost: what it was. | |
| 6 | + | ALTER TABLE price_changes ADD COLUMN old_markup_percent INTEGER; | |
| 7 | + | ||
| 8 | + | INSERT INTO price_changes (id, meter, old_cost_micros, new_cost_micros, markup_percent, old_markup_percent, reason, created_at) | |
| 9 | + | SELECT 'prc_sandbox_at_cost', meter, cost_micros, cost_micros, 20, markup_percent, | |
| 10 | + | 'Sandbox time is now charged at cost plus 20% from the first second', | |
| 11 | + | strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | |
| 12 | + | FROM prices WHERE meter = 'sandbox_second' AND markup_percent <> 20; | |
| 13 | + | ||
| 14 | + | UPDATE prices SET markup_percent = 20, updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | |
| 15 | + | WHERE meter = 'sandbox_second'; | |
| 16 | + | ||
| 17 | + | DROP TABLE IF EXISTS sandbox_months; |
| 268 | 268 | old_cost_micros: f64, | |
| 269 | 269 | new_cost_micros: f64, | |
| 270 | 270 | markup_percent: u32, | |
| 271 | + | old_markup_percent: Option<u32>, | |
| 271 | 272 | reason: String, | |
| 272 | 273 | created_at: String, | |
| 273 | 274 | } | |
| 334 | 335 | old_cost_micros: row.old_cost_micros, | |
| 335 | 336 | new_cost_micros: row.new_cost_micros, | |
| 336 | 337 | markup_percent: row.markup_percent, | |
| 338 | + | old_markup_percent: row.old_markup_percent, | |
| 337 | 339 | reason: row.reason, | |
| 338 | 340 | created_at: row.created_at, | |
| 339 | 341 | }) |
| 138 | 138 | /// Absent when no card processor is configured. | |
| 139 | 139 | stripe: Option<Stripe>, | |
| 140 | 140 | margin_percent: u32, | |
| 141 | − | /// Charged for a run on the workspace's own model provider. | |
| 142 | − | orchestration_fee_micros: i64, | |
| 143 | 141 | /// While g1t is being built out, nothing is charged (`FREE_WHILE_BUILDING`). | |
| 144 | 142 | free: bool, | |
| 145 | 143 | /// The free allowance on g1t's hosted models, when there is one. | |
| 198 | 196 | balance_micros: row.map_or(0, |row| row.balance_micros), | |
| 199 | 197 | status: self.status(), | |
| 200 | 198 | margin_percent: self.margin_percent, | |
| 201 | − | orchestration_fee_micros: self.orchestration_fee_micros, | |
| 202 | 199 | card, | |
| 203 | 200 | }) | |
| 204 | 201 | } | |
| 730 | 727 | if claimed.is_none() { | |
| 731 | 728 | return Ok(Outcome::Ok(false)); | |
| 732 | 729 | } | |
| 733 | − | // On the workspace's own provider, the model was paid for there: | |
| 734 | − | // g1t charges its fee, and keeps the provider's cost to show. | |
| 735 | − | let base = if run.own_provider() { | |
| 736 | − | self.orchestration_fee_micros | |
| 737 | − | } else { | |
| 738 | − | // The free allowance on g1t's models covers what it can. | |
| 739 | − | let cost = charge_micros(a.cost_usd, 0); | |
| 740 | − | let covered = self.trial_covers(&run.workspace, cost).await?; | |
| 741 | − | charge_micros((cost - covered) as f64 / MICROS_PER_DOLLAR as f64, self.margin_percent) | |
| 742 | − | }; | |
| 730 | + | // On the workspace's own provider, the model was paid for there, | |
| 731 | + | // and the run's sandbox time is recorded on its own: nothing more | |
| 732 | + | // to charge. | |
| 733 | + | if run.own_provider() { | |
| 734 | + | return Ok(Outcome::Ok(true)); | |
| 735 | + | } | |
| 736 | + | // The free allowance on g1t's models covers what it can. | |
| 737 | + | let cost = charge_micros(a.cost_usd, 0); | |
| 738 | + | let covered = self.trial_covers(&run.workspace, cost).await?; | |
| 739 | + | let base = charge_micros((cost - covered) as f64 / MICROS_PER_DOLLAR as f64, self.margin_percent); | |
| 743 | 740 | let (charge, terms_note) = self.charged(&run.workspace, base).await?; | |
| 744 | 741 | let mut description = match run.task.as_str() { | |
| 745 | 742 | "plan" => format!("Planning for {}", run.repo), | |
| 747 | 744 | "update" => format!("Catching up {}#{}", run.repo, run.number), | |
| 748 | 745 | _ => format!("Work on {}#{}", run.repo, run.number), | |
| 749 | 746 | }; | |
| 750 | − | if run.own_provider() { | |
| 751 | − | description.push_str(", on your own model provider"); | |
| 752 | − | } | |
| 753 | 747 | description.push_str(&terms_note); | |
| 754 | 748 | self.enter( | |
| 755 | 749 | &run.workspace, | |
| 768 | 762 | } | |
| 769 | 763 | ||
| 770 | 764 | impl Billing { | |
| 771 | − | /// Records how long a sandbox ran: its cost always, and a charge for | |
| 772 | − | /// the seconds past the month's free minutes. | |
| 765 | + | /// Records how long a sandbox ran, with its cost and its charge: every | |
| 766 | + | /// second, from the first, at the price book's price. | |
| 773 | 767 | async fn record_sandbox(&self, a: RecordSandboxArgs) -> Result<Outcome<bool>> { | |
| 774 | 768 | if self.stripe.is_none() || a.seconds == 0 { | |
| 775 | 769 | return Ok(Outcome::Ok(false)); | |
| 786 | 780 | } | |
| 787 | 781 | let now = now_ms(); | |
| 788 | 782 | let timestamp = rfc3339(now); | |
| 789 | − | let month = ×tamp[..7]; | |
| 790 | − | #[derive(Deserialize)] | |
| 791 | − | struct Used { | |
| 792 | − | seconds: i64, | |
| 793 | − | } | |
| 794 | 783 | let seconds = i64::from(a.seconds); | |
| 795 | − | let after = self | |
| 796 | − | .db | |
| 797 | − | .prepare( | |
| 798 | − | "INSERT INTO sandbox_months (workspace, month, seconds) VALUES (?1, ?2, ?3) | |
| 799 | − | ON CONFLICT (workspace, month) DO UPDATE SET seconds = seconds + ?3 | |
| 800 | − | RETURNING seconds", | |
| 801 | − | ) | |
| 802 | − | .bind(&[workspace.as_str().into(), month.into(), (seconds as f64).into()])? | |
| 803 | − | .first::<Used>(None) | |
| 804 | − | .await? | |
| 805 | − | .map_or(seconds, |used| used.seconds); | |
| 806 | − | let billable = sandbox_billable(after - seconds, seconds); | |
| 807 | − | // From the price book, which follows what Cloudflare bills g1t. | |
| 808 | − | let (cost_per_second, price_per_second) = self.price("sandbox_second").await?.unwrap_or(( | |
| 809 | − | sandbox_allowance::COST_MICROS_PER_SECOND as f64, | |
| 810 | − | sandbox_allowance::MICROS_PER_SECOND as f64, | |
| 811 | − | )); | |
| 812 | − | let (charge, terms_note) = self.charged(&workspace, (billable as f64 * price_per_second).ceil() as i64).await?; | |
| 813 | − | let mut description = format!("{}: {} of sandbox time", a.description, duration(seconds)); | |
| 814 | − | if billable < seconds { | |
| 815 | − | description.push_str(if billable == 0 { | |
| 816 | − | ", within the month's free minutes" | |
| 817 | − | } else { | |
| 818 | − | ", partly within the month's free minutes" | |
| 819 | − | }); | |
| 820 | − | } | |
| 821 | − | if billable > 0 { | |
| 822 | − | description.push_str(&terms_note); | |
| 823 | − | } | |
| 784 | + | // From the price book, which follows what Cloudflare bills g1t. A | |
| 785 | + | // sandbox is the same container as a build, so without a row it is | |
| 786 | + | // a build second's cost plus the margin. | |
| 787 | + | let (cost_per_second, price_per_second) = self.price("sandbox_second").await?.unwrap_or_else(|| { | |
| 788 | + | let cost = deployments_allowance::MICROS_PER_BUILD_SECOND as f64; | |
| 789 | + | (cost, Price::price_for(cost, self.margin_percent)) | |
| 790 | + | }); | |
| 791 | + | let (charge, terms_note) = self.charged(&workspace, sandbox_charge(seconds, price_per_second)).await?; | |
| 792 | + | let description = format!("{}: {} of sandbox time{terms_note}", a.description, duration(seconds)); | |
| 824 | 793 | self.db | |
| 825 | 794 | .batch(vec![ | |
| 826 | 795 | self.db | |
| 857 | 826 | } | |
| 858 | 827 | } | |
| 859 | 828 | ||
| 860 | − | /// Of `seconds` used after `before` this month, how many are past the | |
| 861 | − | /// free minutes. | |
| 862 | − | fn sandbox_billable(before: i64, seconds: i64) -> i64 { | |
| 863 | − | let free_left = (sandbox_allowance::FREE_SECONDS - before).max(0); | |
| 864 | − | (seconds - free_left).max(0) | |
| 829 | + | /// What `seconds` of sandbox time are charged at `price_per_second`, in | |
| 830 | + | /// millionths of a dollar: every second, rounded up to the next millionth. | |
| 831 | + | fn sandbox_charge(seconds: i64, price_per_second: f64) -> i64 { | |
| 832 | + | (seconds as f64 * price_per_second).ceil() as i64 | |
| 865 | 833 | } | |
| 866 | 834 | ||
| 867 | 835 | /// `1h 2m`, `3m 12s` or `40s`. | |
| 917 | 885 | .ok() | |
| 918 | 886 | .and_then(|percent| percent.to_string().parse().ok()) | |
| 919 | 887 | .unwrap_or(20), | |
| 920 | − | orchestration_fee_micros: env | |
| 921 | − | .var("ORCHESTRATION_FEE_MICROS") | |
| 922 | − | .ok() | |
| 923 | − | .and_then(|fee| fee.to_string().parse().ok()) | |
| 924 | − | .unwrap_or(100_000), | |
| 925 | 888 | free: env.var("FREE_WHILE_BUILDING").is_ok_and(|v| v.to_string() == "true"), | |
| 926 | 889 | ceilings: limits::Ceilings::from_env(&env), | |
| 927 | 890 | prepaid_only: env.var("PREPAID_ONLY").is_ok_and(|v| v.to_string() == "true"), | |
| 1078 | 1041 | } | |
| 1079 | 1042 | ||
| 1080 | 1043 | #[test] | |
| 1081 | − | fn sandbox_seconds_are_charged_only_past_the_free_minutes() { | |
| 1082 | − | let free = sandbox_allowance::FREE_SECONDS; | |
| 1083 | − | assert_eq!(sandbox_billable(0, 600), 0); | |
| 1084 | − | assert_eq!(sandbox_billable(free - 100, 600), 500); | |
| 1085 | − | assert_eq!(sandbox_billable(free + 5, 600), 600); | |
| 1044 | + | fn sandbox_time_is_charged_from_the_first_second() { | |
| 1045 | + | // 21 millionths a second at cost, plus 20%. | |
| 1046 | + | let price = Price::price_for(21.0, 20); | |
| 1047 | + | assert_eq!(sandbox_charge(1, price), 26); | |
| 1048 | + | assert_eq!(sandbox_charge(60, price), 1_512); | |
| 1049 | + | assert_eq!(sandbox_charge(0, price), 0); | |
| 1086 | 1050 | } | |
| 1087 | 1051 | ||
| 1088 | 1052 | #[test] |
| 51 | 51 | customer_id = COALESCE(excluded.customer_id, accounts.customer_id), | |
| 52 | 52 | created_at = MIN(accounts.created_at, excluded.created_at)", | |
| 53 | 53 | "DELETE FROM accounts WHERE workspace = ?2", | |
| 54 | − | // Sandbox time counts toward the month's free minutes: the seconds add. | |
| 55 | − | "INSERT INTO sandbox_months (workspace, month, seconds) | |
| 56 | − | SELECT ?1, month, seconds FROM sandbox_months WHERE workspace = ?2 | |
| 57 | − | ON CONFLICT (workspace, month) DO UPDATE SET seconds = sandbox_months.seconds + excluded.seconds", | |
| 58 | − | "DELETE FROM sandbox_months WHERE workspace = ?2", | |
| 59 | 54 | // Replaced on each report with the month's whole figure: the newer | |
| 60 | 55 | // report wins. | |
| 61 | 56 | "INSERT INTO pending_usage (workspace, source, month, charge_micros, updated_at) | |
| 243 | 238 | fn every_table_keyed_by_a_slug_is_moved() { | |
| 244 | 239 | let all = STATEMENTS.join("\n"); | |
| 245 | 240 | for table in [ | |
| 246 | − | "ledger", "runs", "checkouts", "workspace_invoices", "sales_notes", "accounts", "sandbox_months", | |
| 241 | + | "ledger", "runs", "checkouts", "workspace_invoices", "sales_notes", "accounts", | |
| 247 | 242 | "pending_usage", "limits", "subscriptions", "month_closes", "account_members", "sales_records", | |
| 248 | 243 | "enterprise_invoice_lines", "billing_accounts", "admin_actions", "enterprise_invoices", | |
| 249 | 244 | ] { |
| 19 | 19 | ||
| 20 | 20 | /// The kind of charge a ledger row is, as the statement names it. One SQL | |
| 21 | 21 | /// expression, so grouping and filtering agree. | |
| 22 | + | /// `Runs on your own model provider` is only on older months: those runs | |
| 23 | + | /// carried a flat fee then, and pay only their sandbox time now. | |
| 22 | 24 | pub(crate) const KIND_SQL: &str = "CASE | |
| 23 | 25 | WHEN kind = 'top_up' AND reference LIKE 'crd%' THEN 'Credits from g1t' | |
| 24 | 26 | WHEN kind = 'top_up' AND amount_micros < 0 THEN 'Refunds' |
| 26 | 26 | "consumers": [{ "queue": "g1t-events-billing", "max_batch_size": 20, "max_batch_timeout": 1 }] | |
| 27 | 27 | }, | |
| 28 | 28 | "vars": { | |
| 29 | − | // What is added to a run's cost, in percent. It pays the card | |
| 30 | − | // processor's fee and the sandbox the agent ran in. | |
| 29 | + | // What is added to a model run's cost, in percent: g1t's overhead for | |
| 30 | + | // running and building it. The price book's markups are the same. | |
| 31 | 31 | "MARGIN_PERCENT": "20", | |
| 32 | − | // What a run on a workspace's own model provider is charged, in | |
| 33 | − | // millionths of a dollar: the sandbox and the orchestration around | |
| 34 | − | // it, with the model paid for at the provider. $0.10. | |
| 35 | − | "ORCHESTRATION_FEE_MICROS": "100000", | |
| 36 | 32 | // While g1t is being built out, workspaces pay nothing: runs are | |
| 37 | 33 | // recorded with what they cost, and nothing is charged. Set to | |
| 38 | 34 | // "false" when pricing starts. |