flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/crates/contracts/src/billing.rs

1,526 lines52,855 bytesCodeBlame
1//! The billing service: what agents cost, charged to the workspace they
2//! worked for.
3//!
4//! A workspace buys credit and each agent run deducts what it cost, plus
5//! g1t's margin. With no credit, no agent starts. Money is held in
6//! millionths of a US dollar, so that a run costing a fraction of a cent is
7//! recorded exactly.
8//!
9//! Each `*Args` struct is the argument of the method of the same name,
10//! served at `POST /rpc/<method>`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::RepoPath;
15use crate::{User, Viewer};
16
17/// Millionths of a US dollar in one dollar.
18pub const MICROS_PER_DOLLAR: i64 = 1_000_000;
19
20/// Whether workspaces are charged for agents at all, and with real money.
21/// `status` takes nothing and returns this.
22#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
23pub struct Status {
24 /// False when no payment provider is configured: nothing is charged,
25 /// and who may run agents is decided some other way.
26 pub enabled: bool,
27 /// False while the payment provider is in its test mode, where cards
28 /// are not real.
29 pub live: bool,
30 /// True while g1t is being built out: runs are recorded, with what
31 /// they cost, but nothing is charged and no credit is needed. Not a
32 /// promise that it stays free.
33 #[serde(default)]
34 pub free: bool,
35}
36
37/// `trial`: a workspace's trial credit, so people can try g1t (its agents on
38/// g1t's hosted models among it) without a key or a card of their own. Each
39/// new workspace gets one grant of usage credit (`TRIAL_WORKSPACE_MICROS`),
40/// made when it first uses something, out of a pool for everyone that
41/// resets each calendar month (`TRIAL_MONTHLY_POOL_MICROS`). When this
42/// month's pool is given out, new grants wait for the next month. Returns
43/// `Trial`.
44#[derive(Debug, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct TrialArgs {
47 pub workspace: String,
48 /// Workspaces open to hosted models anyway, whose use is not counted
49 /// against the pool.
50 #[serde(default)]
51 pub exempt: Vec<String>,
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct Trial {
57 /// Whether its agents may use g1t's hosted models on the trial now: it
58 /// has credit left, or this month's pool can still grant it some.
59 pub open: bool,
60 /// What the trial has paid for so far, in millionths of a dollar.
61 pub used_micros: i64,
62 /// Its grant, or what it would be granted.
63 pub limit_micros: i64,
64 /// No longer used: the trial does not end on a date. Kept for older
65 /// readers; always null.
66 pub ends_at: Option<String>,
67 /// Why it is closed: `off` (no trials), `used` (this workspace's grant
68 /// is spent) or `pool` (this month's grants are all given out; see
69 /// `waits_until`). `ended` is no longer sent.
70 pub reason: Option<String>,
71 /// Whether the workspace has its grant already.
72 #[serde(default)]
73 pub granted: bool,
74 /// RFC 3339: when a workspace waiting for a grant can get one, the
75 /// first of next month. Only with reason `pool`.
76 #[serde(default)]
77 pub waits_until: Option<String>,
78}
79
80/// A workspace's standing.
81#[derive(Clone, Debug, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct Account {
84 pub workspace: String,
85 /// Credit left, in millionths of a dollar. Can dip below zero by the
86 /// cost of the runs that were under way when it ran out.
87 pub balance_micros: i64,
88 pub status: Status,
89 /// What is added to a run's cost, in percent.
90 pub margin_percent: u32,
91 /// The card g1t charges as the workspace nears its limit and when a
92 /// month closes, if one is on file.
93 #[serde(default)]
94 pub card: Option<Card>,
95}
96
97/// A saved card, as far as it is safe to show.
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct Card {
101 /// `visa`, `mastercard`, ...
102 pub brand: String,
103 pub last4: String,
104 pub exp_month: u32,
105 pub exp_year: u32,
106}
107
108/// `billing_portal`: Stripe's hosted billing page for the workspace, where
109/// an owner adds or replaces the card, sees invoices and receipts, and sets
110/// the billing email and address. g1t never handles card numbers. Owners
111/// only. Returns `Outcome<Checkout>` (its `url`); Stripe sends them back
112/// to `return_url`.
113#[derive(Debug, Serialize, Deserialize)]
114pub struct BillingPortalArgs {
115 pub actor: User,
116 pub workspace: String,
117 pub return_url: String,
118}
119
120/// `admin_billing_link`: for staff to send a customer: their Stripe billing
121/// page. Returns `Outcome<BillingLink>`.
122#[derive(Debug, Serialize, Deserialize)]
123pub struct AdminBillingLinkArgs {
124 pub workspace: String,
125 pub by: String,
126}
127
128#[derive(Clone, Debug, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct BillingLink {
131 /// A one-time session on Stripe's billing page, signed in already.
132 pub portal_url: String,
133 /// The billing page's sign-in page, which does not expire: the
134 /// customer signs in with the email Stripe has for them.
135 pub login_url: Option<String>,
136 pub customer_email: Option<String>,
137 pub expires_note: String,
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142pub enum EntryKind {
143 /// Credit bought with a card.
144 TopUp,
145 /// An agent's run, or a paid feature's usage past its allowance.
146 Usage,
147}
148
149/// One line of a workspace's statement.
150#[derive(Clone, Debug, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct LedgerEntry {
153 pub id: String,
154 pub kind: EntryKind,
155 /// Positive for credit added, negative for usage.
156 pub amount_micros: i64,
157 pub description: String,
158 /// For usage: the repository and pull request the agent worked on.
159 pub repo: Option<String>,
160 pub number: Option<u32>,
161 /// For usage: `implement`, `review` or `update`.
162 pub task: Option<String>,
163 /// For usage: the model, by its public name.
164 pub model: Option<String>,
165 /// For usage: `g1t` when g1t paid the model provider, `workspace` when
166 /// the workspace's own account did. Runs on the workspace's own
167 /// provider pay only their sandbox time now, so only older entries
168 /// are `workspace`.
169 #[serde(default = "g1t")]
170 pub billed_to: String,
171 /// For a top-up: the username of whoever paid.
172 pub created_by: Option<String>,
173 /// RFC 3339.
174 pub created_at: String,
175 /// The workspace the line belongs to, which tells an enterprise's
176 /// lines apart.
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub workspace: Option<String>,
179 /// For usage: what the Team plan's monthly credit paid of it. The
180 /// entry's `amount_micros` is what is left to pay.
181 #[serde(default)]
182 pub credit_micros: i64,
183 /// For usage: what the workspace's trial credit paid of it.
184 #[serde(default)]
185 pub trial_micros: i64,
186 /// For usage: what g1t's open-source pool paid of it.
187 #[serde(default)]
188 pub oss_micros: i64,
189}
190
191fn g1t() -> String {
192 "g1t".to_owned()
193}
194
195/// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`,
196/// newest first). Members of the workspace only.
197#[derive(Debug, Serialize, Deserialize)]
198pub struct AccountArgs {
199 pub workspace: String,
200 pub viewer: Viewer,
201}
202
203/// `checkout`: starts a card payment for credit. Owners of the workspace
204/// only. Returns `Outcome<Checkout>`.
205#[derive(Debug, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct CheckoutArgs {
208 pub actor: User,
209 pub workspace: String,
210 /// How much credit to buy, in cents.
211 pub amount_cents: u32,
212 /// Where the payment page sends the person afterwards. The payment's
213 /// id is appended as `session`.
214 pub return_url: String,
215}
216
217#[derive(Debug, Serialize, Deserialize)]
218pub struct Checkout {
219 /// The payment page to send the person to.
220 pub url: String,
221}
222
223/// `confirm`: credits a payment once the provider says it was made. Safe
224/// to call any number of times. Returns `Outcome<Account>`.
225#[derive(Debug, Serialize, Deserialize)]
226pub struct ConfirmArgs {
227 pub workspace: String,
228 pub viewer: Viewer,
229 /// The payment's id, as returned to `return_url`.
230 pub session: String,
231}
232
233/// `can_start`: whether a workspace may start an agent now, asked before
234/// anything is opened for it. Returns `Outcome<bool>`: a failure, with the
235/// reason to show, when it has no credit.
236#[derive(Debug, Serialize, Deserialize)]
237pub struct CanStartArgs {
238 pub workspace: String,
239}
240
241/// `start_run`: asks whether a workspace may start an agent, and opens the
242/// run it will be charged for. Called by the runner service. Returns
243/// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure
244/// when the workspace has no credit.
245#[derive(Debug, Serialize, Deserialize)]
246pub struct StartRunArgs {
247 pub workspace: String,
248 pub repo: RepoPath,
249 pub number: u32,
250 /// `implement`, `review` or `update`.
251 pub task: String,
252 /// The model, by its public name.
253 pub model: String,
254 /// `workspace` when the run uses the workspace's own model provider.
255 /// The runner, which is TypeScript, sends it as `billedTo`.
256 #[serde(default = "g1t", alias = "billedTo")]
257 pub billed_to: String,
258 /// The model session's id, when its requests go through g1t's AI
259 /// Gateway: settling charges the run what the gateway priced them at.
260 #[serde(default)]
261 pub session: Option<String>,
262}
263
264#[derive(Clone, Debug, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct RunTicket {
267 pub run_id: String,
268 /// Lets the sandbox, and nothing else, report what this run cost.
269 pub token: String,
270}
271
272/// `finish_run`: what a run cost, as its sandbox reports it. Charged once.
273/// Returns `Outcome<bool>`.
274#[derive(Debug, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct FinishRunArgs {
277 pub run_id: String,
278 pub token: String,
279 /// What the model provider charged, in US dollars.
280 pub cost_usd: f64,
281 #[serde(default)]
282 pub turns: u32,
283}
284
285
286/// `usage`: what a workspace's agents cost over a period, broken down.
287/// Members only. Returns `Outcome<Usage>`.
288#[derive(Debug, Serialize, Deserialize)]
289pub struct UsageArgs {
290 pub workspace: String,
291 pub viewer: Viewer,
292 /// RFC 3339: the start of the period. The period runs to now.
293 pub since: String,
294}
295
296/// One slice of usage: what it was for, what it cost, how many runs.
297#[derive(Clone, Debug, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub struct UsageSlice {
300 pub key: String,
301 pub micros: i64,
302 pub runs: u32,
303}
304
305/// What a workspace's agents cost over a period.
306#[derive(Clone, Debug, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct Usage {
309 pub since: String,
310 /// Charged, including g1t's margin.
311 pub spent_micros: i64,
312 /// What g1t's model provider charged, before the margin.
313 pub cost_micros: i64,
314 /// What runs on the workspace's own provider cost there, as the harness
315 /// estimated it. Not charged by g1t.
316 pub provider_micros: i64,
317 /// What the runs used, at cost: g1t's models and the workspace's own
318 /// provider together, whatever was charged for them.
319 pub used_micros: i64,
320 /// g1t charges nothing for now. The slices then measure usage at cost,
321 /// since every charge is zero.
322 pub free: bool,
323 pub runs: u32,
324 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
325 pub by_day: Vec<UsageSlice>,
326 /// Per task: implement, review, revise, update, plan.
327 pub by_task: Vec<UsageSlice>,
328 /// Per repository, `namespace/name`.
329 pub by_repo: Vec<UsageSlice>,
330 /// The pull requests that cost most, as `namespace/name#number`.
331 pub by_pull: Vec<UsageSlice>,
332 /// Per model, by its public name.
333 pub by_model: Vec<UsageSlice>,
334 /// Credit bought in the period.
335 pub added_micros: i64,
336}
337
338/// A paid feature a workspace turns on with a monthly plan, the way
339/// Cloudflare's Workers for Platforms or Vercel's Pro are bought. Never
340/// free: `FREE_WHILE_BUILDING` and the free model allowance do not cover
341/// it.
342#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
343#[serde(rename_all = "snake_case")]
344pub enum Feature {
345 /// Previews per pull request and production on g1t.page.
346 Deployments,
347 /// The Team plan: a flat price per workspace, never per person, with a
348 /// monthly usage credit, more private storage and a longer audit log.
349 Team,
350}
351
352impl Feature {
353 pub const ALL: [Feature; 2] = [Feature::Team, Feature::Deployments];
354
355 pub fn as_str(self) -> &'static str {
356 match self {
357 Feature::Deployments => "deployments",
358 Feature::Team => "team",
359 }
360 }
361
362 pub fn parse(name: &str) -> Option<Feature> {
363 Feature::ALL.into_iter().find(|feature| feature.as_str() == name)
364 }
365
366 pub fn title(self) -> &'static str {
367 match self {
368 Feature::Deployments => "Deployments",
369 Feature::Team => "Team",
370 }
371 }
372}
373
374/// What the Deployments plan includes each month; usage past it is charged
375/// at cost plus the margin. The billing service describes the plan with
376/// these and the deployments service meters against them.
377pub mod deployments_allowance {
378 /// Apps deployed at once: production and previews together.
379 pub const APPS: u32 = 10;
380 pub const REQUESTS: u64 = 1_000_000;
381 pub const CPU_MS: u64 = 3_000_000;
382 /// What Cloudflare charges g1t past that, in millionths of a dollar.
383 pub const MICROS_PER_APP_MONTH: i64 = 20_000;
384 pub const MICROS_PER_MILLION_REQUESTS: i64 = 300_000;
385 pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000;
386 /// What one second of a build's sandbox costs g1t (Cloudflare
387 /// Containers, standard-1: half a vCPU, 4 GiB, 8 GB disk), rounded up.
388 pub const MICROS_PER_BUILD_SECOND: i64 = 21;
389 /// Build time the plan includes each month: 200 minutes, about $0.25 of
390 /// the plan's price at cost. Builds past it are charged by the second
391 /// at cost plus the margin. Billing's `DEPLOYMENTS_BUILD_SECONDS`
392 /// overrides it.
393 pub const BUILD_SECONDS: u32 = 12_000;
394 /// Custom domains across the workspace (Cloudflare for SaaS custom
395 /// hostnames); each one past these is charged by the month.
396 pub const CUSTOM_DOMAINS: u32 = 3;
397 /// What one custom hostname costs g1t a month: $0.10.
398 pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000;
399}
400
401/// `record_sandbox`: how long one sandbox ran for a workspace, reported by
402/// the runner when it stops. Every sandbox g1t starts for a workspace
403/// (agents, reviews, checks, the merge queue, workflow jobs) is metered by
404/// the second, from the first: recorded once per `reference`, with what it
405/// cost g1t, and charged at the price book's `sandbox_second` price unless
406/// `FREE_WHILE_BUILDING`. Deploy builds are charged by the Deployments plan
407/// instead.
408/// Returns `Outcome<bool>`: false if that reference was recorded before.
409#[derive(Debug, Serialize, Deserialize)]
410#[serde(rename_all = "camelCase")]
411pub struct RecordSandboxArgs {
412 pub workspace: String,
413 pub seconds: u32,
414 /// What ran, e.g. `Checks on acme/api#12`.
415 pub description: String,
416 /// `namespace/name`.
417 pub repo: Option<String>,
418 /// Unique to the run.
419 pub reference: String,
420}
421
422/// How much a workspace has earned g1t's trust with money, which sets how
423/// far its unpaid usage can go before its work stops.
424#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
425#[serde(rename_all = "snake_case")]
426pub enum Trust {
427 /// No live payment yet: only a little past the free allowances.
428 New,
429 /// Has paid g1t real money: the ceiling grows with what it has paid.
430 Paid,
431 /// Has paid steadily for months, with nothing disputed or declined:
432 /// the ceiling follows its monthly spend, up to $10,000, by itself.
433 Established,
434 /// A ceiling g1t set by hand, after talking to the workspace.
435 Reviewed,
436 /// g1t's own workspaces: no ceiling.
437 Internal,
438}
439
440#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
441#[serde(rename_all = "snake_case")]
442pub enum LimitState {
443 Ok,
444 /// Past 80% of the ceiling.
445 Warning,
446 /// At or past it: no new sandboxes, builds or app requests.
447 Stopped,
448}
449
450/// How far a workspace's unpaid usage has gone this month, and where its
451/// work stops: like Fly's or Cloudflare's limits for new accounts, so no
452/// one runs up costs g1t cannot collect. Usage counts at what it cost g1t
453/// or what it is charged, whichever is more, so it counts while g1t is
454/// free too.
455#[derive(Clone, Debug, Serialize, Deserialize)]
456#[serde(rename_all = "camelCase")]
457pub struct Limit {
458 pub workspace: String,
459 /// The account that pays, whose usage and payments the limit counts:
460 /// the workspace's own, or its enterprise's.
461 #[serde(default)]
462 pub account: String,
463 #[serde(default)]
464 pub account_name: String,
465 pub trust: Trust,
466 /// Usage this month (UTC) less what was paid this month.
467 pub exposure_micros: i64,
468 /// Where work stops: the lower of g1t's ceiling and the owner's own
469 /// spend limit. None for g1t's own workspaces.
470 pub ceiling_micros: Option<i64>,
471 /// The ceiling g1t sets from `trust`.
472 pub trust_ceiling_micros: Option<i64>,
473 /// The owner's own monthly limit, if they set one.
474 pub spend_limit_micros: Option<i64>,
475 pub state: LimitState,
476 /// What to tell people when work is stopped or close to it.
477 pub message: Option<String>,
478 /// Charged this month, which the spend limit is measured against.
479 #[serde(default)]
480 pub spent_micros: i64,
481 /// True while the owners have not chosen a spend limit of their own, so
482 /// the automatic one applies: $200, or twice last month's spend.
483 #[serde(default)]
484 pub default_spend_limit: bool,
485 /// The most the owners may set their own limit to: g1t's ceiling. To
486 /// go past it, they contact g1t.
487 #[serde(default)]
488 pub available_micros: Option<i64>,
489 /// How the ceiling grows from here, in a sentence.
490 #[serde(default)]
491 pub growth: Option<String>,
492}
493
494/// `limit`: a workspace's limit, for its members. Returns `Outcome<Limit>`.
495#[derive(Debug, Serialize, Deserialize)]
496pub struct LimitArgs {
497 pub workspace: String,
498 pub viewer: Viewer,
499}
500
501/// `check_limit`: the same, for the services that enforce it. Returns
502/// `Outcome<Limit>`.
503#[derive(Debug, Serialize, Deserialize)]
504pub struct CheckLimitArgs {
505 pub workspace: String,
506}
507
508/// `note_pending`: usage this month that will be charged later, such as
509/// app traffic past a plan, so the workspace's limit counts it now. Each
510/// report replaces the last for that workspace, source and month. Called
511/// by the service that meters it. Returns `bool`.
512#[derive(Debug, Serialize, Deserialize)]
513#[serde(rename_all = "camelCase")]
514pub struct NotePendingArgs {
515 pub workspace: String,
516 /// `deployments`, `security` (scans), `context` (search embeddings) or
517 /// `storage`. Billing charges `security`, `context` and `storage`
518 /// itself once the month is over; `deployments` charges its own.
519 pub source: String,
520 /// What it cost g1t so far this month, before the margin.
521 pub cost_micros: i64,
522}
523
524/// `set_spend_limit`: the owner's own monthly ceiling, under g1t's; None
525/// removes it. Owners only. Returns `Outcome<Limit>`.
526#[derive(Debug, Serialize, Deserialize)]
527#[serde(rename_all = "camelCase")]
528pub struct SetSpendLimitArgs {
529 pub actor: User,
530 pub workspace: String,
531 /// A monthly limit, at most what is available; None goes back to the
532 /// default.
533 pub spend_limit_micros: Option<i64>,
534 /// Use everything available, with no limit of their own.
535 #[serde(default)]
536 pub use_full_limit: bool,
537}
538
539/// One metered unit: what it costs g1t, and what it is sold at. The price
540/// is always `cost × (100 + markup) / 100`, so it follows the cost.
541#[derive(Clone, Debug, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct Price {
544 /// `sandbox_second`, `build_second`, `app_requests`, `app_cpu`, `app_month`.
545 pub meter: String,
546 pub title: String,
547 pub unit: String,
548 /// Millionths of a dollar per unit; may have a fraction.
549 pub cost_micros: f64,
550 pub markup_percent: u32,
551 pub price_micros: f64,
552 /// `list`: Cloudflare's published price. `cloudflare`: what Cloudflare
553 /// actually billed g1t, measured.
554 pub source: String,
555 /// When it was last checked against Cloudflare's bill.
556 pub checked_at: Option<String>,
557 pub updated_at: String,
558}
559
560impl Price {
561 pub fn price_for(cost_micros: f64, markup_percent: u32) -> f64 {
562 cost_micros * f64::from(100 + markup_percent) / 100.0
563 }
564}
565
566/// A cost that moved.
567#[derive(Clone, Debug, Serialize, Deserialize)]
568#[serde(rename_all = "camelCase")]
569pub struct PriceChange {
570 pub meter: String,
571 pub old_cost_micros: f64,
572 pub new_cost_micros: f64,
573 pub markup_percent: u32,
574 /// The markup before, when the change was to the markup rather than
575 /// to the cost. Absent when the markup stayed `markup_percent`.
576 #[serde(default, skip_serializing_if = "Option::is_none")]
577 pub old_markup_percent: Option<u32>,
578 pub reason: String,
579 pub created_at: String,
580}
581
582/// `prices`: every metered price and the recent changes. Public. Returns
583/// `PriceBook`.
584#[derive(Clone, Debug, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586pub struct PriceBook {
587 pub prices: Vec<Price>,
588 pub changes: Vec<PriceChange>,
589 /// The margin on model usage, which is charged at what AI Gateway
590 /// priced each request at.
591 pub model_margin_percent: u32,
592 /// Every plan, as it is sold now.
593 #[serde(default)]
594 pub plans: Vec<Plan>,
595 /// What is free, and what pays for it.
596 #[serde(default)]
597 pub free: Option<FreeTier>,
598}
599
600/// What g1t gives without a plan, each with what pays for it: a capped
601/// budget, never an open-ended allowance.
602#[derive(Clone, Debug, Default, Serialize, Deserialize)]
603#[serde(rename_all = "camelCase")]
604pub struct FreeTier {
605 /// Each new workspace's trial credit, once.
606 pub trial_workspace_micros: i64,
607 /// Trial grants each month, in all; new trials wait when it is spent.
608 pub trial_monthly_pool_micros: i64,
609 /// g1t's open-source pool each month, and any one repository's share.
610 pub oss_pool_micros: i64,
611 pub oss_repo_micros: i64,
612 /// Private repository storage before it is charged.
613 pub free_private_storage_bytes: i64,
614 /// Days of audit log without Team.
615 pub audit_retention_days: u32,
616 /// The smallest amount a card is charged; less carries over.
617 pub min_charge_micros: i64,
618}
619
620/// Who pays: a billing account. Every workspace has one; by default its
621/// own. An enterprise account pays for several workspaces at once, as
622/// GitHub Enterprise does: one bill, one limit, one set of terms.
623#[derive(Clone, Debug, Serialize, Deserialize)]
624#[serde(rename_all = "camelCase")]
625pub struct BillingAccount {
626 /// `ws_<slug>` for a workspace's own account; `ent_…` for an enterprise.
627 pub id: String,
628 pub kind: AccountKind,
629 pub name: String,
630 pub terms: Terms,
631 /// The workspaces it pays for.
632 pub workspaces: Vec<String>,
633 /// Where an enterprise's invoices go.
634 #[serde(default)]
635 pub billing_email: Option<String>,
636 /// An enterprise's invoices, newest first. Empty for a workspace's own.
637 #[serde(default)]
638 pub invoices: Vec<EnterpriseInvoice>,
639 pub created_at: String,
640 /// What g1t staff set for the account beyond its terms.
641 #[serde(default)]
642 pub allowances: Allowances,
643}
644
645/// Set per account by g1t staff in sudo, on top of its terms.
646#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
647#[serde(rename_all = "camelCase")]
648pub struct Allowances {
649 /// The Team plan without paying for it, such as for a partner.
650 /// Comped accounts have it anyway.
651 #[serde(default)]
652 pub team: bool,
653 /// Each of the account's public repositories' monthly cap on g1t's
654 /// open-source pool, in place of `OSS_REPO_MICROS`. None: the default.
655 #[serde(default)]
656 pub oss_repo_micros: Option<i64>,
657 /// The trial credit each of its workspaces gets, in place of
658 /// `TRIAL_WORKSPACE_MICROS`, outside the monthly pool. None: the default.
659 #[serde(default)]
660 pub trial_micros: Option<i64>,
661}
662
663/// `admin_set_allowances`: the Team plan on or off without charge, and the
664/// account's share of g1t's pools. Recorded with who and why. Returns
665/// `Outcome<BillingAccount>`.
666#[derive(Debug, Serialize, Deserialize)]
667pub struct AdminSetAllowancesArgs {
668 pub id: String,
669 pub allowances: Allowances,
670 pub note: String,
671 pub by: String,
672}
673
674/// `entitlements`: what a workspace's plans give it now, for the services
675/// and pages that apply them (the audit log's retention, private storage,
676/// the Team credit). Returns `Entitlements`.
677#[derive(Debug, Serialize, Deserialize)]
678pub struct EntitlementsArgs {
679 pub workspace: String,
680}
681
682#[derive(Clone, Debug, Serialize, Deserialize)]
683#[serde(rename_all = "camelCase")]
684pub struct Entitlements {
685 pub workspace: String,
686 /// Whether the Team plan is on: paid for, comped, or given by g1t.
687 pub team: bool,
688 /// How far back the audit log can be read and exported.
689 pub audit_retention_days: u32,
690 /// Private repository storage included before it is charged.
691 pub free_private_storage_bytes: i64,
692 /// The last daily measure of the workspace's private repositories.
693 pub private_storage_bytes: i64,
694 /// The Team credit each month, and what of it is used this month.
695 pub team_credit_micros: i64,
696 pub team_credit_used_micros: i64,
697 /// What g1t's open-source pool paid for the workspace this month.
698 pub oss_paid_micros: i64,
699 /// Build time the Deployments plan includes each month, and used.
700 pub build_seconds_included: u32,
701 pub build_seconds_used: u32,
702 /// The smallest amount a card is charged; less carries over.
703 pub min_charge_micros: i64,
704}
705
706#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
707#[serde(rename_all = "snake_case")]
708pub enum AccountKind {
709 Workspace,
710 Enterprise,
711}
712
713/// How an account is charged. Standard unless g1t set otherwise in sudo.
714#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
715#[serde(rename_all = "camelCase")]
716pub struct Terms {
717 pub kind: TermsKind,
718 /// Off every usage charge, in percent. Custom terms only.
719 #[serde(default)]
720 pub discount_percent: u32,
721 /// A ceiling on unpaid usage that replaces the one trust would give.
722 #[serde(default)]
723 pub ceiling_micros: Option<i64>,
724 /// Why, for whoever looks next.
725 #[serde(default)]
726 pub note: String,
727 /// When the terms end and the account goes back to standard.
728 #[serde(default)]
729 pub until: Option<String>,
730 #[serde(default)]
731 pub set_by: Option<String>,
732 #[serde(default)]
733 pub set_at: Option<String>,
734}
735
736impl Terms {
737 pub fn standard() -> Self {
738 Terms {
739 kind: TermsKind::Standard,
740 discount_percent: 0,
741 ceiling_micros: None,
742 note: String::new(),
743 until: None,
744 set_by: None,
745 set_at: None,
746 }
747 }
748
749 /// What a charge becomes under these terms.
750 pub fn apply(&self, charge_micros: i64) -> i64 {
751 match self.kind {
752 TermsKind::Comped => 0,
753 TermsKind::Custom => charge_micros * i64::from(100 - self.discount_percent.min(100)) / 100,
754 TermsKind::Standard => charge_micros,
755 }
756 }
757}
758
759#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
760#[serde(rename_all = "snake_case")]
761pub enum TermsKind {
762 /// Prices as published, limits by trust.
763 Standard,
764 /// Nothing charged; usage still recorded with its cost. Paid features
765 /// are on without a plan. For g1t's own workspaces, partners, and the
766 /// like.
767 Comped,
768 /// A discount, a ceiling, or both.
769 Custom,
770}
771
772/// `stripe_webhook`: an event from Stripe, as the API received it: the raw
773/// body and its `Stripe-Signature` header. Billing checks the signature
774/// against the secret of the endpoint it registered, and handles each
775/// event once. Returns `Outcome<bool>`: false for one already handled.
776#[derive(Debug, Serialize, Deserialize)]
777pub struct StripeWebhookArgs {
778 pub payload: String,
779 pub signature: String,
780}
781
782/// `admin_stripe`: where billing stands with Stripe. Staff only. Returns
783/// `StripeStatus`. With `setup: true`, registers (or replaces) the webhook
784/// endpoint for the current mode first.
785#[derive(Debug, Default, Serialize, Deserialize)]
786pub struct AdminStripeArgs {
787 #[serde(default)]
788 pub setup: bool,
789 #[serde(default)]
790 pub by: Option<String>,
791}
792
793#[derive(Clone, Debug, Serialize, Deserialize)]
794#[serde(rename_all = "camelCase")]
795pub struct StripeStatus {
796 /// `test` or `live`, from the key; `off` without one.
797 pub mode: String,
798 pub webhook: Option<StripeWebhook>,
799 /// The latest events handled, newest first.
800 pub recent_events: Vec<StripeEventSummary>,
801 /// What went wrong setting up, if it did.
802 pub error: Option<String>,
803}
804
805#[derive(Clone, Debug, Serialize, Deserialize)]
806#[serde(rename_all = "camelCase")]
807pub struct StripeWebhook {
808 pub url: String,
809 pub endpoint_id: String,
810 pub events: Vec<String>,
811 pub created_by: String,
812 pub created_at: String,
813}
814
815#[derive(Clone, Debug, Serialize, Deserialize)]
816#[serde(rename_all = "camelCase")]
817pub struct StripeEventSummary {
818 pub id: String,
819 pub kind: String,
820 pub outcome: String,
821 pub received_at: String,
822}
823
824/// `admin_enterprise_billing`: where an enterprise's invoices go. Creates
825/// or updates its Stripe customer. Returns `Outcome<BillingAccount>`.
826#[derive(Debug, Serialize, Deserialize)]
827pub struct AdminEnterpriseBillingArgs {
828 pub id: String,
829 pub email: String,
830 pub by: String,
831}
832
833/// `admin_invoice_enterprise`: sends an enterprise its invoice now, for
834/// what its workspaces owe, rather than waiting for the month to close.
835/// Returns `Outcome<EnterpriseInvoice>`.
836#[derive(Debug, Serialize, Deserialize)]
837pub struct AdminInvoiceEnterpriseArgs {
838 pub id: String,
839 pub by: String,
840}
841
842/// An enterprise's invoice: one line per workspace, paid on Stripe.
843#[derive(Clone, Debug, Serialize, Deserialize)]
844#[serde(rename_all = "camelCase")]
845pub struct EnterpriseInvoice {
846 pub invoice_id: String,
847 /// Stripe's page for it, where it is paid.
848 pub hosted_url: Option<String>,
849 pub amount_micros: i64,
850 /// `open`, `paid`, `overdue` or `void`.
851 pub status: String,
852 pub period: String,
853 pub lines: Vec<InvoiceLine>,
854 pub created_at: String,
855}
856
857#[derive(Clone, Debug, Serialize, Deserialize)]
858#[serde(rename_all = "camelCase")]
859pub struct InvoiceLine {
860 pub workspace: String,
861 pub amount_micros: i64,
862}
863
864/// A workspace's invoice from g1t: one per month, and one each time it is
865/// charged near its limit. Itemised, charged to the card on file, and kept
866/// in Stripe's billing page with its PDF.
867#[derive(Clone, Debug, Serialize, Deserialize)]
868#[serde(rename_all = "camelCase")]
869pub struct WorkspaceInvoice {
870 pub invoice_id: String,
871 pub workspace: String,
872 /// `month` (2026-10) or `threshold`.
873 pub reason: String,
874 pub period: String,
875 pub amount_micros: i64,
876 /// `paid`, `open`, `failed` or `void`.
877 pub status: String,
878 pub hosted_url: Option<String>,
879 pub pdf_url: Option<String>,
880 pub lines: Vec<InvoiceItem>,
881 pub created_at: String,
882}
883
884#[derive(Clone, Debug, Serialize, Deserialize)]
885#[serde(rename_all = "camelCase")]
886pub struct InvoiceItem {
887 pub description: String,
888 pub amount_micros: i64,
889}
890
891/// `invoices`: a workspace's invoices from g1t, newest first. Members
892/// only. Returns `Outcome<Vec<WorkspaceInvoice>>`.
893#[derive(Debug, Serialize, Deserialize)]
894pub struct InvoicesArgs {
895 pub workspace: String,
896 pub viewer: Viewer,
897}
898
899/// `admin_workspace_invoices`: the same, for staff. Returns
900/// `Vec<WorkspaceInvoice>`.
901#[derive(Debug, Serialize, Deserialize)]
902pub struct AdminWorkspaceInvoicesArgs {
903 pub workspace: String,
904}
905
906/// `statement`: a month of a workspace's ledger, grouped by day (or by
907/// project) with a line per kind of charge. Members only. Returns
908/// `Outcome<Statement>`.
909#[derive(Debug, Serialize, Deserialize)]
910pub struct StatementArgs {
911 pub workspace: String,
912 pub viewer: Viewer,
913 /// YYYY-MM; this month when absent.
914 #[serde(default)]
915 pub month: Option<String>,
916 /// `day` (the default) or `project`.
917 #[serde(default)]
918 pub group: Option<String>,
919}
920
921#[derive(Clone, Debug, Serialize, Deserialize)]
922#[serde(rename_all = "camelCase")]
923pub struct Statement {
924 pub month: String,
925 /// Months with any entries, newest first.
926 pub months: Vec<String>,
927 pub groups: Vec<StatementGroup>,
928 pub totals: StatementTotals,
929}
930
931#[derive(Clone, Debug, Serialize, Deserialize)]
932#[serde(rename_all = "camelCase")]
933pub struct StatementGroup {
934 /// The day (YYYY-MM-DD) or the project (`owner/name`, or empty).
935 pub key: String,
936 pub label: String,
937 pub lines: Vec<StatementLine>,
938 /// What the group's charges come to.
939 pub charged_micros: i64,
940}
941
942#[derive(Clone, Debug, Serialize, Deserialize)]
943#[serde(rename_all = "camelCase")]
944pub struct StatementLine {
945 /// Agent runs, Sandbox time, Deployments, Payments, Credits from g1t,
946 /// Refunds, and, for older entries, Runs on your own model provider.
947 pub kind: String,
948 pub count: u32,
949 /// Charges positive; money in (payments, credits) negative.
950 pub charged_micros: i64,
951 pub cost_micros: i64,
952 /// Of the usage on the line, what was paid for before it was charged:
953 /// by the Team plan's credit, the trial credit or g1t's open-source
954 /// pool. Not in `charged_micros`.
955 #[serde(default)]
956 pub covered_micros: i64,
957}
958
959#[derive(Clone, Debug, Serialize, Deserialize)]
960#[serde(rename_all = "camelCase")]
961pub struct StatementTotals {
962 pub charged_micros: i64,
963 pub paid_micros: i64,
964 pub cost_micros: i64,
965 pub entries: u32,
966 /// What paid for usage before it was charged, one line per source,
967 /// such as "Paid by g1t's open-source pool".
968 #[serde(default)]
969 pub covered: Vec<Covered>,
970 /// Owed when the month closed but under the minimum charge, so it
971 /// carries over to the next invoice. Zero when nothing carried.
972 #[serde(default)]
973 pub carried_micros: i64,
974}
975
976/// One source that paid for usage before it was charged.
977#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
978#[serde(rename_all = "camelCase")]
979pub struct Covered {
980 /// `team_credit`, `trial` or `oss_pool`.
981 pub source: String,
982 /// "Paid by your Team plan's credit", "Paid by your trial credit",
983 /// "Paid by g1t's open-source pool".
984 pub label: String,
985 pub micros: i64,
986}
987
988/// `statement_entries`: one statement line's entries, newest first, 50 at
989/// a time (`before` = the last id seen). Returns `Outcome<Vec<LedgerEntry>>`.
990#[derive(Debug, Serialize, Deserialize)]
991pub struct StatementEntriesArgs {
992 pub workspace: String,
993 pub viewer: Viewer,
994 pub month: String,
995 pub kind: String,
996 #[serde(default)]
997 pub day: Option<String>,
998 #[serde(default)]
999 pub project: Option<String>,
1000 #[serde(default)]
1001 pub before: Option<String>,
1002}
1003
1004// --- Sales (sudo.g1t.sh) ------------------------------------------------------
1005//
1006// What staff need to know to reach out: who is growing, who is close to
1007// their limit, who was declined, who has become a steady customer. And what
1008// was done about it: a stage, an owner on g1t's side, a next step, notes.
1009
1010/// Why a workspace is worth a look.
1011#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1012#[serde(rename_all = "snake_case")]
1013pub enum SignalKind {
1014 /// At its limit, or its own spend limit: work is stopped.
1015 AtLimit,
1016 /// Past 80% of what is available to it: about to need more.
1017 NearCeiling,
1018 /// Its card was declined or a payment disputed.
1019 Declined,
1020 /// This month is well ahead of last month.
1021 Growing,
1022 /// Became Established: the ceiling now follows its spend.
1023 Established,
1024 /// Paid g1t for the first time.
1025 FirstPayment,
1026 /// Spending enough that custom terms or an enterprise may suit it.
1027 HighSpend,
1028}
1029
1030#[derive(Clone, Debug, Serialize, Deserialize)]
1031#[serde(rename_all = "camelCase")]
1032pub struct Signal {
1033 pub workspace: String,
1034 pub kind: SignalKind,
1035 /// One sentence, with the figures.
1036 pub detail: String,
1037 /// The figure that matters, such as this month's spend.
1038 pub value_micros: i64,
1039 /// Its sales stage, if staff gave it one.
1040 pub stage: Option<String>,
1041 pub owner: Option<String>,
1042 #[serde(default)]
1043 pub next_step: Option<String>,
1044 /// When the next step is due, `YYYY-MM-DD`.
1045 #[serde(default)]
1046 pub next_at: Option<String>,
1047}
1048
1049/// `admin_invoices`: every invoice g1t has sent, workspaces' and
1050/// enterprises', newest first. Returns `Vec<InvoiceSummary>`.
1051#[derive(Debug, Default, Serialize, Deserialize)]
1052pub struct AdminInvoicesArgs {
1053 /// `paid`, `open`, `failed`, `overdue` or `void`.
1054 #[serde(default)]
1055 pub status: Option<String>,
1056 /// YYYY-MM, by when it was sent.
1057 #[serde(default)]
1058 pub month: Option<String>,
1059}
1060
1061#[derive(Clone, Debug, Serialize, Deserialize)]
1062#[serde(rename_all = "camelCase")]
1063pub struct InvoiceSummary {
1064 pub invoice_id: String,
1065 /// `workspace` or `enterprise`.
1066 pub kind: String,
1067 /// The workspace's slug, or the enterprise's account id.
1068 pub account: String,
1069 /// What to call it: the workspace, or the enterprise's name.
1070 pub name: String,
1071 pub reason: String,
1072 pub period: String,
1073 pub amount_micros: i64,
1074 pub status: String,
1075 pub hosted_url: Option<String>,
1076 pub created_at: String,
1077 pub paid_at: Option<String>,
1078}
1079
1080/// `admin_audit`: every change made in sudo, and by Stripe, newest first.
1081/// Returns `Vec<AdminAction>`.
1082#[derive(Debug, Default, Serialize, Deserialize)]
1083pub struct AdminAuditArgs {
1084 #[serde(default)]
1085 pub by: Option<String>,
1086 #[serde(default)]
1087 pub action: Option<String>,
1088 /// Only those before this time, for paging.
1089 #[serde(default)]
1090 pub before: Option<String>,
1091}
1092
1093/// `admin_signals`: every workspace worth reaching out to, most urgent
1094/// first. Returns `Vec<Signal>`.
1095#[derive(Debug, Default, Serialize, Deserialize)]
1096pub struct AdminSignalsArgs {}
1097
1098/// What staff are doing about a workspace.
1099#[derive(Clone, Debug, Serialize, Deserialize)]
1100#[serde(rename_all = "camelCase")]
1101pub struct SalesRecord {
1102 pub workspace: String,
1103 /// `none`, `lead`, `contacted`, `negotiating`, `won`, `lost` or `churn_risk`.
1104 pub stage: String,
1105 /// The staff member looking after it.
1106 pub owner: Option<String>,
1107 pub next_step: Option<String>,
1108 /// RFC 3339 date.
1109 pub next_at: Option<String>,
1110 pub notes: Vec<SalesNote>,
1111 pub updated_at: Option<String>,
1112}
1113
1114#[derive(Clone, Debug, Serialize, Deserialize)]
1115#[serde(rename_all = "camelCase")]
1116pub struct SalesNote {
1117 pub id: String,
1118 pub text: String,
1119 pub by: String,
1120 pub created_at: String,
1121}
1122
1123/// `admin_sales`: a workspace's sales record. Returns `SalesRecord`.
1124#[derive(Debug, Serialize, Deserialize)]
1125pub struct AdminSalesArgs {
1126 pub workspace: String,
1127}
1128
1129/// `admin_set_sales`: its stage, owner and next step. Returns `Outcome<SalesRecord>`.
1130#[derive(Debug, Serialize, Deserialize)]
1131pub struct AdminSetSalesArgs {
1132 pub workspace: String,
1133 pub stage: String,
1134 #[serde(default)]
1135 pub owner: Option<String>,
1136 #[serde(default)]
1137 pub next_step: Option<String>,
1138 #[serde(default)]
1139 pub next_at: Option<String>,
1140 pub by: String,
1141}
1142
1143/// `admin_add_note`. Returns `Outcome<SalesRecord>`.
1144#[derive(Debug, Serialize, Deserialize)]
1145pub struct AdminAddNoteArgs {
1146 pub workspace: String,
1147 pub text: String,
1148 pub by: String,
1149}
1150
1151/// `admin_overview`: the business at a glance. Returns `Overview`.
1152#[derive(Debug, Default, Serialize, Deserialize)]
1153pub struct AdminOverviewArgs {}
1154
1155#[derive(Clone, Debug, Serialize, Deserialize)]
1156#[serde(rename_all = "camelCase")]
1157pub struct Overview {
1158 /// YYYY-MM.
1159 pub month: String,
1160 /// The last six months, oldest first, all workspaces together.
1161 pub months: Vec<MonthFigures>,
1162 /// This month by kind of usage: models, sandbox, deployments, plans.
1163 pub by_kind: Vec<KindFigures>,
1164 pub paying_workspaces: u32,
1165 pub stopped: u32,
1166 pub near_ceiling: u32,
1167 pub declined: u32,
1168 /// Sent and not yet paid, workspaces and enterprises.
1169 pub open_invoices_micros: i64,
1170 /// Follow-ups due today or earlier.
1171 pub follow_ups_due: u32,
1172 /// The capped budgets g1t pays from, this month.
1173 #[serde(default)]
1174 pub pools: Option<Pools>,
1175}
1176
1177/// g1t's capped budgets for free usage, this calendar month (UTC).
1178#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1179#[serde(rename_all = "camelCase")]
1180pub struct Pools {
1181 /// YYYY-MM.
1182 pub month: String,
1183 /// Trial grants made this month, against the month's pool.
1184 pub trial_granted_micros: i64,
1185 pub trial_pool_micros: i64,
1186 pub trial_grants: u32,
1187 /// What the open-source pool paid this month, against its cap.
1188 pub oss_used_micros: i64,
1189 pub oss_pool_micros: i64,
1190 /// Each public repository's monthly cap on the pool.
1191 pub oss_repo_micros: i64,
1192}
1193
1194#[derive(Clone, Debug, Serialize, Deserialize)]
1195#[serde(rename_all = "camelCase")]
1196pub struct KindFigures {
1197 pub kind: String,
1198 pub charged_micros: i64,
1199 pub cost_micros: i64,
1200}
1201
1202// --- Staff (sudo.g1t.sh) ------------------------------------------------------
1203//
1204// Called only by the sudo app, which only g1t staff can reach (behind
1205// Cloudflare Access). Each change names who made it, and is kept in the
1206// audit log.
1207
1208/// `admin_accounts`: every billing account, with where each stands this
1209/// month. Returns `Vec<AccountSummary>`.
1210#[derive(Debug, Default, Serialize, Deserialize)]
1211pub struct AdminAccountsArgs {
1212 #[serde(default)]
1213 pub query: Option<String>,
1214 /// Exactly these workspaces' accounts, such as one page of sudo's
1215 /// list; every account with activity when absent.
1216 #[serde(default)]
1217 pub workspaces: Option<Vec<String>>,
1218}
1219
1220#[derive(Clone, Debug, Serialize, Deserialize)]
1221#[serde(rename_all = "camelCase")]
1222pub struct AccountSummary {
1223 pub account: BillingAccount,
1224 pub limit: Limit,
1225 /// Charged this month, after terms.
1226 pub charged_micros: i64,
1227 /// What this month's usage cost g1t.
1228 pub cost_micros: i64,
1229 /// Paid, ever.
1230 pub paid_micros: i64,
1231 /// The same figures for each of the account's workspaces that has
1232 /// any, so staff can see what one member of an enterprise used.
1233 #[serde(default)]
1234 pub by_workspace: Vec<WorkspaceFigures>,
1235 /// The last six months, oldest first, for trends.
1236 #[serde(default)]
1237 pub months: Vec<MonthFigures>,
1238}
1239
1240/// One month of an account's billing.
1241#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1242#[serde(rename_all = "camelCase")]
1243pub struct MonthFigures {
1244 /// YYYY-MM.
1245 pub month: String,
1246 pub charged_micros: i64,
1247 pub cost_micros: i64,
1248 pub paid_micros: i64,
1249}
1250
1251/// One workspace's share of an [`AccountSummary`].
1252#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1253#[serde(rename_all = "camelCase")]
1254pub struct WorkspaceFigures {
1255 pub workspace: String,
1256 pub charged_micros: i64,
1257 pub cost_micros: i64,
1258 pub paid_micros: i64,
1259}
1260
1261/// `admin_account`: one account in full. Returns `Outcome<AccountDetail>`.
1262#[derive(Debug, Serialize, Deserialize)]
1263pub struct AdminAccountArgs {
1264 /// An account id, or a workspace slug.
1265 pub id: String,
1266}
1267
1268#[derive(Clone, Debug, Serialize, Deserialize)]
1269#[serde(rename_all = "camelCase")]
1270pub struct AccountDetail {
1271 pub summary: AccountSummary,
1272 /// Each workspace's limit, for an enterprise.
1273 pub workspaces: Vec<Limit>,
1274 pub ledger: Vec<LedgerEntry>,
1275 pub audit: Vec<AdminAction>,
1276}
1277
1278/// `admin_set_terms`. Returns `Outcome<BillingAccount>`.
1279#[derive(Debug, Serialize, Deserialize)]
1280pub struct AdminSetTermsArgs {
1281 pub id: String,
1282 pub terms: Terms,
1283 pub by: String,
1284}
1285
1286/// `admin_create_enterprise`. Returns `Outcome<BillingAccount>`.
1287#[derive(Debug, Serialize, Deserialize)]
1288pub struct AdminCreateEnterpriseArgs {
1289 pub name: String,
1290 pub workspaces: Vec<String>,
1291 pub by: String,
1292}
1293
1294/// `admin_attach`: moves a workspace onto an enterprise account, or back
1295/// onto its own with `account: None`. Returns `Outcome<BillingAccount>`.
1296#[derive(Debug, Serialize, Deserialize)]
1297pub struct AdminAttachArgs {
1298 pub workspace: String,
1299 pub account: Option<String>,
1300 pub by: String,
1301}
1302
1303/// `admin_credit`: money g1t gives a workspace, such as a refund or a
1304/// goodwill credit. Returns `Outcome<LedgerEntry>`.
1305#[derive(Debug, Serialize, Deserialize)]
1306pub struct AdminCreditArgs {
1307 pub workspace: String,
1308 pub amount_micros: i64,
1309 pub note: String,
1310 pub by: String,
1311}
1312
1313/// One change made in sudo.
1314#[derive(Clone, Debug, Serialize, Deserialize)]
1315#[serde(rename_all = "camelCase")]
1316pub struct AdminAction {
1317 pub id: String,
1318 pub account: String,
1319 pub action: String,
1320 pub detail: String,
1321 pub by: String,
1322 pub created_at: String,
1323}
1324
1325/// What a feature's plan costs and includes.
1326#[derive(Clone, Debug, Serialize, Deserialize)]
1327#[serde(rename_all = "camelCase")]
1328pub struct Plan {
1329 pub feature: Feature,
1330 pub title: String,
1331 /// Charged every month while the plan is on, in cents.
1332 pub monthly_cents: u32,
1333 /// What the monthly price includes, one line each, for people to read.
1334 pub includes: Vec<String>,
1335 /// How usage past the allowance is charged, for people to read.
1336 pub overage: String,
1337}
1338
1339#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1340#[serde(rename_all = "snake_case")]
1341pub enum SubscriptionStatus {
1342 /// Paid up; the feature works.
1343 Active,
1344 /// Paid up to the end of the period, and ends then.
1345 Canceling,
1346 /// The last payment failed; the feature is off until it is paid.
1347 PastDue,
1348 /// Ended.
1349 Canceled,
1350}
1351
1352impl SubscriptionStatus {
1353 /// Whether the feature works in this state.
1354 pub fn on(self) -> bool {
1355 matches!(self, SubscriptionStatus::Active | SubscriptionStatus::Canceling)
1356 }
1357}
1358
1359/// A workspace's plan for one feature.
1360#[derive(Clone, Debug, Serialize, Deserialize)]
1361#[serde(rename_all = "camelCase")]
1362pub struct Subscription {
1363 pub feature: Feature,
1364 pub status: SubscriptionStatus,
1365 /// RFC 3339: when the period paid for ends, and the plan renews or
1366 /// ends.
1367 pub period_end: Option<String>,
1368 /// Username of whoever turned it on.
1369 pub started_by: String,
1370 /// RFC 3339.
1371 pub started_at: String,
1372}
1373
1374/// A feature as a workspace sees it: what it costs, and its plan if it has
1375/// one.
1376#[derive(Clone, Debug, Serialize, Deserialize)]
1377#[serde(rename_all = "camelCase")]
1378pub struct FeatureState {
1379 pub plan: Plan,
1380 pub subscription: Option<Subscription>,
1381 /// Whether the feature works for the workspace now.
1382 pub on: bool,
1383 /// On without a plan: comped terms, or given by g1t. Nothing to pay
1384 /// and nothing to turn off.
1385 #[serde(default)]
1386 pub included: bool,
1387}
1388
1389/// `features`: every paid feature and the workspace's plan for each.
1390/// Members only. Returns `Outcome<Vec<FeatureState>>`.
1391#[derive(Debug, Serialize, Deserialize)]
1392pub struct FeaturesArgs {
1393 pub workspace: String,
1394 pub viewer: Viewer,
1395}
1396
1397/// `subscribe`: starts the card page for a feature's monthly plan. Owners
1398/// only. Returns `Outcome<Checkout>`; the page's id comes back to
1399/// `return_url` as `session`, for `confirm_subscription`.
1400#[derive(Debug, Serialize, Deserialize)]
1401#[serde(rename_all = "camelCase")]
1402pub struct SubscribeArgs {
1403 pub actor: User,
1404 pub workspace: String,
1405 pub feature: Feature,
1406 pub return_url: String,
1407}
1408
1409/// `confirm_subscription`: turns the feature on once the processor says
1410/// the plan was paid for. Safe to call any number of times. Returns
1411/// `Outcome<FeatureState>`.
1412#[derive(Debug, Serialize, Deserialize)]
1413pub struct ConfirmSubscriptionArgs {
1414 pub workspace: String,
1415 pub viewer: Viewer,
1416 pub session: String,
1417}
1418
1419/// `cancel_subscription` (`resume` false) ends a plan at the end of the
1420/// period paid for; with `resume` true, takes that back. Owners only.
1421/// Returns `Outcome<FeatureState>`.
1422#[derive(Debug, Serialize, Deserialize)]
1423pub struct CancelSubscriptionArgs {
1424 pub actor: User,
1425 pub workspace: String,
1426 pub feature: Feature,
1427 #[serde(default)]
1428 pub resume: bool,
1429}
1430
1431/// `has_feature`: whether a feature works for a workspace now, asked by the
1432/// service that provides it before doing paid work. Returns
1433/// `Outcome<bool>`: a failure, with the reason to show, when it does not.
1434/// True everywhere when no card processor is configured.
1435#[derive(Debug, Serialize, Deserialize)]
1436pub struct HasFeatureArgs {
1437 pub workspace: String,
1438 pub feature: Feature,
1439}
1440
1441/// `charge_feature`: usage of a feature past its plan's allowance, charged
1442/// from the workspace's credit at cost plus the margin, whatever
1443/// `FREE_WHILE_BUILDING` says. Called by the service that provides it.
1444/// Charged once per `reference`. Returns `Outcome<bool>`: false if that
1445/// reference was charged before.
1446#[derive(Debug, Serialize, Deserialize)]
1447#[serde(rename_all = "camelCase")]
1448pub struct ChargeFeatureArgs {
1449 pub workspace: String,
1450 pub feature: Feature,
1451 /// What it cost g1t, in millionths of a dollar, before the margin.
1452 pub cost_micros: i64,
1453 pub description: String,
1454 /// `namespace/name`, when the usage was one repository's.
1455 pub repo: Option<String>,
1456 /// Unique to this charge, e.g. `deployments/acme/2026-10`.
1457 pub reference: String,
1458 /// For a build: how long it ran. The plan's included build time this
1459 /// month pays for what it can, and only the rest of `cost_micros` is
1460 /// charged.
1461 #[serde(default)]
1462 pub build_seconds: Option<u32>,
1463}
1464
1465#[cfg(test)]
1466mod tests {
1467 use super::*;
1468
1469 #[test]
1470 fn an_account_carries_no_run_fee() {
1471 let account = Account {
1472 workspace: "acme".into(),
1473 balance_micros: 0,
1474 status: Status { enabled: true, live: false, free: false },
1475 margin_percent: 20,
1476 card: None,
1477 };
1478 let json = serde_json::to_value(account).unwrap();
1479 let mut keys: Vec<&str> = json.as_object().unwrap().keys().map(String::as_str).collect();
1480 keys.sort_unstable();
1481 assert_eq!(keys, ["balanceMicros", "card", "marginPercent", "status", "workspace"]);
1482 }
1483
1484 #[test]
1485 fn a_price_change_says_when_the_markup_moved() {
1486 let change = PriceChange {
1487 meter: "sandbox_second".into(),
1488 old_cost_micros: 21.0,
1489 new_cost_micros: 21.0,
1490 markup_percent: 20,
1491 old_markup_percent: Some(138),
1492 reason: "Sandbox time is now charged at cost plus 20% from the first second".into(),
1493 created_at: "2026-10-05T00:00:00Z".into(),
1494 };
1495 assert_eq!(serde_json::to_value(&change).unwrap()["oldMarkupPercent"], 138);
1496 let cost_only = PriceChange { old_markup_percent: None, ..change };
1497 assert!(serde_json::to_value(&cost_only).unwrap().get("oldMarkupPercent").is_none());
1498 }
1499
1500 #[test]
1501 fn features_are_named_as_the_site_sends_them() {
1502 assert_eq!(
1503 serde_json::to_value(Feature::Deployments).unwrap(),
1504 serde_json::json!("deployments")
1505 );
1506 assert_eq!(Feature::parse("deployments"), Some(Feature::Deployments));
1507 assert_eq!(serde_json::to_value(Feature::Team).unwrap(), serde_json::json!("team"));
1508 assert_eq!(Feature::parse("team"), Some(Feature::Team));
1509 assert!(SubscriptionStatus::Canceling.on());
1510 assert!(!SubscriptionStatus::PastDue.on());
1511 }
1512
1513 #[test]
1514 fn who_pays_is_read_as_the_runner_sends_it() {
1515 let run: StartRunArgs = serde_json::from_value(serde_json::json!({
1516 "workspace": "acme",
1517 "repo": { "namespace": "acme", "name": "web" },
1518 "number": 7,
1519 "task": "implement",
1520 "model": "Claude Sonnet 5.5",
1521 "billedTo": "workspace",
1522 }))
1523 .unwrap();
1524 assert_eq!(run.billed_to, "workspace");
1525 }
1526}