Skip to content

g1t/apps/web/app/routes/pricing.tsx

677 lines34,699 bytesCodeBlame
1import { ArrowUpRight } from "lucide-react";
2
3import { type FeaturePlan, type FreeTier, MICROS_PER_DOLLAR, type Price } from "@g1t/contracts";
4
5import type { Route } from "./+types/pricing";
6import { wholeDollars } from "../lib/billing";
7import { page } from "../lib/meta";
8import { TimeAgo } from "../components/ui";
9import { billing } from "../lib/services.server";
10
11export function meta(args: Route.MetaArgs) {
12 return page(args, {
13 title: "Pricing · g1t",
14 description:
15 "The forge is free. One plan, $20 a month per workspace with $10 of usage included; usage at what it costs g1t plus 20%. No seats, ever.",
16 });
17}
18
19export async function loader() {
20 const [book, status] = await Promise.all([billing.prices().catch(() => null), billing.status().catch(() => null)]);
21 return { book, free: status?.free ?? false };
22}
23
24/** A price in dollars, with as many digits as it needs to say anything. */
25function money(micros: number): string {
26 const dollars = micros / MICROS_PER_DOLLAR;
27 if (dollars >= 1) return `$${dollars.toFixed(2)}`;
28 if (dollars >= 0.01) return `$${dollars.toFixed(3)}`;
29 return `$${dollars.toPrecision(2)}`;
30}
31
32/** Per second is easier to read per minute. */
33function perUnit(price: Price, micros: number): string {
34 return price.unit === "second" ? `${money(micros * 60)} a minute` : `${money(micros)} per ${price.unit}`;
35}
36
37function gigabytes(bytes: number): string {
38 return `${Math.round((bytes / 1e9) * 10) / 10} GB`;
39}
40
41function count(n: number): string {
42 return n.toLocaleString("en-US");
43}
44
45/** Price-book rows the table shows in rows of their own, or not at all. */
46const SHOWN_APART = new Set([
47 "app_month",
48 "agent_models",
49 "agent_tokens",
50 "agent_tokens_own",
51 "agent_token_weight_input",
52 "agent_token_weight_output",
53 "agent_token_weight_cache_read",
54 "agent_token_weight_cache_write",
55 "gateway_models",
56 "card_fee_percent",
57 "card_fee_fixed",
58]);
59
60/** How each kind of token counts toward the agent rate, from the price book: `input ×1, …`. */
61function tokenWeights(prices: { meter: string; costMicros: number }[]): string {
62 const weight = (kind: string) => {
63 const row = prices.find((p) => p.meter === `agent_token_weight_${kind}`);
64 return row ? Number((row.costMicros / 1_000_000).toFixed(3)) : 1;
65 };
66 return `input ×${weight("input")}, output ×${weight("output")}, cache reads ×${weight("cache_read")}, cache writes ×${weight("cache_write")}`;
67}
68
69/** What the page says when billing cannot be reached: the published defaults. */
70const DEFAULT_FREE: Required<FreeTier> = {
71 trialWorkspaceMicros: 5_000_000,
72 trialMonthlyPoolMicros: 100_000_000,
73 ossPoolMicros: 25_000_000,
74 ossRepoMicros: 2_000_000,
75 freePrivateStorageBytes: 1_000_000_000,
76 auditRetentionDays: 7,
77 planAuditRetentionDays: 90,
78 minChargeMicros: 5_000_000,
79 gitOperationsIncluded: 50_000,
80 paidStartCeilingMicros: 100_000_000,
81 overageForgiveCostMicros: 50_000_000,
82};
83
84const DEFAULT_PLAN: FeaturePlan = {
85 feature: "plan",
86 title: "g1t",
87 monthlyCents: 2000,
88 includes: [
89 "$10 of usage each month at cost plus 20%, used first",
90 "Everyone in the workspace at one price, never per person",
91 "Unlimited projects, previews and repositories",
92 "Agents, checks, workflows, the merge queue, deployments and semantic search",
93 "Usage past $10 is charged at cost plus 20%, up to your spend limit",
94 ],
95 overage: "Everything is metered from the first unit at what it costs g1t plus 20%. Unused included usage does not roll over.",
96};
97
98/** The ways to reach g1t about an enterprise account. */
99const ENTERPRISE_MAIL = "mailto:hey@flagon.io?subject=Enterprise%20billing%20for%20g1t";
100
101const HOW = [
102 {
103 title: "Our cost, passed through",
104 body: "Every sandbox second, build and app request costs g1t money at Cloudflare. Each is metered and charged at that cost plus 20%, from the first second and the first request. Models are charged at the provider's price with no markup, plus a flat agent rate per million tokens for what g1t adds around them, from prepaid AI credit. Use a little, pay a little.",
105 },
106 {
107 title: "Prices follow costs, by themselves",
108 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, sandbox, build and app costs are checked against what Cloudflare billed g1t; when one moves, its price moves with it, and the change is listed below. The rest are Cloudflare's published prices, marked as such.",
109 },
110 {
111 title: "The 20% is the overhead",
112 body: "It pays for running g1t and for building and keeping up the features you use. The same 20% on everything but models, whose overhead is the agent rate, and nothing bundled in.",
113 },
114 {
115 title: "No seats, ever",
116 body: "Once a workspace is on the plan, add as many people and agents as you like. It pays one flat price for the plan, and for what it uses past what the plan includes.",
117 },
118 {
119 title: "Prices exclude tax",
120 body: "Every price here is before tax. Stripe adds sales tax, VAT or GST where it applies, worked out from the billing address, and shows it as its own line before you pay and on every receipt and invoice. A valid business tax ID is applied where the law says so.",
121 },
122 {
123 title: "Card fees are passed on, nothing more",
124 body: "Paying by card adds Stripe's card processing fee as its own line, shown before you pay, so the 20% is never spent on fees. A bank transfer or invoiced billing has no card fee.",
125 },
126];
127
128/** One row of the free-and-plan table. */
129type Row = { what: string; free: string; plan: string; note?: string };
130
131function rows(tier: Required<FreeTier>): Row[] {
132 return [
133 {
134 what: "The forge",
135 free: "Free",
136 plan: "Included",
137 note: "Public and private repositories, issues, pull requests, reviews, protected branches, code owners, search and Explore.",
138 },
139 {
140 what: "Agents, checks, workflows, merge queue",
141 free: "After a card check: the trial, and the open-source pool for checks, workflows and the merge queue on public repositories",
142 plan: "From the plan's included usage, then at cost plus 20%",
143 },
144 {
145 what: "Projects and previews",
146 free: "Not available: deployments run on g1t's machines",
147 plan: "Unlimited, and never charged for. An app no one visits costs nothing.",
148 },
149 {
150 what: "Deployments",
151 free: "Not available",
152 plan: "Builds by the second, app requests, CPU time and custom domains, each from the first at cost plus 20%",
153 },
154 {
155 what: "Semantic search",
156 free: "After a card check, from the trial",
157 plan: "From the plan's included usage, then at cost plus 20%",
158 },
159 {
160 what: "Private storage",
161 free: `${gigabytes(tier.freePrivateStorageBytes)}, never charged. Past it, pushes to private repositories stop until you make room or start the plan.`,
162 plan: `${gigabytes(tier.freePrivateStorageBytes)} free, then at cost plus 20%. Pushes never stop.`,
163 note: "Public repositories are never charged for storage.",
164 },
165 {
166 what: "Git operations",
167 free: `${count(tier.gitOperationsIncluded)} a month, never charged. Past it, slowed to 60 an hour until the month turns.`,
168 plan: `${count(tier.gitOperationsIncluded)} a month free, then at cost plus 20%. Never slowed.`,
169 note: "Clones, fetches and pushes through g1t.",
170 },
171 {
172 what: "Actions cache",
173 free: "2 GiB an entry and 10 GiB a repository, never charged",
174 plan: "The same limits, with what it stores at cost plus 20%",
175 },
176 {
177 what: "Audit log",
178 free: `${tier.auditRetentionDays} days, with export`,
179 plan: `${tier.planAuditRetentionDays} days, with export`,
180 note: "Longer by arrangement. Older entries are deleted each day.",
181 },
182 {
183 what: "Secret scanning, push protection, vulnerability alerts, security updates",
184 free: "Included",
185 plan: "Included",
186 note: "Custom patterns, validity checks, code scanning, dependency review and the security overview on private repositories are the Security and quality activation. On public repositories they are free.",
187 },
188 { what: "Single sign-on", free: "On every plan, once it is built", plan: "On every plan, once it is built" },
189 {
190 what: "Members",
191 free: "Only the people already in it: a free workspace cannot add members, invite people or invite outside collaborators",
192 plan: "Unlimited, never per seat",
193 note: "Each person can own one free workspace. More workspaces need the plan.",
194 },
195 {
196 what: "Usage past what is included",
197 free: "Not possible: a free workspace never runs up a bill",
198 plan: "Charged at cost plus 20%, up to a spend limit you set. No quotas: only your limit stops anything.",
199 },
200 ];
201}
202
203export default function Pricing({ loaderData }: Route.ComponentProps) {
204 const { book, free } = loaderData;
205 const checked = book?.prices.map((p) => p.checkedAt).filter((at): at is string => !!at).sort().at(-1);
206 const given = Object.fromEntries(Object.entries(book?.free ?? {}).filter(([, value]) => value != null));
207 const tier: Required<FreeTier> = { ...DEFAULT_FREE, ...given };
208 const plan = book?.plans?.find((p) => p.feature === "plan") ?? DEFAULT_PLAN;
209 // The Security and quality activation, as the price book prices it.
210 const securityPlan = book?.plans?.find((p) => p.feature === "security") ?? null;
211 const price = plan.monthlyCents / 100;
212 const dollars = wholeDollars;
213 return (
214 <main className="mx-auto max-w-4xl px-4 py-12">
215 <p className="text-sm font-medium text-accent">Pricing</p>
216 <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">One plan, and usage at cost plus 20%</h1>
217 <p className="mt-3 max-w-2xl text-muted">
218 The forge is free for everyone. Work that runs on g1t's machines is metered at what it costs g1t, plus 20%. The
219 numbers on this page are the live price book g1t charges from. Prices exclude tax, which is added where it applies.
220 </p>
221 {free && (
222 <div className="mt-5 rounded-xl border border-accent/30 bg-accent/5 px-4 py-3 text-sm">
223 <span className="font-medium">Free while g1t is being built out.</span>{" "}
224 <span className="text-muted">Usage is recorded at these prices but not charged for now. The plan is charged.</span>
225 </div>
226 )}
227
228 <section className="mt-10 rounded-xl border border-accent/40 bg-surface p-6">
229 <div className="flex flex-wrap items-baseline justify-between gap-3">
230 <h2 className="text-xl font-semibold tracking-tight">{plan.title}</h2>
231 <p>
232 <span className="text-3xl font-semibold tabular-nums">${price}</span>{" "}
233 <span className="text-sm text-muted">a month per workspace</span>
234 <span className="block text-right text-xs text-faint">
235 {plan.cardFeeCents ? `+ $${(plan.cardFeeCents / 100).toFixed(2)} card processing fee, plus tax where it applies` : "Plus tax where it applies"}
236 </span>
237 </p>
238 </div>
239 <ul className="mt-4 space-y-1.5 text-sm text-muted">
240 {plan.includes.map((line) => (
241 <li key={line} className="flex gap-2">
242 <span aria-hidden className="text-accent">
243 ✓
244 </span>
245 {line}
246 </li>
247 ))}
248 </ul>
249 <p className="mt-3 text-xs text-faint">{plan.overage}</p>
250 <div className="mt-5 grid gap-5 border-t border-line pt-5 text-sm sm:grid-cols-2">
251 <div>
252 <p className="font-medium">Where the ${price} goes</p>
253 <p className="mt-1 text-muted">
254 $10 comes back to you as usage: agents, sandboxes, builds and deployments at cost plus 20%, used before
255 anything else. The other $10 pays for running g1t, the free forge for everyone, and the people building it.
256 </p>
257 </div>
258 <div>
259 <p className="font-medium">No quotas</p>
260 <p className="mt-1 text-muted">
261 No count of projects, previews, builds, requests or git operations ever stops a workspace on the plan. Past
262 the $10, usage is charged at cost plus 20%, and only your spend limit stops it. Unused included usage does
263 not roll over.
264 </p>
265 </div>
266 </div>
267 <p className="mt-5 text-sm text-muted">An owner starts the plan from the workspace's Billing page.</p>
268 </section>
269
270 {securityPlan && (
271 <section id="security" className="mt-6 rounded-xl border border-line bg-surface p-6">
272 <div className="flex flex-wrap items-baseline justify-between gap-3">
273 <h2 className="text-xl font-semibold tracking-tight">{securityPlan.title}</h2>
274 <p>
275 <span className="text-3xl font-semibold tabular-nums">${(securityPlan.monthlyCents / 100).toFixed(securityPlan.monthlyCents % 100 ? 2 : 0)}</span>{" "}
276 <span className="text-sm text-muted">a month per workspace</span>
277 <span className="block text-right text-xs text-faint">
278 {securityPlan.cardFeeCents
279 ? `+ $${(securityPlan.cardFeeCents / 100).toFixed(2)} card processing fee, plus tax where it applies`
280 : "Plus tax where it applies"}
281 </span>
282 </p>
283 </div>
284 <p className="mt-2 text-sm text-muted">
285 An activation, with or without the plan: the security suite's paid parts for a workspace's private repositories.
286 </p>
287 <ul className="mt-4 space-y-1.5 text-sm text-muted">
288 {securityPlan.includes.map((line) => (
289 <li key={line} className="flex gap-2">
290 <span aria-hidden className="text-accent">
291 ✓
292 </span>
293 {line}
294 </li>
295 ))}
296 </ul>
297 <p className="mt-3 text-xs text-faint">{securityPlan.overage}</p>
298 </section>
299 )}
300
301 <h2 className="mt-14 text-xl font-semibold tracking-tight">Free and the plan, side by side</h2>
302 <p className="mt-1 text-sm text-muted">
303 Secret scanning, push protection, vulnerability alerts and security updates are free everywhere, on every plan.
304 </p>
305 {/* On a phone, one row at a time with both answers under it; a table that wide would scroll. */}
306 <ul className="mt-4 divide-y divide-line rounded-xl border border-line text-sm sm:hidden">
307 {rows(tier).map((row) => (
308 <li key={row.what} className="px-4 py-3">
309 <p className="font-medium">{row.what}</p>
310 {row.note && <p className="text-xs text-faint">{row.note}</p>}
311 <dl className="mt-2 grid grid-cols-[4.5rem_1fr] gap-x-3 gap-y-1.5">
312 <dt className="text-xs leading-5 text-faint">Free</dt>
313 <dd className="text-muted">{row.free}</dd>
314 <dt className="text-xs leading-5 text-faint">{plan.title}</dt>
315 <dd className="text-muted">{row.plan}</dd>
316 </dl>
317 </li>
318 ))}
319 </ul>
320 <div className="mt-4 hidden overflow-x-auto rounded-xl border border-line sm:block">
321 <table className="w-full min-w-[36rem] text-left text-sm">
322 <thead className="border-b border-line text-xs text-muted">
323 <tr>
324 <th className="px-4 py-2.5 font-medium">
325 <span className="sr-only">What</span>
326 </th>
327 <th className="px-4 py-2.5 font-medium">Free</th>
328 <th className="px-4 py-2.5 font-medium">
329 {plan.title}, ${price} a month
330 </th>
331 </tr>
332 </thead>
333 <tbody className="divide-y divide-line">
334 {rows(tier).map((row) => (
335 <tr key={row.what} className="align-top">
336 <td className="px-4 py-3">
337 <p className="font-medium">{row.what}</p>
338 {row.note && <p className="text-xs text-faint">{row.note}</p>}
339 </td>
340 <td className="px-4 py-3 text-muted">{row.free}</td>
341 <td className="px-4 py-3 text-muted">{row.plan}</td>
342 </tr>
343 ))}
344 </tbody>
345 </table>
346 </div>
347 <p className="mt-3 text-sm text-muted">
348 Git operations cost g1t money too: Cloudflare charges g1t $0.15 per 1,000. {count(tier.gitOperationsIncluded)} a
349 month is far more than an active workspace uses; an agent run takes two to four.
350 </p>
351
352 <h2 className="mt-14 text-xl font-semibold tracking-tight">No card, no compute</h2>
353 <div className="mt-3 max-w-3xl space-y-3 text-sm text-muted">
354 <p>
355 Agents, checks, workflows, the merge queue, deployments and semantic search run on g1t's machines. They need
356 the plan, or a card check.
357 </p>
358 <p>
359 The reason: compute costs g1t real money from the first second, and free compute draws people who use it to
360 mine cryptocurrency. A card check is the smallest gate that stops that, with one trial per card.
361 </p>
362 <p>
363 A card check is never charged. Stripe saves and verifies the card, with 3-D Secure where your bank asks for it.
364 Nothing is charged until an owner starts the plan, and then the same card is used, without asking for it again.
365 The trial needs a credit or debit card; prepaid cards can still pay for the plan.
366 </p>
367 </div>
368
369 <div className="mt-5 grid gap-4 sm:grid-cols-2">
370 <section className="rounded-xl border border-line bg-surface p-5">
371 <h3 className="font-medium">The trial</h3>
372 <p className="mt-1.5 text-sm text-muted">
373 {dollars(tier.trialWorkspaceMicros)} of usage, once per workspace and once per card, after a card check with a credit or
374 debit card. It comes from a{" "}
375 {dollars(tier.trialMonthlyPoolMicros)} pool each month for everyone; when a month's pool is given out, new
376 trials wait until the 1st. The trial does not cover deployments and never turns into a charge. If the last
377 run on a trial goes past it, g1t covers the difference.
378 </p>
379 </section>
380 <section className="rounded-xl border border-line bg-surface p-5">
381 <h3 className="font-medium">The open-source pool</h3>
382 <p className="mt-1.5 text-sm text-muted">
383 Checks, workflows and the merge queue on public repositories, after a card check:{" "}
384 {dollars(tier.ossPoolMicros)} a month in all, up to {dollars(tier.ossRepoMicros)} a month per repository.
385 Agents do not run from the pool. When the pool or a repository's share is used up, the work waits for the
386 month to turn, or the workspace pays as usual.
387 </p>
388 </section>
389 </div>
390
391 <h2 className="mt-14 text-xl font-semibold tracking-tight">Limits, and how to raise them</h2>
392 <p className="mt-1 max-w-3xl text-sm text-muted">
393 g1t lets usage run ahead of payment only so far. Limits keep a mistake small, for you and for g1t.
394 </p>
395 <ul className="mt-4 grid gap-3 text-sm sm:grid-cols-2">
396 <li className="rounded-xl border border-line bg-surface p-4">
397 <p className="font-medium">{dollars(tier.paidStartCeilingMicros)} in the first month</p>
398 <p className="mt-1 text-muted">
399 A workspace on the plan can use up to {dollars(tier.paidStartCeilingMicros)} in its first month. After that,
400 the limit grows with payments that clear.
401 </p>
402 </li>
403 <li className="rounded-xl border border-line bg-surface p-4">
404 <p className="font-medium">Your own spend limit</p>
405 <p className="mt-1 text-muted">
406 Owners set a monthly spend limit anywhere up to the highest limit the workspace has had, with no approval.
407 Once, you can raise it yourself to twice that.
408 </p>
409 </li>
410 <li className="rounded-xl border border-line bg-surface p-4">
411 <p className="font-medium">Prepay to raise it now</p>
412 <p className="mt-1 text-muted">
413 Prepaying from $25 raises what you can use by the same amount, at once. From $1,000 you can also pay by bank
414 transfer.
415 </p>
416 </li>
417 <li className="rounded-xl border border-line bg-surface p-4">
418 <p className="font-medium">Raise my limit</p>
419 <p className="mt-1 text-muted">
420 Need more? Ask from Billing with the amount, why, and what you expect to spend. You get an answer within one
421 business day, in the app and by email.
422 </p>
423 </li>
424 </ul>
425
426 <h2 className="mt-14 text-xl font-semibold tracking-tight">When usage runs high</h2>
427 <ul className="mt-4 grid gap-3 text-sm sm:grid-cols-2">
428 <li className="rounded-xl border border-line bg-surface p-4">
429 <p className="font-medium">A spike pauses new work</p>
430 <p className="mt-1 text-muted">
431 When an hour's spend is five times your usual hour, and at least $5, new compute waits for an owner to choose
432 Keep going or Stop. Runs already going finish.
433 </p>
434 </li>
435 <li className="rounded-xl border border-line bg-surface p-4">
436 <p className="font-medium">Caps per run and per issue</p>
437 <p className="mt-1 text-muted">
438 One agent run stops at $2 and the agents on one issue at $10 in all, so a run stuck in a loop stops on its own.
439 Owners change both on Billing.
440 </p>
441 </li>
442 <li className="rounded-xl border border-line bg-surface p-4">
443 <p className="font-medium">Alerts at 50, 75, 90 and 100%</p>
444 <p className="mt-1 text-muted">
445 Of the plan's included usage, your spend limit and g1t's limit: in the app and by email.
446 </p>
447 </li>
448 <li className="rounded-xl border border-line bg-surface p-4">
449 <p className="font-medium">Spent more than you meant to? Tell us</p>
450 <p className="mt-1 text-muted">
451 Tell us from Billing what happened. Once in 12 months, g1t can credit back usage you did not mean, always
452 including its 20% on it. Larger credits, or a second one within 12 months, are reviewed by a person.
453 </p>
454 </li>
455 </ul>
456 <p className="mt-3 text-sm text-muted">
457 No card is charged less than {dollars(tier.minChargeMicros)} when a month closes, so a payment's fee is never most
458 of it. Smaller amounts carry over to the next invoice. Charges at a limit always go through.
459 </p>
460
461 <div className="mt-14 grid gap-4 sm:grid-cols-2">
462 {HOW.map((item) => (
463 <section key={item.title} className="rounded-xl border border-line bg-surface p-5">
464 <h2 className="font-medium">{item.title}</h2>
465 <p className="mt-1.5 text-sm text-muted">{item.body}</p>
466 </section>
467 ))}
468 </div>
469
470 <h2 className="mt-14 text-xl font-semibold tracking-tight">Usage</h2>
471 <p className="mt-1 text-sm text-muted">
472 {checked ? (
473 <>
474 Last checked against Cloudflare's bill <TimeAgo at={checked} />.
475 </>
476 ) : (
477 "Starting from Cloudflare's published prices; checked against its bill daily."
478 )}
479 </p>
480 <div className="mt-4 overflow-x-auto rounded-xl border border-line">
481 <table className="w-full text-left text-sm sm:min-w-[36rem]">
482 <thead className="border-b border-line text-xs text-muted">
483 <tr>
484 <th className="px-4 py-2.5 font-medium">What</th>
485 <th className="px-4 py-2.5 font-medium">Costs g1t</th>
486 {/* The markup is in what you pay; on a phone the column gives way. */}
487 <th className="hidden px-4 py-2.5 font-medium sm:table-cell">Markup</th>
488 <th className="px-4 py-2.5 font-medium">You pay</th>
489 </tr>
490 </thead>
491 <tbody className="divide-y divide-line">
492 <tr>
493 <td className="px-4 py-3">
494 <p className="font-medium">Agent models</p>
495 <p className="text-xs text-faint">g1t's hosted models for agent runs, the cheapest that can do each job, paid from AI credit</p>
496 </td>
497 <td className="px-4 py-3 text-muted">What the provider charges, per request</td>
498 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">{book?.modelMarginPercent ?? 0}%</td>
499 <td className="px-4 py-3 text-muted">{book?.modelMarginPercent ? `Cost + ${book.modelMarginPercent}%` : "The provider's price"}</td>
500 </tr>
501 {(() => {
502 const rate = book?.prices.find((p) => p.meter === "agent_tokens");
503 const gateway = book?.prices.find((p) => p.meter === "gateway_models");
504 const feePercent = book?.prices.find((p) => p.meter === "card_fee_percent");
505 const feeFixed = book?.prices.find((p) => p.meter === "card_fee_fixed");
506 const coming = book?.changes.find((c) => c.meter === "agent_tokens" && c.effectiveAt);
507 const ownRate = book?.prices.find((p) => p.meter === "agent_tokens_own");
508 const ownComing = book?.changes.find((c) => c.meter === "agent_tokens_own" && c.effectiveAt);
509 return (
510 <>
511 <tr>
512 <td className="px-4 py-3">
513 <p className="font-medium">g1t agent rate</p>
514 <p className="text-xs text-faint">Context, memory, routing and orchestration, on every token an agent run uses</p>
515 <p className="text-xs text-faint">Tokens count by kind: {tokenWeights(book?.prices ?? [])}</p>
516 </td>
517 <td className="px-4 py-3 text-muted">A flat rate</td>
518 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
519 <td className="px-4 py-3 font-mono text-xs tabular-nums">
520 {rate && rate.priceMicros > 0 ? `${money(rate.priceMicros)} per million tokens` : coming ? `${money(coming.newCostMicros)} per million tokens from ${coming.effectiveAt!.slice(0, 10)}` : "$0.25 per million tokens"}
521 </td>
522 </tr>
523 <tr>
524 <td className="px-4 py-3">
525 <p className="font-medium">g1t agent rate, your own model key</p>
526 <p className="text-xs text-faint">The same, on runs that use your own provider; its models are billed by your provider, not g1t</p>
527 </td>
528 <td className="px-4 py-3 text-muted">A flat rate</td>
529 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
530 <td className="px-4 py-3 font-mono text-xs tabular-nums">
531 {ownRate && ownRate.priceMicros > 0 ? `${money(ownRate.priceMicros)} per million tokens` : ownComing ? `${money(ownComing.newCostMicros)} per million tokens from ${ownComing.effectiveAt!.slice(0, 10)}` : "$0.25 per million tokens"}
532 </td>
533 </tr>
534 <tr>
535 <td className="px-4 py-3">
536 <p className="font-medium">
537 AI Gateway <span className="ml-1 rounded bg-accent/15 px-1.5 py-0.5 text-xs text-accent">Free during beta</span>
538 </p>
539 <p className="text-xs text-faint">Your own apps calling models through g1t; your own key is free on the plan</p>
540 </td>
541 <td className="px-4 py-3 text-muted">What the provider charges</td>
542 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">{gateway?.markupPercent ?? 0}%</td>
543 <td className="px-4 py-3 text-muted">{gateway?.markupPercent ? `Cost + ${gateway.markupPercent}%` : "The provider's price"}</td>
544 </tr>
545 {feePercent && feeFixed && (
546 <tr>
547 <td className="px-4 py-3">
548 <p className="font-medium">Card processing fee</p>
549 <p className="text-xs text-faint">
550 On every card payment, as its own line shown before you pay; never on bank transfers or invoiced billing
551 </p>
552 </td>
553 <td className="px-4 py-3 text-muted">Stripe's fee</td>
554 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
555 <td className="px-4 py-3 font-mono text-xs tabular-nums">
556 {(feePercent.priceMicros / 10_000).toFixed(1)}% + {money(feeFixed.priceMicros)}
557 </td>
558 </tr>
559 )}
560 </>
561 );
562 })()}
563 <tr>
564 <td className="px-4 py-3">
565 <p className="font-medium">Projects, previews and apps</p>
566 <p className="text-xs text-faint">Having them. An app's requests and CPU time are charged below.</p>
567 </td>
568 <td className="px-4 py-3 text-muted">Next to nothing</td>
569 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
570 <td className="px-4 py-3 text-muted">Not charged</td>
571 </tr>
572 {(book?.prices ?? []).filter((price) => !SHOWN_APART.has(price.meter)).map((price) => (
573 <tr key={price.meter}>
574 <td className="px-4 py-3">
575 <p className="font-medium">{price.title}</p>
576 <p className="text-xs text-faint">
577 {price.meter === "actions_cache" ? "On the plan only. A free workspace is never charged for it. " : ""}
578 {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"}
579 </p>
580 </td>
581 <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td>
582 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">{price.markupPercent}%</td>
583 <td className="px-4 py-3 font-mono text-xs tabular-nums">{perUnit(price, price.priceMicros)}</td>
584 </tr>
585 ))}
586 </tbody>
587 </table>
588 </div>
589
590 <h2 className="mt-14 text-xl font-semibold tracking-tight">Enterprise</h2>
591 <p className="mt-1 text-sm text-muted">
592 For organizations with several workspaces: one invoice and one limit. The prices are the ones on this page.
593 </p>
594 <section className="mt-4 rounded-xl border border-line bg-surface p-5">
595 <ul className="grid gap-3 text-sm sm:grid-cols-2">
596 <li>
597 <p className="font-medium">Consolidated invoicing</p>
598 <p className="text-muted">One monthly invoice for every workspace, paid by card or bank transfer.</p>
599 </li>
600 <li>
601 <p className="font-medium">Custom terms</p>
602 <p className="text-muted">One limit across every workspace, invoice terms and a billing contact, set with you.</p>
603 </li>
604 <li>
605 <p className="font-medium">Security on every plan</p>
606 <p className="text-muted">
607 The audit log with export, {tier.planAuditRetentionDays} days or longer by arrangement, and secret push
608 protection, for every workspace.
609 </p>
610 </li>
611 <li>
612 <p className="font-medium">
613 Single sign-on <span className="ml-1 rounded bg-raised px-1.5 py-0.5 text-xs text-muted">Coming</span>
614 </p>
615 <p className="text-muted">Through your identity provider, on every plan once it is built. Not available yet.</p>
616 </li>
617 </ul>
618 <a
619 href={ENTERPRISE_MAIL}
620 className="mt-5 inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm hover:border-line-strong"
621 >
622 Write to us about enterprise
623 <ArrowUpRight size={14} />
624 </a>
625 </section>
626
627 <h2 className="mt-14 text-xl font-semibold tracking-tight">Price changes</h2>
628 {book && book.changes.length > 0 ? (
629 <ul className="mt-4 divide-y divide-line rounded-xl border border-line">
630 {book.changes.map((change) => {
631 const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter;
632 // What you pay moved: the cost, the markup, or both.
633 const before = change.oldCostMicros * (100 + (change.oldMarkupPercent ?? change.markupPercent));
634 const after = change.newCostMicros * (100 + change.markupPercent);
635 const up = after > before;
636 const fresh = after === before || before === 0;
637 return (
638 <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm">
639 <p>
640 <span className="font-medium">{title}</span>{" "}
641 {fresh ? (
642 <span className="text-muted">new</span>
643 ) : (
644 <span className={up ? "text-warn" : "text-accent"}>
645 {up ? "up" : "down"} {Math.abs((after / before - 1) * 100).toFixed(1)}%
646 </span>
647 )}
648 </p>
649 <p className="mt-0.5 text-xs text-faint">
650 {change.effectiveAt ? (
651 <>
652 {change.reason} · <span className="text-fg-soft">takes effect {change.effectiveAt.slice(0, 10)}</span>
653 </>
654 ) : (
655 <>
656 {change.reason} · <TimeAgo at={change.createdAt} />
657 </>
658 )}
659 </p>
660 </li>
661 );
662 })}
663 </ul>
664 ) : (
665 <p className="mt-2 text-sm text-muted">None yet. When a price moves, it is listed here with why.</p>
666 )}
667
668 <a
669 href="https://docs.g1t.sh/guides/usage-and-billing/"
670 className="mt-12 inline-flex items-center gap-1.5 text-sm text-accent hover:underline"
671 >
672 How usage and billing work
673 <ArrowUpRight size={14} />
674 </a>
675 </main>
676 );
677}