Skip to content

g1t/apps/api/src/openapi.rs

1,100 lines46,805 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

API and MCP server in Rust; a public index at the API root1//! The OpenAPI document, generated from the same list the routes are.
Merge branch 'worktree-agent-ab2e39e11a6493412'2//!
3//! The docs site builds its API reference from a copy of this document,
4//! `apps/docs/src/data/openapi.json`. A test keeps the copy current: run
5//! `G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi` to rewrite it.
API and MCP server in Rust; a public index at the API root6
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step7use g1t_contracts::scopes::scope_for;
API and MCP server in Rust; a public index at the API root8use serde_json::{Map, Value, json};
9
10use crate::operations::Op;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar11use crate::security::SecurityOp;
API and MCP server in Rust; a public index at the API root12use crate::rest::{ROUTES, Route};
13
Merge branch 'worktree-agent-ab2e39e11a6493412'14/// The sections of the API reference: a name, what it covers, and its
15/// operations in the order a reader meets them.
16const SECTIONS: &[(&str, &str, &[Op])] = &[
17 (
18 "Accounts",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look19 "Signing in from a tool, who a token acts as, and your email addresses.",
20 &[Op::Whoami, Op::ListEmails, Op::AddEmail, Op::RemoveEmail, Op::UpdateEmailSettings],
Merge branch 'worktree-agent-ab2e39e11a6493412'21 ),
22 (
API: notifications over REST and MCP, with notifications scopes23 "Notifications",
24 "Your inbox: a thread for each thing you were told about (an issue, a pull request, a workflow on a branch, a deployment), why you were told, and what you subscribe to and watch. Your own: personal tokens and sessions only.",
25 &[
26 Op::ListNotifications,
27 Op::MarkNotificationsRead,
28 Op::GetNotificationThread,
29 Op::MarkThreadRead,
30 Op::MarkThreadDone,
31 Op::SaveThread,
32 Op::SnoozeThread,
33 Op::GetThreadSubscription,
34 Op::SetThreadSubscription,
35 Op::DeleteThreadSubscription,
36 Op::GetRepoSubscription,
37 Op::SetRepoSubscription,
38 Op::DeleteRepoSubscription,
39 Op::ListWatchedRepos,
40 ],
41 ),
42 (
API: pinned projects over REST and MCP43 "Pinned projects",
44 "The projects you keep at the top of a workspace's sidebar, in your order, up to eight a workspace. Your own: personal tokens and sessions only.",
45 &[Op::ListPinnedProjects, Op::PinProject, Op::UnpinProject, Op::ReorderPinnedProjects],
46 ),
47 (
Merge branch 'worktree-agent-ab2e39e11a6493412'48 "Workspaces",
49 "A workspace owns repositories and is the first part of their address. People and agents work in workspaces.",
Merge branch 'worktree-agent-ad7c6d88d93adc817'50 &[Op::GetWorkspace, Op::CreateWorkspace, Op::UpdateWorkspace, Op::DeleteWorkspace],
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51 ),
52 (
53 "Invites",
54 "While g1t is invite-only, every new account needs an invite. Your invites, and inviting people into a workspace by email.",
55 &[
56 Op::ListInvites,
57 Op::CreateInvite,
58 Op::RevokeInvite,
59 Op::ListWorkspaceInvites,
60 Op::InviteMember,
61 Op::RevokeWorkspaceInvite,
62 ],
Merge branch 'worktree-agent-ab2e39e11a6493412'63 ),
64 (
Usage, Billing settings and prepaid AI credit; fixes from the UX audit65 "Billing",
66 "A workspace's usage, its budget, its AI credit and its invoices. Members read them; owners change the budget and buy credit, as people. g1t's agents never change billing.",
67 &[
68 Op::GetUsage,
69 Op::GetBudget,
70 Op::SetBudget,
71 Op::GetAiCredit,
72 Op::BuyAiCredit,
73 Op::ListInvoices,
74 Op::GetBillingDetails,
75 ],
76 ),
77 (
Merge branch 'worktree-agent-ab2e39e11a6493412'78 "Repositories",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look79 "A repository, how it handles pull requests, and its timeline: renaming, archiving, moving and deleting it.",
Merge branch 'worktree-agent-ab2e39e11a6493412'80 &[
81 Op::ListRepos,
82 Op::CreateRepo,
83 Op::GetRepo,
84 Op::UpdateRepo,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look85 Op::RenameRepo,
86 Op::RenameBranch,
87 Op::SetRepoVisibility,
88 Op::ArchiveRepo,
89 Op::UnarchiveRepo,
90 Op::TransferRepo,
91 Op::DeleteRepo,
92 Op::ListDeletedRepos,
93 Op::RestoreRepo,
94 Op::PurgeRepo,
Merge branch 'worktree-agent-ab2e39e11a6493412'95 Op::GetRepoSettings,
96 Op::UpdateRepoSettings,
Fast pages, required checks on the branch, self-hosted runners, honest incidents97 Op::ListCheckNames,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar98 Op::GetCodeownersErrors,
Merge branch 'worktree-agent-ab2e39e11a6493412'99 Op::ListEvents,
100 ],
101 ),
102 (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look103 "Access",
104 "Who can do what in a repository: repository roles, people given a role on one repository (outside collaborators when they are not members), invitations, and a workspace's base permission.",
105 &[
106 Op::ListCollaborators,
107 Op::AddCollaborator,
108 Op::UpdateCollaborator,
109 Op::RemoveCollaborator,
110 Op::GetCollaboratorPermission,
111 Op::ListRepoInvitations,
112 Op::RevokeRepoInvitation,
113 Op::ListMyRepoInvitations,
114 Op::AcceptRepoInvitation,
115 Op::DeclineRepoInvitation,
116 Op::SetBasePermission,
117 Op::ListOutsideCollaborators,
118 ],
119 ),
120 (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar121 "Teams",
122 "Groups of a workspace's members: given a role on repositories together, mentioned together as @workspace/team, and asked to review together. Any member may create a team; the workspace's owners and the team's maintainers manage it.",
123 &[
124 Op::ListTeams,
125 Op::CreateTeam,
126 Op::GetTeam,
127 Op::UpdateTeam,
128 Op::DeleteTeam,
129 Op::ListTeamMembers,
130 Op::SetTeamMember,
131 Op::RemoveTeamMember,
132 Op::ListChildTeams,
133 Op::ListTeamRepos,
134 Op::SetTeamRepo,
135 Op::RemoveTeamRepo,
136 Op::SetTeamReviewAssignment,
137 Op::ListUserTeams,
138 ],
139 ),
140 (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily141 "Security",
142 "Secrets found in what is pushed and in a repository's history, and dependencies with known vulnerabilities: listing the alerts, and dismissing or reopening them.",
143 &[Op::ListSecurityAlerts, Op::DismissSecurityAlert, Op::ReopenSecurityAlert],
144 ),
145 (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar146 "Secret scanning",
147 "Secrets found in pushes and history, where each one is, pushing past push protection with a reason (and asking for approval when the workspace delegates bypasses), checking with a secret's issuer whether it still works, and custom patterns.",
148 &[
149 Op::Security(SecurityOp::ListSecretAlerts),
150 Op::Security(SecurityOp::GetSecretAlert),
151 Op::Security(SecurityOp::UpdateSecretAlert),
152 Op::Security(SecurityOp::ListSecretLocations),
153 Op::Security(SecurityOp::BypassPushProtection),
154 Op::Security(SecurityOp::CheckSecretValidity),
155 Op::Security(SecurityOp::ListBypassRequests),
156 Op::Security(SecurityOp::ReviewBypassRequest),
157 Op::Security(SecurityOp::ListCustomPatterns),
158 Op::Security(SecurityOp::CreateCustomPattern),
159 Op::Security(SecurityOp::UpdateCustomPattern),
160 Op::Security(SecurityOp::DeleteCustomPattern),
161 Op::Security(SecurityOp::DryRunCustomPattern),
162 ],
163 ),
164 (
165 "Code scanning",
166 "Results of static analysis tools, uploaded as SARIF: alerts on the default branch, the analyses that made them, uploads, and putting g1t on an alert to fix it.",
167 &[
168 Op::Security(SecurityOp::ListCodeAlerts),
169 Op::Security(SecurityOp::GetCodeAlert),
170 Op::Security(SecurityOp::UpdateCodeAlert),
171 Op::Security(SecurityOp::ListAnalyses),
172 Op::Security(SecurityOp::UploadSarif),
173 Op::Security(SecurityOp::GetSarifUpload),
174 Op::Security(SecurityOp::FixAlert),
175 ],
176 ),
177 (
178 "Supply chain",
179 "What a repository depends on: vulnerability alerts, the dependency graph, an SPDX SBOM of it, and comparing two commits' dependencies as dependency review does.",
180 &[
181 Op::Security(SecurityOp::ListVulnerabilityAlerts),
182 Op::Security(SecurityOp::GetVulnerabilityAlert),
183 Op::Security(SecurityOp::UpdateVulnerabilityAlert),
184 Op::Security(SecurityOp::GetDependencyGraph),
185 Op::Security(SecurityOp::GetSbom),
186 Op::Security(SecurityOp::CompareDependencies),
187 ],
188 ),
189 (
190 "Security settings",
191 "When pull request checks fail, dependency review's policy, delegated bypass and validity checks, and a workspace's security overview.",
192 &[
193 Op::Security(SecurityOp::GetSettings),
194 Op::Security(SecurityOp::UpdateSettings),
195 Op::Security(SecurityOp::GetWorkspaceSettings),
196 Op::Security(SecurityOp::UpdateWorkspaceSettings),
197 Op::Security(SecurityOp::GetOverview),
198 ],
199 ),
200 (
Merge branch 'worktree-agent-ab2e39e11a6493412'201 "Issues",
202 "What should change in a repository, with labels and comments. Issues and pull requests share one sequence of numbers.",
203 &[
204 Op::ListIssues,
205 Op::CreateIssue,
206 Op::GetIssue,
207 Op::UpdateIssue,
208 Op::CloseIssue,
209 Op::ReopenIssue,
210 Op::AssignIssue,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step211 Op::Delegate,
Merge branch 'worktree-agent-ab2e39e11a6493412'212 Op::AddComment,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar213 Op::ListIssueLabels,
214 Op::AddIssueLabels,
215 Op::SetIssueLabels,
216 Op::RemoveIssueLabels,
217 ],
218 ),
219 (
220 "Labels and milestones",
221 "A repository's labels, which issues and pull requests carry by name, and its milestones, which gather them under a goal and a due date.",
222 &[
Merge branch 'worktree-agent-ab2e39e11a6493412'223 Op::ListLabels,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar224 Op::CreateLabel,
225 Op::UpdateLabel,
226 Op::DeleteLabel,
227 Op::AddDefaultLabels,
228 Op::ListMilestones,
229 Op::CreateMilestone,
230 Op::GetMilestone,
231 Op::UpdateMilestone,
232 Op::DeleteMilestone,
Merge branch 'worktree-agent-ab2e39e11a6493412'233 ],
234 ),
235 (
236 "Plans",
237 "An outcome turned into the issues that would get there, with the order they must merge in.",
238 &[Op::PlanWork, Op::GetPlan, Op::ApplyPlan],
239 ),
240 (
241 "Pull requests",
242 "A proposed change in its own fork or on a branch. Several can be made for one issue; the one merged resolves it.",
243 &[
244 Op::ListPullRequests,
245 Op::CreatePullRequest,
246 Op::GetPullRequest,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar247 Op::UpdatePullRequest,
Merge branch 'worktree-agent-ab2e39e11a6493412'248 Op::GetPullRequestChanges,
249 Op::MarkPullRequestReady,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar250 Op::RequestReviewers,
251 Op::RemoveRequestedReviewers,
Merge branch 'worktree-agent-ab2e39e11a6493412'252 Op::ReviewPullRequest,
253 Op::MergePullRequest,
254 Op::ClosePullRequest,
255 Op::GetMergeQueue,
256 Op::MessageAgent,
257 Op::AnswerMessage,
258 Op::TakeMessages,
259 ],
260 ),
261 (
262 "Sessions",
263 "The record of how a pull request was made: prompts, reasoning and the tools that ran.",
264 &[Op::ReadSession, Op::RecordSession],
265 ),
266 (
Agents and memory, checks and conflicts, profiles, slug renames, custom domains267 "Memory",
268 "What agents and people learned that the next agent should know, for one project or across a workspace. Members and g1t's agents only; never a secret.",
269 &[Op::Remember, Op::Recall],
270 ),
271 (
Search across all of g1t, Explore, and a command palette272 "Search",
273 "One search across all of g1t: repositories, code, issues, pull requests, people and workspaces. Public content for everyone, and private content in workspaces you belong to.",
274 &[Op::Search],
275 ),
276 (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API277 "Context",
278 "A workspace's context hub: a catalog of what it builds and runs, built from its repositories, deployments and integrations, and one search across the catalog, docs, issues, pull requests and memory.",
279 &[Op::SearchContext, Op::GetEntity],
280 ),
281 (
Merge branch 'worktree-agent-ab2e39e11a6493412'282 "Actions",
283 "GitHub Actions workflows in .g1t/workflows, their runs, and their jobs' logs.",
284 &[
285 Op::ListWorkflows,
286 Op::ListWorkflowRuns,
287 Op::GetWorkflowRun,
288 Op::GetJobLogs,
289 Op::DispatchWorkflow,
290 Op::CancelWorkflowRun,
291 Op::RerunWorkflowRun,
292 Op::UpdateWorkflow,
293 ],
294 ),
295 (
296 "Secrets and variables",
297 "Values that workflows and deployments read, per repository or for a whole workspace, with a row per environment.",
298 &[
299 Op::ListActionsSecrets,
300 Op::SetActionsSecret,
301 Op::DeleteActionsSecret,
302 Op::ListActionsVariables,
303 Op::SetActionsVariable,
304 Op::DeleteActionsVariable,
305 ],
306 ),
307 (
Fast pages, required checks on the branch, self-hosted runners, honest incidents308 "Runners",
309 "Self-hosted runners: your own machines, which run your workflow jobs (and, if you choose, your agents' work) for $0 of g1t compute. They register with a short-lived token and only ever connect out.",
310 &[
311 Op::ListRunners,
312 Op::CreateRunnerRegistrationToken,
313 Op::RemoveRunner,
314 Op::ListRunnerGroups,
315 Op::CreateRunnerGroup,
316 Op::UpdateRunnerGroup,
317 Op::DeleteRunnerGroup,
318 Op::GetRunnerSettings,
319 Op::UpdateRunnerSettings,
320 ],
321 ),
322 (
Merge branch 'worktree-agent-ab2e39e11a6493412'323 "Webhooks",
324 "Signed HTTPS requests sent to your own address as things happen, for a repository or a whole workspace.",
325 &[
326 Op::ListWebhooks,
327 Op::CreateWebhook,
328 Op::UpdateWebhook,
329 Op::DeleteWebhook,
330 Op::PingWebhook,
331 Op::ListWebhookDeliveries,
332 Op::RedeliverWebhook,
333 ],
334 ),
335 (
336 "Integrations",
337 "A workspace's connections to outside systems: model providers, alert sources and issue trackers.",
338 &[
339 Op::ListIntegrations,
340 Op::ConnectIntegration,
341 Op::DisconnectIntegration,
342 Op::TestIntegration,
343 Op::GetModelRoutes,
344 Op::SetModelRoutes,
345 Op::GetContext,
346 Op::ImportIssue,
347 ],
348 ),
349];
350
API and MCP server in Rust; a public index at the API root351/// The section of the API reference an operation is listed under.
352fn tag(op: Op) -> &'static str {
Merge branch 'worktree-agent-ab2e39e11a6493412'353 SECTIONS
API and MCP server in Rust; a public index at the API root354 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'355 .find(|(_, _, ops)| ops.contains(&op))
356 .map_or("Repositories", |(name, _, _)| name)
357}
358
359/// What an operation's page is called, as a short sentence.
360fn title(op: Op) -> &'static str {
361 match op {
362 Op::Whoami => "Get the current user",
Merge branch 'worktree-agent-ad7c6d88d93adc817'363 Op::GetWorkspace => "Get a workspace",
Merge branch 'worktree-agent-ab2e39e11a6493412'364 Op::CreateWorkspace => "Create a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look365 Op::DeleteWorkspace => "Delete a workspace",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily366 Op::UpdateWorkspace => "Update a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look367 Op::ListEmails => "List your email addresses",
368 Op::AddEmail => "Add an email address",
369 Op::RemoveEmail => "Remove an email address",
370 Op::UpdateEmailSettings => "Change your email settings",
371 Op::ListInvites => "List your invites",
372 Op::CreateInvite => "Create an invite",
373 Op::RevokeInvite => "Revoke an invite",
374 Op::ListWorkspaceInvites => "List a workspace's invites",
375 Op::InviteMember => "Invite someone to a workspace",
376 Op::RevokeWorkspaceInvite => "Revoke a workspace's invite",
377 Op::TransferRepo => "Transfer a repository",
378 Op::RenameRepo => "Rename a repository",
379 Op::RenameBranch => "Rename a branch",
380 Op::ArchiveRepo => "Archive a repository",
381 Op::UnarchiveRepo => "Unarchive a repository",
382 Op::SetRepoVisibility => "Change a repository's visibility",
383 Op::DeleteRepo => "Delete a repository",
384 Op::ListDeletedRepos => "List recently deleted repositories",
385 Op::RestoreRepo => "Restore a deleted repository",
386 Op::PurgeRepo => "Purge a deleted repository",
Merge branch 'worktree-agent-ab2e39e11a6493412'387 Op::ListRepos => "List repositories",
388 Op::GetRepo => "Get a repository",
389 Op::CreateRepo => "Create a repository",
390 Op::UpdateRepo => "Update a repository",
391 Op::GetRepoSettings => "Get repository settings",
392 Op::UpdateRepoSettings => "Update repository settings",
Fast pages, required checks on the branch, self-hosted runners, honest incidents393 Op::ListCheckNames => "List check names",
Merge branch 'worktree-agent-ab2e39e11a6493412'394 Op::GetMergeQueue => "Get the merge queue",
395 Op::MessageAgent => "Message an agent",
396 Op::AnswerMessage => "Answer a message",
397 Op::TakeMessages => "Take new messages",
Agents and memory, checks and conflicts, profiles, slug renames, custom domains398 Op::Remember => "Remember something",
399 Op::Recall => "Recall memory",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API400 Op::SearchContext => "Search the context hub",
401 Op::GetEntity => "Get a catalog entry",
Search across all of g1t, Explore, and a command palette402 Op::Search => "Search g1t",
Merge branch 'worktree-agent-ab2e39e11a6493412'403 Op::ListIssues => "List issues",
404 Op::GetIssue => "Get an issue",
405 Op::CreateIssue => "Create an issue",
406 Op::UpdateIssue => "Update an issue",
407 Op::CloseIssue => "Close an issue",
408 Op::ReopenIssue => "Reopen an issue",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent409 Op::AssignIssue => "Assign an issue to g1t",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step410 Op::Delegate => "Put an agent on it",
Merge branch 'worktree-agent-ab2e39e11a6493412'411 Op::PlanWork => "Plan work",
412 Op::GetPlan => "Get a plan",
413 Op::ApplyPlan => "Apply a plan",
414 Op::ListLabels => "List labels",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar415 Op::CreateLabel => "Create a label",
416 Op::UpdateLabel => "Update a label",
417 Op::DeleteLabel => "Delete a label",
418 Op::AddDefaultLabels => "Add the default labels",
419 Op::ListIssueLabels => "List an issue's labels",
420 Op::AddIssueLabels => "Add labels to an issue",
421 Op::SetIssueLabels => "Set an issue's labels",
422 Op::RemoveIssueLabels => "Remove labels from an issue",
423 Op::ListMilestones => "List milestones",
424 Op::GetMilestone => "Get a milestone",
425 Op::CreateMilestone => "Create a milestone",
426 Op::UpdateMilestone => "Update a milestone",
427 Op::DeleteMilestone => "Delete a milestone",
428 Op::UpdatePullRequest => "Update a pull request",
Merge branch 'worktree-agent-ab2e39e11a6493412'429 Op::AddComment => "Add a comment",
430 Op::ReviewPullRequest => "Review a pull request",
431 Op::ListPullRequests => "List pull requests",
432 Op::GetPullRequest => "Get a pull request",
433 Op::CreatePullRequest => "Create a pull request",
434 Op::RecordSession => "Record session entries",
435 Op::ReadSession => "Read a session",
436 Op::MarkPullRequestReady => "Mark a pull request ready",
437 Op::ClosePullRequest => "Close a pull request",
438 Op::GetPullRequestChanges => "Get a pull request's changes",
439 Op::MergePullRequest => "Merge a pull request",
440 Op::ListEvents => "List repository events",
441 Op::ListIntegrations => "List integrations",
442 Op::ConnectIntegration => "Connect an integration",
443 Op::DisconnectIntegration => "Disconnect an integration",
444 Op::TestIntegration => "Test an integration",
445 Op::GetContext => "Look up a ticket",
446 Op::ImportIssue => "Import an issue",
447 Op::GetModelRoutes => "Get model routes",
448 Op::SetModelRoutes => "Set model routes",
449 Op::ListWebhooks => "List webhooks",
450 Op::CreateWebhook => "Create a webhook",
451 Op::UpdateWebhook => "Update a webhook",
452 Op::DeleteWebhook => "Delete a webhook",
453 Op::PingWebhook => "Ping a webhook",
454 Op::ListWebhookDeliveries => "List webhook deliveries",
455 Op::RedeliverWebhook => "Redeliver a webhook delivery",
456 Op::ListWorkflows => "List workflows",
457 Op::ListWorkflowRuns => "List workflow runs",
458 Op::GetWorkflowRun => "Get a workflow run",
459 Op::GetJobLogs => "Get a job's log",
460 Op::DispatchWorkflow => "Run a workflow",
461 Op::CancelWorkflowRun => "Cancel a workflow run",
462 Op::RerunWorkflowRun => "Re-run a workflow run",
463 Op::UpdateWorkflow => "Turn a workflow on or off",
464 Op::ListActionsSecrets => "List secrets",
465 Op::SetActionsSecret => "Set a secret",
466 Op::DeleteActionsSecret => "Delete a secret",
467 Op::ListActionsVariables => "List variables",
468 Op::SetActionsVariable => "Set a variable",
469 Op::DeleteActionsVariable => "Delete a variable",
Fast pages, required checks on the branch, self-hosted runners, honest incidents470 Op::ListRunners => "List self-hosted runners",
471 Op::ListRunnerGroups => "List runner groups",
472 Op::GetRunnerSettings => "Get runner settings",
473 Op::CreateRunnerRegistrationToken => "Create a runner registration token",
474 Op::RemoveRunner => "Remove a self-hosted runner",
475 Op::CreateRunnerGroup => "Create a runner group",
476 Op::UpdateRunnerGroup => "Change a runner group",
477 Op::DeleteRunnerGroup => "Delete a runner group",
478 Op::UpdateRunnerSettings => "Change runner settings",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look479 Op::ListCollaborators => "List who has access",
480 Op::AddCollaborator => "Add a collaborator",
481 Op::UpdateCollaborator => "Change a collaborator's role",
482 Op::RemoveCollaborator => "Remove a collaborator",
483 Op::GetCollaboratorPermission => "Get someone's permission",
484 Op::ListRepoInvitations => "List a repository's invitations",
485 Op::RevokeRepoInvitation => "Revoke a repository invitation",
486 Op::ListMyRepoInvitations => "List your repository invitations",
487 Op::AcceptRepoInvitation => "Accept a repository invitation",
488 Op::DeclineRepoInvitation => "Decline a repository invitation",
489 Op::SetBasePermission => "Set the base permission",
490 Op::ListOutsideCollaborators => "List outside collaborators",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily491 Op::ListSecurityAlerts => "List security alerts",
492 Op::DismissSecurityAlert => "Dismiss a security alert",
493 Op::ReopenSecurityAlert => "Reopen a security alert",
API: notifications over REST and MCP, with notifications scopes494 Op::ListNotifications => "List notifications",
495 Op::MarkNotificationsRead => "Mark notifications read",
496 Op::GetNotificationThread => "Get a thread",
497 Op::MarkThreadRead => "Mark a thread read",
498 Op::MarkThreadDone => "Mark a thread done",
499 Op::SaveThread => "Save a thread",
500 Op::SnoozeThread => "Snooze a thread",
501 Op::GetThreadSubscription => "Get a thread subscription",
502 Op::SetThreadSubscription => "Set a thread subscription",
503 Op::DeleteThreadSubscription => "Unsubscribe from a thread",
504 Op::GetRepoSubscription => "Get how you watch a repository",
505 Op::SetRepoSubscription => "Watch a repository",
506 Op::DeleteRepoSubscription => "Stop watching a repository",
507 Op::ListWatchedRepos => "List repositories you watch",
API: pinned projects over REST and MCP508 Op::ListPinnedProjects => "List your pinned projects",
Usage, Billing settings and prepaid AI credit; fixes from the UX audit509 Op::GetUsage => "Get a workspace's usage",
510 Op::GetBudget => "Get a workspace's budget",
511 Op::SetBudget => "Change a workspace's budget",
512 Op::GetAiCredit => "Get a workspace's AI credit",
513 Op::BuyAiCredit => "Buy AI credit",
514 Op::ListInvoices => "List a workspace's invoices",
515 Op::GetBillingDetails => "Get a workspace's billing details",
API: pinned projects over REST and MCP516 Op::PinProject => "Pin a project",
517 Op::UnpinProject => "Unpin a project",
518 Op::ReorderPinnedProjects => "Reorder your pinned projects",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar519 Op::ListTeams => "List teams",
520 Op::GetTeam => "Get a team",
521 Op::CreateTeam => "Create a team",
522 Op::UpdateTeam => "Update a team",
523 Op::DeleteTeam => "Delete a team",
524 Op::ListTeamMembers => "List a team's members",
525 Op::SetTeamMember => "Add or change a team member",
526 Op::RemoveTeamMember => "Remove a team member",
527 Op::ListChildTeams => "List child teams",
528 Op::ListTeamRepos => "List a team's repositories",
529 Op::SetTeamRepo => "Give a team a role on a repository",
530 Op::RemoveTeamRepo => "Remove a team from a repository",
531 Op::SetTeamReviewAssignment => "Set a team's review assignment",
532 Op::ListUserTeams => "List someone's teams",
533 Op::RequestReviewers => "Request reviewers",
534 Op::RemoveRequestedReviewers => "Remove requested reviewers",
535 Op::GetCodeownersErrors => "List CODEOWNERS errors",
536 Op::Security(op) => op.title(),
API and MCP server in Rust; a public index at the API root537 }
538}
539
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look540/// Why an operation can be refused with `402 payment_required`, if it
541/// can: the ones that start an agent, when the workspace has no credit,
542/// and the ones that make a repository private in a workspace, when a free
543/// workspace's private storage has no room for it.
544fn may_need_payment(op: Op) -> Option<&'static str> {
545 match op {
546 Op::AssignIssue | Op::PlanWork | Op::ApplyPlan => Some("The workspace has no agent credit."),
547 Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo => Some(
548 "A free workspace's private storage has no room for this private repository.",
549 ),
550 _ => None,
551 }
Merge branch 'worktree-agent-ab2e39e11a6493412'552}
553
554/// What the reference says beyond each operation's own description, keyed
555/// by operation id, written by hand from what the services return: `notes`
556/// (Markdown, added to the description) and example `params` (path),
557/// `query`, `request` (body) and `response`.
558const REFERENCE: &str = include_str!("reference.json");
559
560fn examples() -> Map<String, Value> {
561 match serde_json::from_str(REFERENCE) {
562 Ok(Value::Object(examples)) => examples,
563 _ => Map::new(),
API and MCP server in Rust; a public index at the API root564 }
565}
566
Agents as a team: lifecycle, merge queue, billing and a new shell567/// `/repos/:owner/:name` as OpenAPI writes it: `/repos/{owner}/{name}`.
API and MCP server in Rust; a public index at the API root568fn openapi_path(route: &Route) -> String {
569 route
570 .path
571 .split('/')
572 .map(|segment| match segment.strip_prefix(':') {
573 Some(name) => format!("{{{name}}}"),
574 None => segment.to_owned(),
575 })
576 .collect::<Vec<_>>()
577 .join("/")
578}
579
580fn error_response(description: &str) -> Value {
581 json!({
582 "description": description,
583 "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } },
584 })
585}
586
Merge branch 'worktree-agent-ab2e39e11a6493412'587/// A parameter in the path or the query, described by the operation's
588/// input schema where it has the same name.
589fn parameter(name: &str, place: &str, required: bool, schema: Option<&Value>) -> Value {
590 let mut schema = schema.cloned().unwrap_or_else(|| json!({ "type": "string" }));
591 let description = match name {
592 "owner" => Some(Value::from("The workspace that owns the repository.")),
593 "name" => Some(Value::from("The repository's name.")),
594 _ => schema.as_object_mut().and_then(|schema| schema.remove("description")),
595 };
596 let mut parameter = json!({
597 "name": name,
598 "in": place,
599 "required": required,
600 "schema": schema,
601 });
602 if let Some(description) = description {
603 parameter["description"] = description;
604 }
605 parameter
606}
607
608/// The operation id of a route. An operation reached at a workspace's
609/// address as well as a repository's is documented once for each, with its
610/// own id; GitHub's alternative addresses for one operation keep GitHub's
611/// names.
612fn operation_id(route: &Route) -> String {
613 let op = route.op;
614 let base = match (route.method, route.path.rsplit('/').next().unwrap_or_default()) {
615 ("PUT", "enable") => "enable_workflow".to_owned(),
616 ("PUT", "disable") => "disable_workflow".to_owned(),
617 ("POST", "rerun-failed-jobs") => "rerun_failed_jobs".to_owned(),
618 ("PATCH", ":setting") => "update_actions_variable".to_owned(),
619 ("GET", "runs") if route.path.contains("/workflows/:workflow/") => "list_runs_of_workflow".to_owned(),
API: notifications over REST and MCP, with notifications scopes620 // One repository's notifications, and an issue's subscription by
621 // its number rather than a thread's id.
622 (_, "notifications") if route.path.starts_with("/repos/") => match op {
623 Op::ListNotifications => "list_repo_notifications".to_owned(),
624 _ => "mark_repo_notifications_read".to_owned(),
625 },
626 (method, "subscription") if route.path.contains("/issues/:number/") => match method {
627 "GET" => "get_issue_subscription".to_owned(),
628 "PUT" => "set_issue_subscription".to_owned(),
629 _ => "delete_issue_subscription".to_owned(),
630 },
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar631 // One label off an issue, by its name in the path.
632 ("DELETE", ":label") if route.path.contains("/issues/:number/") => "remove_issue_label".to_owned(),
API: notifications over REST and MCP, with notifications scopes633 ("DELETE", "saved") => "unsave_thread".to_owned(),
634 ("DELETE", "snooze") => "unsnooze_thread".to_owned(),
Merge branch 'worktree-agent-ab2e39e11a6493412'635 _ => op.name().to_owned(),
636 };
637 if route.path.starts_with("/workspaces/") && ROUTES.iter().any(|other| other.op == op && other.path.starts_with("/repos/")) {
638 format!("{base}_for_workspace")
639 } else {
640 base
641 }
642}
643
644/// The summary of a route: its operation's title, or for one of GitHub's
645/// alternative addresses, what that address does.
646fn summary(route: &Route, id: &str) -> String {
647 let base = match id.trim_end_matches("_for_workspace") {
648 "enable_workflow" => "Turn a workflow on",
649 "disable_workflow" => "Turn a workflow off",
650 "rerun_failed_jobs" => "Re-run failed jobs",
651 "update_actions_variable" => "Update a variable",
652 "list_runs_of_workflow" => "List a workflow's runs",
API: notifications over REST and MCP, with notifications scopes653 "list_repo_notifications" => "List a repository's notifications",
654 "mark_repo_notifications_read" => "Mark a repository's notifications read",
655 "get_issue_subscription" => "Get your subscription to an issue",
656 "set_issue_subscription" => "Subscribe to an issue",
657 "delete_issue_subscription" => "Unsubscribe from an issue",
658 "unsave_thread" => "Unsave a thread",
659 "unsnooze_thread" => "Bring a snoozed thread back",
Merge branch 'worktree-agent-ab2e39e11a6493412'660 _ => title(route.op),
661 };
662 if id.ends_with("_for_workspace") {
663 format!("{base} for a workspace")
664 } else {
665 base.to_owned()
666 }
667}
668
API and MCP server in Rust; a public index at the API root669fn operation(route: &Route) -> Value {
670 let op = route.op;
671 let path_params: Vec<&str> = route.params().collect();
672 // `owner` and `name` in the path stand for the operation's `repo` input.
673 let covered = |name: &str| name == "repo" || path_params.contains(&name);
Merge branch 'worktree-agent-ab2e39e11a6493412'674 let all_properties = op.properties();
675 let mut properties = all_properties.clone();
API and MCP server in Rust; a public index at the API root676 properties.retain(|name, _| !covered(name));
677 let required: Vec<String> = op
678 .required()
679 .into_iter()
680 .filter(|name| !covered(name))
681 .collect();
682
683 let mut parameters: Vec<Value> = path_params
684 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'685 .map(|name| parameter(name, "path", true, all_properties.get(*name)))
API and MCP server in Rust; a public index at the API root686 .collect();
687 let mut body = Value::Null;
688 if route.method == "GET" {
689 for (name, key) in route.query {
Merge branch 'worktree-agent-ab2e39e11a6493412'690 parameters.push(parameter(
691 name,
692 "query",
693 required.iter().any(|required| required == key),
694 properties.get(*key),
695 ));
API and MCP server in Rust; a public index at the API root696 }
697 } else if !properties.is_empty() {
698 let mut schema = json!({ "type": "object", "properties": properties });
699 if !required.is_empty() {
700 schema["required"] = json!(required);
701 }
702 body = json!({
703 "required": !required.is_empty(),
704 "content": { "application/json": { "schema": schema } },
705 });
706 }
707
Merge branch 'worktree-agent-ab2e39e11a6493412'708 let id = operation_id(route);
709 let mut responses = Map::new();
710 responses.insert(
711 "200".into(),
712 json!({
713 "description": "Success.",
714 "content": { "application/json": { "schema": {} } },
715 }),
716 );
717 responses.insert(
718 "401".into(),
719 error_response("A token is required, or the one sent is not valid."),
720 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look721 if let Some(reason) = may_need_payment(op) {
722 responses.insert("402".into(), error_response(reason));
Merge branch 'worktree-agent-ab2e39e11a6493412'723 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step724 responses.insert(
725 "403".into(),
726 error_response("Signed in, but not allowed to do this: the role you have is not enough, or the token lacks the scope it needs, which `needed_scope` names."),
727 );
Search across all of g1t, Explore, and a command palette728 if !matches!(op, Op::Whoami | Op::ListRepos | Op::Search) {
Merge branch 'worktree-agent-ab2e39e11a6493412'729 responses.insert("404".into(), error_response("It does not exist, or you cannot see it."));
730 }
731 if route.method != "GET" {
732 responses.insert(
733 "409".into(),
734 error_response("The request conflicts with the current state."),
735 );
736 }
737 if op != Op::Whoami {
738 responses.insert("422".into(), error_response("The input is not valid."));
739 }
740 // Public data can be read without a token; everything else needs one.
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step741 let scope: Vec<&str> = scope_for(op.name()).map(|scope| scope.as_str()).into_iter().collect();
Merge branch 'worktree-agent-ab2e39e11a6493412'742 let security = if op.needs_user() {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step743 json!([{ "token": scope }])
Webhooks: every event, to your own addresses, signed and retried744 } else {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step745 json!([{ "token": scope }, {}])
Webhooks: every event, to your own addresses, signed and retried746 };
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step747 let (tool, action) = crate::tools::TOOLS
748 .iter()
749 .find_map(|tool| {
750 tool.actions
751 .iter()
752 .find(|action| action.op == op)
753 .map(|action| (tool.name, action.name))
754 })
755 .unwrap_or_default();
API and MCP server in Rust; a public index at the API root756 let mut described = json!({
Webhooks: every event, to your own addresses, signed and retried757 "operationId": id,
API and MCP server in Rust; a public index at the API root758 "tags": [tag(op)],
Merge branch 'worktree-agent-ab2e39e11a6493412'759 "summary": summary(route, &id),
API and MCP server in Rust; a public index at the API root760 "description": op.description(),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step761 "x-operation": op.name(),
762 "x-mcp-tool": tool,
763 "x-mcp-action": action,
764 "x-scope": scope.first().copied(),
Merge branch 'worktree-agent-ab2e39e11a6493412'765 "security": security,
API and MCP server in Rust; a public index at the API root766 "parameters": parameters,
Merge branch 'worktree-agent-ab2e39e11a6493412'767 "responses": responses,
API and MCP server in Rust; a public index at the API root768 });
769 if !body.is_null() {
770 described["requestBody"] = body;
771 }
772 described
773}
774
775/// Entries for device sign-in, which is not an operation.
776fn onboarding() -> Map<String, Value> {
777 let paths = json!({
Agents as a team: lifecycle, merge queue, billing and a new shell778 "/device/code": {
API and MCP server in Rust; a public index at the API root779 "post": {
780 "operationId": "device_code",
781 "tags": ["Accounts"],
782 "summary": "Start signing in",
Agents as a team: lifecycle, merge queue, billing and a new shell783 "description": "Begins a device sign-in. Show the person `verification_uri_complete` and have them open it in a browser, where they sign in or register and approve the code. Then poll `/device/token`.",
API and MCP server in Rust; a public index at the API root784 "security": [],
785 "requestBody": {
786 "content": { "application/json": { "schema": {
787 "type": "object",
788 "properties": {
789 "client_name": {
790 "type": "string",
791 "description": "What is asking, shown to the person approving. For example, Claude Code.",
792 },
793 },
794 } } },
795 },
796 "responses": { "200": {
797 "description": "The codes for this sign-in.",
798 "content": { "application/json": { "schema": {
799 "type": "object",
800 "properties": {
Agents as a team: lifecycle, merge queue, billing and a new shell801 "device_code": { "type": "string", "description": "Secret. Send it to /device/token." },
API and MCP server in Rust; a public index at the API root802 "user_code": { "type": "string", "description": "Shown to the person, like WDJB-MJHT." },
803 "verification_uri": { "type": "string" },
804 "verification_uri_complete": {
805 "type": "string",
806 "description": "The link to give the person; it carries the code.",
807 },
808 "expires_in": { "type": "integer", "description": "Seconds until the codes expire." },
809 "interval": { "type": "integer", "description": "Seconds to wait between polls." },
810 },
811 } } },
812 } },
813 },
814 },
Agents as a team: lifecycle, merge queue, billing and a new shell815 "/device/token": {
API and MCP server in Rust; a public index at the API root816 "post": {
817 "operationId": "device_token",
818 "tags": ["Accounts"],
819 "summary": "Finish signing in",
820 "description": "Asks whether the person has approved. Poll no faster than the interval. The token is returned once.",
821 "security": [],
822 "requestBody": {
823 "required": true,
824 "content": { "application/json": { "schema": {
825 "type": "object",
826 "required": ["device_code"],
827 "properties": { "device_code": { "type": "string" } },
828 } } },
829 },
830 "responses": { "200": {
831 "description": "The state of the sign-in.",
832 "content": { "application/json": { "schema": {
833 "type": "object",
834 "required": ["status"],
835 "properties": {
836 "status": { "type": "string", "enum": ["pending", "approved", "denied", "expired"] },
837 "token": { "type": "string", "description": "Present when approved." },
838 "username": { "type": "string" },
839 "verified": {
840 "type": "boolean",
841 "description": "Whether the account's email is confirmed.",
842 },
843 },
844 } } },
845 } },
846 },
847 },
848 });
849 match paths {
850 Value::Object(paths) => paths,
851 _ => Map::new(),
852 }
853}
854
Merge branch 'worktree-agent-ab2e39e11a6493412'855
856/// Puts each operation's examples, where it has them, into its request
857/// and response. Path and query values go under `x-example-params` and
858/// `x-example-query`, which tools that build a request can use.
859fn attach_examples(paths: &mut Map<String, Value>) {
860 let examples = examples();
861 for methods in paths.values_mut() {
862 let Some(methods) = methods.as_object_mut() else { continue };
863 for operation in methods.values_mut() {
864 let id = operation["operationId"].as_str().unwrap_or_default().to_owned();
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step865 let name = operation["x-operation"].as_str().unwrap_or_default().to_owned();
866 let Some(example) = examples.get(&id).or_else(|| examples.get(&name)) else {
Merge branch 'worktree-agent-ab2e39e11a6493412'867 continue;
868 };
869 if let Some(notes) = example.get("notes").and_then(Value::as_str) {
870 let description = operation["description"].as_str().unwrap_or_default();
871 operation["description"] = json!(format!("{description}\n\n{notes}"));
872 }
873 if let Some(response) = example.get("response") {
874 let content = &mut operation["responses"]["200"]["content"]["application/json"];
875 if content.is_object() {
876 content["example"] = response.clone();
877 }
878 }
879 if let Some(request) = example.get("request") {
880 let content = &mut operation["requestBody"]["content"]["application/json"];
881 if content.is_object() {
882 content["example"] = request.clone();
883 }
884 }
885 for (key, extension) in [("params", "x-example-params"), ("query", "x-example-query")] {
886 if let Some(values) = example.get(key) {
887 operation[extension] = values.clone();
888 }
889 }
890 }
891 }
892}
893
API and MCP server in Rust; a public index at the API root894pub fn document() -> Value {
895 let mut paths = onboarding();
896 for route in ROUTES {
897 let entry = paths
898 .entry(openapi_path(route))
899 .or_insert_with(|| json!({}));
900 entry[route.method.to_lowercase()] = operation(route);
901 }
Merge branch 'worktree-agent-ab2e39e11a6493412'902 attach_examples(&mut paths);
903 let tags: Vec<Value> = SECTIONS
904 .iter()
905 .map(|(name, description, ops)| {
906 json!({
907 "name": name,
908 "description": description,
909 // The section's operations in reading order, by MCP tool name.
910 "x-tools": ops.iter().map(|op| op.name()).collect::<Vec<_>>(),
911 })
912 })
913 .collect();
914 let codes = ["unauthenticated", "payment_required", "forbidden", "not_found", "conflict", "invalid"];
API and MCP server in Rust; a public index at the API root915 json!({
916 "openapi": "3.1.0",
917 "info": {
918 "title": "g1t API",
919 "version": "1",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API920 "description": "The REST API for g1t, a git forge built for agents. The same operations are available to agents as MCP tools at https://mcp.g1t.sh. Every name in a request or response body is `snake_case`; names you chose, such as a workflow's inputs or a secret's name, are returned as you wrote them.",
API and MCP server in Rust; a public index at the API root921 "license": { "name": "MIT", "identifier": "MIT" },
922 },
923 "servers": [{ "url": "https://api.g1t.sh" }],
924 "security": [{ "token": [] }, {}],
Merge branch 'worktree-agent-ab2e39e11a6493412'925 "tags": tags,
API and MCP server in Rust; a public index at the API root926 "paths": paths,
927 "components": {
928 "securitySchemes": {
929 "token": {
930 "type": "http",
931 "scheme": "bearer",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step932 "description": "An access token, `g1t_…`. Public data needs none. Each operation names the scope a token needs for it; see https://docs.g1t.sh/guides/authentication/#scopes.",
API and MCP server in Rust; a public index at the API root933 },
934 },
935 "schemas": {
936 "Error": {
937 "type": "object",
938 "required": ["error"],
939 "properties": {
940 "error": {
941 "type": "object",
942 "required": ["code", "message"],
943 "properties": {
Merge branch 'worktree-agent-ab2e39e11a6493412'944 "code": { "type": "string", "enum": codes },
API and MCP server in Rust; a public index at the API root945 "message": { "type": "string" },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step946 "needed_scope": {
947 "type": "string",
948 "description": "On a 403 for an access token without the scope the call needs: that scope, such as `issues:write`.",
949 },
API and MCP server in Rust; a public index at the API root950 },
951 },
952 },
953 },
954 },
955 },
956 })
957}
958
959#[cfg(test)]
960mod tests {
961 use super::*;
962
963 #[test]
964 fn every_route_is_documented_once() {
965 let document = document();
966 let mut ids = Vec::new();
967 for (_, methods) in document["paths"].as_object().unwrap() {
968 for (_, operation) in methods.as_object().unwrap() {
969 ids.push(operation["operationId"].as_str().unwrap().to_owned());
970 }
971 }
972 for op in Op::ALL {
973 assert_eq!(
974 ids.iter().filter(|id| *id == op.name()).count(),
975 1,
976 "{}",
977 op.name()
978 );
979 }
Webhooks: every event, to your own addresses, signed and retried980 let mut unique = ids.clone();
981 unique.sort();
982 unique.dedup();
983 assert_eq!(unique.len(), ids.len(), "operation ids repeat");
API and MCP server in Rust; a public index at the API root984 }
985
986 #[test]
987 fn path_and_query_inputs_are_not_repeated_in_the_body() {
988 let document = document();
Agents as a team: lifecycle, merge queue, billing and a new shell989 let merge = &document["paths"]["/repos/{owner}/{name}/pulls/{number}/merge"]["post"];
API and MCP server in Rust; a public index at the API root990 let body = &merge["requestBody"]["content"]["application/json"]["schema"]["properties"];
991 assert!(body.get("keep_issue_open").is_some());
992 assert!(body.get("repo").is_none() && body.get("number").is_none());
Agents as a team: lifecycle, merge queue, billing and a new shell993 let list = &document["paths"]["/repos"]["get"];
API and MCP server in Rust; a public index at the API root994 assert_eq!(list["parameters"][0]["name"], "q");
995 assert!(list.get("requestBody").is_none());
996 }
997
998 #[test]
Merge branch 'worktree-agent-ab2e39e11a6493412'999 fn every_operation_is_in_one_section() {
1000 for op in Op::ALL {
1001 let sections = SECTIONS
1002 .iter()
1003 .filter(|(_, _, ops)| ops.contains(&op))
1004 .count();
1005 assert_eq!(sections, 1, "{}", op.name());
1006 }
1007 }
1008
1009 #[test]
API and MCP server in Rust; a public index at the API root1010 fn titles_read_as_sentences() {
Merge branch 'worktree-agent-ab2e39e11a6493412'1011 assert_eq!(title(Op::CreateIssue), "Create an issue");
API and MCP server in Rust; a public index at the API root1012 assert_eq!(title(Op::Whoami), "Get the current user");
1013 }
Merge branch 'worktree-agent-ab2e39e11a6493412'1014
1015 #[test]
1016 fn every_operation_has_an_example_response() {
1017 let examples = examples();
1018 assert!(!examples.is_empty(), "reference.json does not parse");
1019 let document = document();
1020 let mut known = Vec::new();
1021 for (path, methods) in document["paths"].as_object().unwrap() {
1022 for (method, operation) in methods.as_object().unwrap() {
1023 known.push(operation["operationId"].as_str().unwrap().to_owned());
1024 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
1025 assert!(!example.is_null(), "{method} {path} has no example response");
1026 }
1027 }
1028 for id in examples.keys() {
1029 assert!(known.contains(id), "reference.json names {id}, which is not an operation");
1030 }
1031 }
1032
1033 #[test]
1034 fn example_requests_send_only_what_the_body_takes() {
1035 let document = document();
1036 for (path, methods) in document["paths"].as_object().unwrap() {
1037 for (method, operation) in methods.as_object().unwrap() {
1038 let content = &operation["requestBody"]["content"]["application/json"];
1039 let Some(example) = content["example"].as_object() else { continue };
1040 let properties = &content["schema"]["properties"];
1041 for key in example.keys() {
1042 assert!(!properties[key].is_null(), "{method} {path}: {key} is not in the body");
1043 }
1044 }
1045 }
1046 }
1047
1048 /// The docs site's copy of the document. Run with `G1T_WRITE_OPENAPI=1`
1049 /// to rewrite it after changing an operation.
1050 #[test]
1051 fn the_docs_copy_is_current() {
1052 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../docs/src/data/openapi.json");
1053 let current = serde_json::to_string_pretty(&document()).unwrap() + "\n";
1054 if std::env::var_os("G1T_WRITE_OPENAPI").is_some() {
1055 std::fs::write(path, &current).unwrap();
1056 return;
1057 }
1058 let copy = std::fs::read_to_string(path).unwrap_or_default().replace("\r\n", "\n");
1059 assert!(
1060 copy == current,
1061 "apps/docs/src/data/openapi.json is out of date: run G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi"
1062 );
1063 }
API reference: no example reads as a real secret1064
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1065 /// The reference shows responses as they are sent: `snake_case`.
1066 #[test]
1067 fn example_responses_are_snake_case() {
1068 let document = document();
1069 for (path, methods) in document["paths"].as_object().unwrap() {
1070 for (method, operation) in methods.as_object().unwrap() {
1071 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
1072 let leaked = g1t_kit::wire::camel_case_keys(example);
1073 assert!(leaked.is_empty(), "{method} {path} shows {leaked:?}");
1074 }
1075 }
1076 }
1077
API reference: no example reads as a real secret1078 /// Examples never hold anything that reads as a real credential, which
1079 /// secret scanners rightly flag in a public repository: they end in `…`
1080 /// after the prefix, as `whsec_…` and `g1t_…` do.
1081 #[test]
1082 fn examples_hold_no_real_looking_secrets() {
Fast pages, required checks on the branch, self-hosted runners, honest incidents1083 let prefixes = ["whsec_", "g1t_", "g1tr_", "g1trt_", "sk_live_", "sk_test_", "ghp_", "github_pat_", "xoxb-", "AKIA"];
API reference: no example reads as a real secret1084 for (line, text) in REFERENCE.lines().enumerate() {
1085 for prefix in prefixes {
1086 let mut rest = text;
1087 while let Some(at) = rest.find(prefix) {
1088 let after = &rest[at + prefix.len()..];
1089 let run = after.chars().take_while(|c| c.is_ascii_alphanumeric()).count();
1090 assert!(
1091 run < 12,
1092 "reference.json line {}: `{prefix}` followed by {run} characters reads as a real secret; write `{prefix}…`",
1093 line + 1
1094 );
1095 rest = after;
1096 }
1097 }
1098 }
1099 }
API and MCP server in Rust; a public index at the API root1100}

This file's history is long; its oldest lines are credited to the oldest commit read.