Skip to content

g1t/apps/api/src/openapi.rs

946 lines39,765 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;
11use crate::rest::{ROUTES, Route};
12
Merge branch 'worktree-agent-ab2e39e11a6493412'13/// The sections of the API reference: a name, what it covers, and its
14/// operations in the order a reader meets them.
15const SECTIONS: &[(&str, &str, &[Op])] = &[
16 (
17 "Accounts",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look18 "Signing in from a tool, who a token acts as, and your email addresses.",
19 &[Op::Whoami, Op::ListEmails, Op::AddEmail, Op::RemoveEmail, Op::UpdateEmailSettings],
Merge branch 'worktree-agent-ab2e39e11a6493412'20 ),
21 (
API: notifications over REST and MCP, with notifications scopes22 "Notifications",
23 "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.",
24 &[
25 Op::ListNotifications,
26 Op::MarkNotificationsRead,
27 Op::GetNotificationThread,
28 Op::MarkThreadRead,
29 Op::MarkThreadDone,
30 Op::SaveThread,
31 Op::SnoozeThread,
32 Op::GetThreadSubscription,
33 Op::SetThreadSubscription,
34 Op::DeleteThreadSubscription,
35 Op::GetRepoSubscription,
36 Op::SetRepoSubscription,
37 Op::DeleteRepoSubscription,
38 Op::ListWatchedRepos,
39 ],
40 ),
41 (
API: pinned projects over REST and MCP42 "Pinned projects",
43 "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.",
44 &[Op::ListPinnedProjects, Op::PinProject, Op::UnpinProject, Op::ReorderPinnedProjects],
45 ),
46 (
Merge branch 'worktree-agent-ab2e39e11a6493412'47 "Workspaces",
48 "A workspace owns repositories and is the first part of their address. People and agents work in workspaces.",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily49 &[Op::CreateWorkspace, Op::UpdateWorkspace, Op::DeleteWorkspace],
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look50 ),
51 (
52 "Invites",
53 "While g1t is invite-only, every new account needs an invite. Your invites, and inviting people into a workspace by email.",
54 &[
55 Op::ListInvites,
56 Op::CreateInvite,
57 Op::RevokeInvite,
58 Op::ListWorkspaceInvites,
59 Op::InviteMember,
60 Op::RevokeWorkspaceInvite,
61 ],
Merge branch 'worktree-agent-ab2e39e11a6493412'62 ),
63 (
64 "Repositories",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look65 "A repository, how it handles pull requests, and its timeline: renaming, archiving, moving and deleting it.",
Merge branch 'worktree-agent-ab2e39e11a6493412'66 &[
67 Op::ListRepos,
68 Op::CreateRepo,
69 Op::GetRepo,
70 Op::UpdateRepo,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look71 Op::RenameRepo,
72 Op::RenameBranch,
73 Op::SetRepoVisibility,
74 Op::ArchiveRepo,
75 Op::UnarchiveRepo,
76 Op::TransferRepo,
77 Op::DeleteRepo,
78 Op::ListDeletedRepos,
79 Op::RestoreRepo,
80 Op::PurgeRepo,
Merge branch 'worktree-agent-ab2e39e11a6493412'81 Op::GetRepoSettings,
82 Op::UpdateRepoSettings,
Fast pages, required checks on the branch, self-hosted runners, honest incidents83 Op::ListCheckNames,
Merge branch 'worktree-agent-ab2e39e11a6493412'84 Op::ListEvents,
85 ],
86 ),
87 (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look88 "Access",
89 "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.",
90 &[
91 Op::ListCollaborators,
92 Op::AddCollaborator,
93 Op::UpdateCollaborator,
94 Op::RemoveCollaborator,
95 Op::GetCollaboratorPermission,
96 Op::ListRepoInvitations,
97 Op::RevokeRepoInvitation,
98 Op::ListMyRepoInvitations,
99 Op::AcceptRepoInvitation,
100 Op::DeclineRepoInvitation,
101 Op::SetBasePermission,
102 Op::ListOutsideCollaborators,
103 ],
104 ),
105 (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily106 "Security",
107 "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.",
108 &[Op::ListSecurityAlerts, Op::DismissSecurityAlert, Op::ReopenSecurityAlert],
109 ),
110 (
Merge branch 'worktree-agent-ab2e39e11a6493412'111 "Issues",
112 "What should change in a repository, with labels and comments. Issues and pull requests share one sequence of numbers.",
113 &[
114 Op::ListIssues,
115 Op::CreateIssue,
116 Op::GetIssue,
117 Op::UpdateIssue,
118 Op::CloseIssue,
119 Op::ReopenIssue,
120 Op::AssignIssue,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step121 Op::Delegate,
Merge branch 'worktree-agent-ab2e39e11a6493412'122 Op::AddComment,
123 Op::ListLabels,
124 ],
125 ),
126 (
127 "Plans",
128 "An outcome turned into the issues that would get there, with the order they must merge in.",
129 &[Op::PlanWork, Op::GetPlan, Op::ApplyPlan],
130 ),
131 (
132 "Pull requests",
133 "A proposed change in its own fork or on a branch. Several can be made for one issue; the one merged resolves it.",
134 &[
135 Op::ListPullRequests,
136 Op::CreatePullRequest,
137 Op::GetPullRequest,
138 Op::GetPullRequestChanges,
139 Op::MarkPullRequestReady,
140 Op::ReviewPullRequest,
141 Op::MergePullRequest,
142 Op::ClosePullRequest,
143 Op::GetMergeQueue,
144 Op::MessageAgent,
145 Op::AnswerMessage,
146 Op::TakeMessages,
147 ],
148 ),
149 (
150 "Sessions",
151 "The record of how a pull request was made: prompts, reasoning and the tools that ran.",
152 &[Op::ReadSession, Op::RecordSession],
153 ),
154 (
Agents and memory, checks and conflicts, profiles, slug renames, custom domains155 "Memory",
156 "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.",
157 &[Op::Remember, Op::Recall],
158 ),
159 (
Search across all of g1t, Explore, and a command palette160 "Search",
161 "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.",
162 &[Op::Search],
163 ),
164 (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API165 "Context",
166 "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.",
167 &[Op::SearchContext, Op::GetEntity],
168 ),
169 (
Merge branch 'worktree-agent-ab2e39e11a6493412'170 "Actions",
171 "GitHub Actions workflows in .g1t/workflows, their runs, and their jobs' logs.",
172 &[
173 Op::ListWorkflows,
174 Op::ListWorkflowRuns,
175 Op::GetWorkflowRun,
176 Op::GetJobLogs,
177 Op::DispatchWorkflow,
178 Op::CancelWorkflowRun,
179 Op::RerunWorkflowRun,
180 Op::UpdateWorkflow,
181 ],
182 ),
183 (
184 "Secrets and variables",
185 "Values that workflows and deployments read, per repository or for a whole workspace, with a row per environment.",
186 &[
187 Op::ListActionsSecrets,
188 Op::SetActionsSecret,
189 Op::DeleteActionsSecret,
190 Op::ListActionsVariables,
191 Op::SetActionsVariable,
192 Op::DeleteActionsVariable,
193 ],
194 ),
195 (
Fast pages, required checks on the branch, self-hosted runners, honest incidents196 "Runners",
197 "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.",
198 &[
199 Op::ListRunners,
200 Op::CreateRunnerRegistrationToken,
201 Op::RemoveRunner,
202 Op::ListRunnerGroups,
203 Op::CreateRunnerGroup,
204 Op::UpdateRunnerGroup,
205 Op::DeleteRunnerGroup,
206 Op::GetRunnerSettings,
207 Op::UpdateRunnerSettings,
208 ],
209 ),
210 (
Merge branch 'worktree-agent-ab2e39e11a6493412'211 "Webhooks",
212 "Signed HTTPS requests sent to your own address as things happen, for a repository or a whole workspace.",
213 &[
214 Op::ListWebhooks,
215 Op::CreateWebhook,
216 Op::UpdateWebhook,
217 Op::DeleteWebhook,
218 Op::PingWebhook,
219 Op::ListWebhookDeliveries,
220 Op::RedeliverWebhook,
221 ],
222 ),
223 (
224 "Integrations",
225 "A workspace's connections to outside systems: model providers, alert sources and issue trackers.",
226 &[
227 Op::ListIntegrations,
228 Op::ConnectIntegration,
229 Op::DisconnectIntegration,
230 Op::TestIntegration,
231 Op::GetModelRoutes,
232 Op::SetModelRoutes,
233 Op::GetContext,
234 Op::ImportIssue,
235 ],
236 ),
237];
238
API and MCP server in Rust; a public index at the API root239/// The section of the API reference an operation is listed under.
240fn tag(op: Op) -> &'static str {
Merge branch 'worktree-agent-ab2e39e11a6493412'241 SECTIONS
API and MCP server in Rust; a public index at the API root242 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'243 .find(|(_, _, ops)| ops.contains(&op))
244 .map_or("Repositories", |(name, _, _)| name)
245}
246
247/// What an operation's page is called, as a short sentence.
248fn title(op: Op) -> &'static str {
249 match op {
250 Op::Whoami => "Get the current user",
251 Op::CreateWorkspace => "Create a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look252 Op::DeleteWorkspace => "Delete a workspace",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily253 Op::UpdateWorkspace => "Update a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look254 Op::ListEmails => "List your email addresses",
255 Op::AddEmail => "Add an email address",
256 Op::RemoveEmail => "Remove an email address",
257 Op::UpdateEmailSettings => "Change your email settings",
258 Op::ListInvites => "List your invites",
259 Op::CreateInvite => "Create an invite",
260 Op::RevokeInvite => "Revoke an invite",
261 Op::ListWorkspaceInvites => "List a workspace's invites",
262 Op::InviteMember => "Invite someone to a workspace",
263 Op::RevokeWorkspaceInvite => "Revoke a workspace's invite",
264 Op::TransferRepo => "Transfer a repository",
265 Op::RenameRepo => "Rename a repository",
266 Op::RenameBranch => "Rename a branch",
267 Op::ArchiveRepo => "Archive a repository",
268 Op::UnarchiveRepo => "Unarchive a repository",
269 Op::SetRepoVisibility => "Change a repository's visibility",
270 Op::DeleteRepo => "Delete a repository",
271 Op::ListDeletedRepos => "List recently deleted repositories",
272 Op::RestoreRepo => "Restore a deleted repository",
273 Op::PurgeRepo => "Purge a deleted repository",
Merge branch 'worktree-agent-ab2e39e11a6493412'274 Op::ListRepos => "List repositories",
275 Op::GetRepo => "Get a repository",
276 Op::CreateRepo => "Create a repository",
277 Op::UpdateRepo => "Update a repository",
278 Op::GetRepoSettings => "Get repository settings",
279 Op::UpdateRepoSettings => "Update repository settings",
Fast pages, required checks on the branch, self-hosted runners, honest incidents280 Op::ListCheckNames => "List check names",
Merge branch 'worktree-agent-ab2e39e11a6493412'281 Op::GetMergeQueue => "Get the merge queue",
282 Op::MessageAgent => "Message an agent",
283 Op::AnswerMessage => "Answer a message",
284 Op::TakeMessages => "Take new messages",
Agents and memory, checks and conflicts, profiles, slug renames, custom domains285 Op::Remember => "Remember something",
286 Op::Recall => "Recall memory",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API287 Op::SearchContext => "Search the context hub",
288 Op::GetEntity => "Get a catalog entry",
Search across all of g1t, Explore, and a command palette289 Op::Search => "Search g1t",
Merge branch 'worktree-agent-ab2e39e11a6493412'290 Op::ListIssues => "List issues",
291 Op::GetIssue => "Get an issue",
292 Op::CreateIssue => "Create an issue",
293 Op::UpdateIssue => "Update an issue",
294 Op::CloseIssue => "Close an issue",
295 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-agent296 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 step297 Op::Delegate => "Put an agent on it",
Merge branch 'worktree-agent-ab2e39e11a6493412'298 Op::PlanWork => "Plan work",
299 Op::GetPlan => "Get a plan",
300 Op::ApplyPlan => "Apply a plan",
301 Op::ListLabels => "List labels",
302 Op::AddComment => "Add a comment",
303 Op::ReviewPullRequest => "Review a pull request",
304 Op::ListPullRequests => "List pull requests",
305 Op::GetPullRequest => "Get a pull request",
306 Op::CreatePullRequest => "Create a pull request",
307 Op::RecordSession => "Record session entries",
308 Op::ReadSession => "Read a session",
309 Op::MarkPullRequestReady => "Mark a pull request ready",
310 Op::ClosePullRequest => "Close a pull request",
311 Op::GetPullRequestChanges => "Get a pull request's changes",
312 Op::MergePullRequest => "Merge a pull request",
313 Op::ListEvents => "List repository events",
314 Op::ListIntegrations => "List integrations",
315 Op::ConnectIntegration => "Connect an integration",
316 Op::DisconnectIntegration => "Disconnect an integration",
317 Op::TestIntegration => "Test an integration",
318 Op::GetContext => "Look up a ticket",
319 Op::ImportIssue => "Import an issue",
320 Op::GetModelRoutes => "Get model routes",
321 Op::SetModelRoutes => "Set model routes",
322 Op::ListWebhooks => "List webhooks",
323 Op::CreateWebhook => "Create a webhook",
324 Op::UpdateWebhook => "Update a webhook",
325 Op::DeleteWebhook => "Delete a webhook",
326 Op::PingWebhook => "Ping a webhook",
327 Op::ListWebhookDeliveries => "List webhook deliveries",
328 Op::RedeliverWebhook => "Redeliver a webhook delivery",
329 Op::ListWorkflows => "List workflows",
330 Op::ListWorkflowRuns => "List workflow runs",
331 Op::GetWorkflowRun => "Get a workflow run",
332 Op::GetJobLogs => "Get a job's log",
333 Op::DispatchWorkflow => "Run a workflow",
334 Op::CancelWorkflowRun => "Cancel a workflow run",
335 Op::RerunWorkflowRun => "Re-run a workflow run",
336 Op::UpdateWorkflow => "Turn a workflow on or off",
337 Op::ListActionsSecrets => "List secrets",
338 Op::SetActionsSecret => "Set a secret",
339 Op::DeleteActionsSecret => "Delete a secret",
340 Op::ListActionsVariables => "List variables",
341 Op::SetActionsVariable => "Set a variable",
342 Op::DeleteActionsVariable => "Delete a variable",
Fast pages, required checks on the branch, self-hosted runners, honest incidents343 Op::ListRunners => "List self-hosted runners",
344 Op::ListRunnerGroups => "List runner groups",
345 Op::GetRunnerSettings => "Get runner settings",
346 Op::CreateRunnerRegistrationToken => "Create a runner registration token",
347 Op::RemoveRunner => "Remove a self-hosted runner",
348 Op::CreateRunnerGroup => "Create a runner group",
349 Op::UpdateRunnerGroup => "Change a runner group",
350 Op::DeleteRunnerGroup => "Delete a runner group",
351 Op::UpdateRunnerSettings => "Change runner settings",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look352 Op::ListCollaborators => "List who has access",
353 Op::AddCollaborator => "Add a collaborator",
354 Op::UpdateCollaborator => "Change a collaborator's role",
355 Op::RemoveCollaborator => "Remove a collaborator",
356 Op::GetCollaboratorPermission => "Get someone's permission",
357 Op::ListRepoInvitations => "List a repository's invitations",
358 Op::RevokeRepoInvitation => "Revoke a repository invitation",
359 Op::ListMyRepoInvitations => "List your repository invitations",
360 Op::AcceptRepoInvitation => "Accept a repository invitation",
361 Op::DeclineRepoInvitation => "Decline a repository invitation",
362 Op::SetBasePermission => "Set the base permission",
363 Op::ListOutsideCollaborators => "List outside collaborators",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily364 Op::ListSecurityAlerts => "List security alerts",
365 Op::DismissSecurityAlert => "Dismiss a security alert",
366 Op::ReopenSecurityAlert => "Reopen a security alert",
API: notifications over REST and MCP, with notifications scopes367 Op::ListNotifications => "List notifications",
368 Op::MarkNotificationsRead => "Mark notifications read",
369 Op::GetNotificationThread => "Get a thread",
370 Op::MarkThreadRead => "Mark a thread read",
371 Op::MarkThreadDone => "Mark a thread done",
372 Op::SaveThread => "Save a thread",
373 Op::SnoozeThread => "Snooze a thread",
374 Op::GetThreadSubscription => "Get a thread subscription",
375 Op::SetThreadSubscription => "Set a thread subscription",
376 Op::DeleteThreadSubscription => "Unsubscribe from a thread",
377 Op::GetRepoSubscription => "Get how you watch a repository",
378 Op::SetRepoSubscription => "Watch a repository",
379 Op::DeleteRepoSubscription => "Stop watching a repository",
380 Op::ListWatchedRepos => "List repositories you watch",
API: pinned projects over REST and MCP381 Op::ListPinnedProjects => "List your pinned projects",
382 Op::PinProject => "Pin a project",
383 Op::UnpinProject => "Unpin a project",
384 Op::ReorderPinnedProjects => "Reorder your pinned projects",
API and MCP server in Rust; a public index at the API root385 }
386}
387
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look388/// Why an operation can be refused with `402 payment_required`, if it
389/// can: the ones that start an agent, when the workspace has no credit,
390/// and the ones that make a repository private in a workspace, when a free
391/// workspace's private storage has no room for it.
392fn may_need_payment(op: Op) -> Option<&'static str> {
393 match op {
394 Op::AssignIssue | Op::PlanWork | Op::ApplyPlan => Some("The workspace has no agent credit."),
395 Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo => Some(
396 "A free workspace's private storage has no room for this private repository.",
397 ),
398 _ => None,
399 }
Merge branch 'worktree-agent-ab2e39e11a6493412'400}
401
402/// What the reference says beyond each operation's own description, keyed
403/// by operation id, written by hand from what the services return: `notes`
404/// (Markdown, added to the description) and example `params` (path),
405/// `query`, `request` (body) and `response`.
406const REFERENCE: &str = include_str!("reference.json");
407
408fn examples() -> Map<String, Value> {
409 match serde_json::from_str(REFERENCE) {
410 Ok(Value::Object(examples)) => examples,
411 _ => Map::new(),
API and MCP server in Rust; a public index at the API root412 }
413}
414
Agents as a team: lifecycle, merge queue, billing and a new shell415/// `/repos/:owner/:name` as OpenAPI writes it: `/repos/{owner}/{name}`.
API and MCP server in Rust; a public index at the API root416fn openapi_path(route: &Route) -> String {
417 route
418 .path
419 .split('/')
420 .map(|segment| match segment.strip_prefix(':') {
421 Some(name) => format!("{{{name}}}"),
422 None => segment.to_owned(),
423 })
424 .collect::<Vec<_>>()
425 .join("/")
426}
427
428fn error_response(description: &str) -> Value {
429 json!({
430 "description": description,
431 "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } },
432 })
433}
434
Merge branch 'worktree-agent-ab2e39e11a6493412'435/// A parameter in the path or the query, described by the operation's
436/// input schema where it has the same name.
437fn parameter(name: &str, place: &str, required: bool, schema: Option<&Value>) -> Value {
438 let mut schema = schema.cloned().unwrap_or_else(|| json!({ "type": "string" }));
439 let description = match name {
440 "owner" => Some(Value::from("The workspace that owns the repository.")),
441 "name" => Some(Value::from("The repository's name.")),
442 _ => schema.as_object_mut().and_then(|schema| schema.remove("description")),
443 };
444 let mut parameter = json!({
445 "name": name,
446 "in": place,
447 "required": required,
448 "schema": schema,
449 });
450 if let Some(description) = description {
451 parameter["description"] = description;
452 }
453 parameter
454}
455
456/// The operation id of a route. An operation reached at a workspace's
457/// address as well as a repository's is documented once for each, with its
458/// own id; GitHub's alternative addresses for one operation keep GitHub's
459/// names.
460fn operation_id(route: &Route) -> String {
461 let op = route.op;
462 let base = match (route.method, route.path.rsplit('/').next().unwrap_or_default()) {
463 ("PUT", "enable") => "enable_workflow".to_owned(),
464 ("PUT", "disable") => "disable_workflow".to_owned(),
465 ("POST", "rerun-failed-jobs") => "rerun_failed_jobs".to_owned(),
466 ("PATCH", ":setting") => "update_actions_variable".to_owned(),
467 ("GET", "runs") if route.path.contains("/workflows/:workflow/") => "list_runs_of_workflow".to_owned(),
API: notifications over REST and MCP, with notifications scopes468 // One repository's notifications, and an issue's subscription by
469 // its number rather than a thread's id.
470 (_, "notifications") if route.path.starts_with("/repos/") => match op {
471 Op::ListNotifications => "list_repo_notifications".to_owned(),
472 _ => "mark_repo_notifications_read".to_owned(),
473 },
474 (method, "subscription") if route.path.contains("/issues/:number/") => match method {
475 "GET" => "get_issue_subscription".to_owned(),
476 "PUT" => "set_issue_subscription".to_owned(),
477 _ => "delete_issue_subscription".to_owned(),
478 },
479 ("DELETE", "saved") => "unsave_thread".to_owned(),
480 ("DELETE", "snooze") => "unsnooze_thread".to_owned(),
Merge branch 'worktree-agent-ab2e39e11a6493412'481 _ => op.name().to_owned(),
482 };
483 if route.path.starts_with("/workspaces/") && ROUTES.iter().any(|other| other.op == op && other.path.starts_with("/repos/")) {
484 format!("{base}_for_workspace")
485 } else {
486 base
487 }
488}
489
490/// The summary of a route: its operation's title, or for one of GitHub's
491/// alternative addresses, what that address does.
492fn summary(route: &Route, id: &str) -> String {
493 let base = match id.trim_end_matches("_for_workspace") {
494 "enable_workflow" => "Turn a workflow on",
495 "disable_workflow" => "Turn a workflow off",
496 "rerun_failed_jobs" => "Re-run failed jobs",
497 "update_actions_variable" => "Update a variable",
498 "list_runs_of_workflow" => "List a workflow's runs",
API: notifications over REST and MCP, with notifications scopes499 "list_repo_notifications" => "List a repository's notifications",
500 "mark_repo_notifications_read" => "Mark a repository's notifications read",
501 "get_issue_subscription" => "Get your subscription to an issue",
502 "set_issue_subscription" => "Subscribe to an issue",
503 "delete_issue_subscription" => "Unsubscribe from an issue",
504 "unsave_thread" => "Unsave a thread",
505 "unsnooze_thread" => "Bring a snoozed thread back",
Merge branch 'worktree-agent-ab2e39e11a6493412'506 _ => title(route.op),
507 };
508 if id.ends_with("_for_workspace") {
509 format!("{base} for a workspace")
510 } else {
511 base.to_owned()
512 }
513}
514
API and MCP server in Rust; a public index at the API root515fn operation(route: &Route) -> Value {
516 let op = route.op;
517 let path_params: Vec<&str> = route.params().collect();
518 // `owner` and `name` in the path stand for the operation's `repo` input.
519 let covered = |name: &str| name == "repo" || path_params.contains(&name);
Merge branch 'worktree-agent-ab2e39e11a6493412'520 let all_properties = op.properties();
521 let mut properties = all_properties.clone();
API and MCP server in Rust; a public index at the API root522 properties.retain(|name, _| !covered(name));
523 let required: Vec<String> = op
524 .required()
525 .into_iter()
526 .filter(|name| !covered(name))
527 .collect();
528
529 let mut parameters: Vec<Value> = path_params
530 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'531 .map(|name| parameter(name, "path", true, all_properties.get(*name)))
API and MCP server in Rust; a public index at the API root532 .collect();
533 let mut body = Value::Null;
534 if route.method == "GET" {
535 for (name, key) in route.query {
Merge branch 'worktree-agent-ab2e39e11a6493412'536 parameters.push(parameter(
537 name,
538 "query",
539 required.iter().any(|required| required == key),
540 properties.get(*key),
541 ));
API and MCP server in Rust; a public index at the API root542 }
543 } else if !properties.is_empty() {
544 let mut schema = json!({ "type": "object", "properties": properties });
545 if !required.is_empty() {
546 schema["required"] = json!(required);
547 }
548 body = json!({
549 "required": !required.is_empty(),
550 "content": { "application/json": { "schema": schema } },
551 });
552 }
553
Merge branch 'worktree-agent-ab2e39e11a6493412'554 let id = operation_id(route);
555 let mut responses = Map::new();
556 responses.insert(
557 "200".into(),
558 json!({
559 "description": "Success.",
560 "content": { "application/json": { "schema": {} } },
561 }),
562 );
563 responses.insert(
564 "401".into(),
565 error_response("A token is required, or the one sent is not valid."),
566 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look567 if let Some(reason) = may_need_payment(op) {
568 responses.insert("402".into(), error_response(reason));
Merge branch 'worktree-agent-ab2e39e11a6493412'569 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step570 responses.insert(
571 "403".into(),
572 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."),
573 );
Search across all of g1t, Explore, and a command palette574 if !matches!(op, Op::Whoami | Op::ListRepos | Op::Search) {
Merge branch 'worktree-agent-ab2e39e11a6493412'575 responses.insert("404".into(), error_response("It does not exist, or you cannot see it."));
576 }
577 if route.method != "GET" {
578 responses.insert(
579 "409".into(),
580 error_response("The request conflicts with the current state."),
581 );
582 }
583 if op != Op::Whoami {
584 responses.insert("422".into(), error_response("The input is not valid."));
585 }
586 // 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 step587 let scope: Vec<&str> = scope_for(op.name()).map(|scope| scope.as_str()).into_iter().collect();
Merge branch 'worktree-agent-ab2e39e11a6493412'588 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 step589 json!([{ "token": scope }])
Webhooks: every event, to your own addresses, signed and retried590 } else {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step591 json!([{ "token": scope }, {}])
Webhooks: every event, to your own addresses, signed and retried592 };
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step593 let (tool, action) = crate::tools::TOOLS
594 .iter()
595 .find_map(|tool| {
596 tool.actions
597 .iter()
598 .find(|action| action.op == op)
599 .map(|action| (tool.name, action.name))
600 })
601 .unwrap_or_default();
API and MCP server in Rust; a public index at the API root602 let mut described = json!({
Webhooks: every event, to your own addresses, signed and retried603 "operationId": id,
API and MCP server in Rust; a public index at the API root604 "tags": [tag(op)],
Merge branch 'worktree-agent-ab2e39e11a6493412'605 "summary": summary(route, &id),
API and MCP server in Rust; a public index at the API root606 "description": op.description(),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step607 "x-operation": op.name(),
608 "x-mcp-tool": tool,
609 "x-mcp-action": action,
610 "x-scope": scope.first().copied(),
Merge branch 'worktree-agent-ab2e39e11a6493412'611 "security": security,
API and MCP server in Rust; a public index at the API root612 "parameters": parameters,
Merge branch 'worktree-agent-ab2e39e11a6493412'613 "responses": responses,
API and MCP server in Rust; a public index at the API root614 });
615 if !body.is_null() {
616 described["requestBody"] = body;
617 }
618 described
619}
620
621/// Entries for device sign-in, which is not an operation.
622fn onboarding() -> Map<String, Value> {
623 let paths = json!({
Agents as a team: lifecycle, merge queue, billing and a new shell624 "/device/code": {
API and MCP server in Rust; a public index at the API root625 "post": {
626 "operationId": "device_code",
627 "tags": ["Accounts"],
628 "summary": "Start signing in",
Agents as a team: lifecycle, merge queue, billing and a new shell629 "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 root630 "security": [],
631 "requestBody": {
632 "content": { "application/json": { "schema": {
633 "type": "object",
634 "properties": {
635 "client_name": {
636 "type": "string",
637 "description": "What is asking, shown to the person approving. For example, Claude Code.",
638 },
639 },
640 } } },
641 },
642 "responses": { "200": {
643 "description": "The codes for this sign-in.",
644 "content": { "application/json": { "schema": {
645 "type": "object",
646 "properties": {
Agents as a team: lifecycle, merge queue, billing and a new shell647 "device_code": { "type": "string", "description": "Secret. Send it to /device/token." },
API and MCP server in Rust; a public index at the API root648 "user_code": { "type": "string", "description": "Shown to the person, like WDJB-MJHT." },
649 "verification_uri": { "type": "string" },
650 "verification_uri_complete": {
651 "type": "string",
652 "description": "The link to give the person; it carries the code.",
653 },
654 "expires_in": { "type": "integer", "description": "Seconds until the codes expire." },
655 "interval": { "type": "integer", "description": "Seconds to wait between polls." },
656 },
657 } } },
658 } },
659 },
660 },
Agents as a team: lifecycle, merge queue, billing and a new shell661 "/device/token": {
API and MCP server in Rust; a public index at the API root662 "post": {
663 "operationId": "device_token",
664 "tags": ["Accounts"],
665 "summary": "Finish signing in",
666 "description": "Asks whether the person has approved. Poll no faster than the interval. The token is returned once.",
667 "security": [],
668 "requestBody": {
669 "required": true,
670 "content": { "application/json": { "schema": {
671 "type": "object",
672 "required": ["device_code"],
673 "properties": { "device_code": { "type": "string" } },
674 } } },
675 },
676 "responses": { "200": {
677 "description": "The state of the sign-in.",
678 "content": { "application/json": { "schema": {
679 "type": "object",
680 "required": ["status"],
681 "properties": {
682 "status": { "type": "string", "enum": ["pending", "approved", "denied", "expired"] },
683 "token": { "type": "string", "description": "Present when approved." },
684 "username": { "type": "string" },
685 "verified": {
686 "type": "boolean",
687 "description": "Whether the account's email is confirmed.",
688 },
689 },
690 } } },
691 } },
692 },
693 },
694 });
695 match paths {
696 Value::Object(paths) => paths,
697 _ => Map::new(),
698 }
699}
700
Merge branch 'worktree-agent-ab2e39e11a6493412'701
702/// Puts each operation's examples, where it has them, into its request
703/// and response. Path and query values go under `x-example-params` and
704/// `x-example-query`, which tools that build a request can use.
705fn attach_examples(paths: &mut Map<String, Value>) {
706 let examples = examples();
707 for methods in paths.values_mut() {
708 let Some(methods) = methods.as_object_mut() else { continue };
709 for operation in methods.values_mut() {
710 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 step711 let name = operation["x-operation"].as_str().unwrap_or_default().to_owned();
712 let Some(example) = examples.get(&id).or_else(|| examples.get(&name)) else {
Merge branch 'worktree-agent-ab2e39e11a6493412'713 continue;
714 };
715 if let Some(notes) = example.get("notes").and_then(Value::as_str) {
716 let description = operation["description"].as_str().unwrap_or_default();
717 operation["description"] = json!(format!("{description}\n\n{notes}"));
718 }
719 if let Some(response) = example.get("response") {
720 let content = &mut operation["responses"]["200"]["content"]["application/json"];
721 if content.is_object() {
722 content["example"] = response.clone();
723 }
724 }
725 if let Some(request) = example.get("request") {
726 let content = &mut operation["requestBody"]["content"]["application/json"];
727 if content.is_object() {
728 content["example"] = request.clone();
729 }
730 }
731 for (key, extension) in [("params", "x-example-params"), ("query", "x-example-query")] {
732 if let Some(values) = example.get(key) {
733 operation[extension] = values.clone();
734 }
735 }
736 }
737 }
738}
739
API and MCP server in Rust; a public index at the API root740pub fn document() -> Value {
741 let mut paths = onboarding();
742 for route in ROUTES {
743 let entry = paths
744 .entry(openapi_path(route))
745 .or_insert_with(|| json!({}));
746 entry[route.method.to_lowercase()] = operation(route);
747 }
Merge branch 'worktree-agent-ab2e39e11a6493412'748 attach_examples(&mut paths);
749 let tags: Vec<Value> = SECTIONS
750 .iter()
751 .map(|(name, description, ops)| {
752 json!({
753 "name": name,
754 "description": description,
755 // The section's operations in reading order, by MCP tool name.
756 "x-tools": ops.iter().map(|op| op.name()).collect::<Vec<_>>(),
757 })
758 })
759 .collect();
760 let codes = ["unauthenticated", "payment_required", "forbidden", "not_found", "conflict", "invalid"];
API and MCP server in Rust; a public index at the API root761 json!({
762 "openapi": "3.1.0",
763 "info": {
764 "title": "g1t API",
765 "version": "1",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API766 "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 root767 "license": { "name": "MIT", "identifier": "MIT" },
768 },
769 "servers": [{ "url": "https://api.g1t.sh" }],
770 "security": [{ "token": [] }, {}],
Merge branch 'worktree-agent-ab2e39e11a6493412'771 "tags": tags,
API and MCP server in Rust; a public index at the API root772 "paths": paths,
773 "components": {
774 "securitySchemes": {
775 "token": {
776 "type": "http",
777 "scheme": "bearer",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step778 "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 root779 },
780 },
781 "schemas": {
782 "Error": {
783 "type": "object",
784 "required": ["error"],
785 "properties": {
786 "error": {
787 "type": "object",
788 "required": ["code", "message"],
789 "properties": {
Merge branch 'worktree-agent-ab2e39e11a6493412'790 "code": { "type": "string", "enum": codes },
API and MCP server in Rust; a public index at the API root791 "message": { "type": "string" },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step792 "needed_scope": {
793 "type": "string",
794 "description": "On a 403 for an access token without the scope the call needs: that scope, such as `issues:write`.",
795 },
API and MCP server in Rust; a public index at the API root796 },
797 },
798 },
799 },
800 },
801 },
802 })
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808
809 #[test]
810 fn every_route_is_documented_once() {
811 let document = document();
812 let mut ids = Vec::new();
813 for (_, methods) in document["paths"].as_object().unwrap() {
814 for (_, operation) in methods.as_object().unwrap() {
815 ids.push(operation["operationId"].as_str().unwrap().to_owned());
816 }
817 }
818 for op in Op::ALL {
819 assert_eq!(
820 ids.iter().filter(|id| *id == op.name()).count(),
821 1,
822 "{}",
823 op.name()
824 );
825 }
Webhooks: every event, to your own addresses, signed and retried826 let mut unique = ids.clone();
827 unique.sort();
828 unique.dedup();
829 assert_eq!(unique.len(), ids.len(), "operation ids repeat");
API and MCP server in Rust; a public index at the API root830 }
831
832 #[test]
833 fn path_and_query_inputs_are_not_repeated_in_the_body() {
834 let document = document();
Agents as a team: lifecycle, merge queue, billing and a new shell835 let merge = &document["paths"]["/repos/{owner}/{name}/pulls/{number}/merge"]["post"];
API and MCP server in Rust; a public index at the API root836 let body = &merge["requestBody"]["content"]["application/json"]["schema"]["properties"];
837 assert!(body.get("keep_issue_open").is_some());
838 assert!(body.get("repo").is_none() && body.get("number").is_none());
Agents as a team: lifecycle, merge queue, billing and a new shell839 let list = &document["paths"]["/repos"]["get"];
API and MCP server in Rust; a public index at the API root840 assert_eq!(list["parameters"][0]["name"], "q");
841 assert!(list.get("requestBody").is_none());
842 }
843
844 #[test]
Merge branch 'worktree-agent-ab2e39e11a6493412'845 fn every_operation_is_in_one_section() {
846 for op in Op::ALL {
847 let sections = SECTIONS
848 .iter()
849 .filter(|(_, _, ops)| ops.contains(&op))
850 .count();
851 assert_eq!(sections, 1, "{}", op.name());
852 }
853 }
854
855 #[test]
API and MCP server in Rust; a public index at the API root856 fn titles_read_as_sentences() {
Merge branch 'worktree-agent-ab2e39e11a6493412'857 assert_eq!(title(Op::CreateIssue), "Create an issue");
API and MCP server in Rust; a public index at the API root858 assert_eq!(title(Op::Whoami), "Get the current user");
859 }
Merge branch 'worktree-agent-ab2e39e11a6493412'860
861 #[test]
862 fn every_operation_has_an_example_response() {
863 let examples = examples();
864 assert!(!examples.is_empty(), "reference.json does not parse");
865 let document = document();
866 let mut known = Vec::new();
867 for (path, methods) in document["paths"].as_object().unwrap() {
868 for (method, operation) in methods.as_object().unwrap() {
869 known.push(operation["operationId"].as_str().unwrap().to_owned());
870 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
871 assert!(!example.is_null(), "{method} {path} has no example response");
872 }
873 }
874 for id in examples.keys() {
875 assert!(known.contains(id), "reference.json names {id}, which is not an operation");
876 }
877 }
878
879 #[test]
880 fn example_requests_send_only_what_the_body_takes() {
881 let document = document();
882 for (path, methods) in document["paths"].as_object().unwrap() {
883 for (method, operation) in methods.as_object().unwrap() {
884 let content = &operation["requestBody"]["content"]["application/json"];
885 let Some(example) = content["example"].as_object() else { continue };
886 let properties = &content["schema"]["properties"];
887 for key in example.keys() {
888 assert!(!properties[key].is_null(), "{method} {path}: {key} is not in the body");
889 }
890 }
891 }
892 }
893
894 /// The docs site's copy of the document. Run with `G1T_WRITE_OPENAPI=1`
895 /// to rewrite it after changing an operation.
896 #[test]
897 fn the_docs_copy_is_current() {
898 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../docs/src/data/openapi.json");
899 let current = serde_json::to_string_pretty(&document()).unwrap() + "\n";
900 if std::env::var_os("G1T_WRITE_OPENAPI").is_some() {
901 std::fs::write(path, &current).unwrap();
902 return;
903 }
904 let copy = std::fs::read_to_string(path).unwrap_or_default().replace("\r\n", "\n");
905 assert!(
906 copy == current,
907 "apps/docs/src/data/openapi.json is out of date: run G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi"
908 );
909 }
API reference: no example reads as a real secret910
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API911 /// The reference shows responses as they are sent: `snake_case`.
912 #[test]
913 fn example_responses_are_snake_case() {
914 let document = document();
915 for (path, methods) in document["paths"].as_object().unwrap() {
916 for (method, operation) in methods.as_object().unwrap() {
917 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
918 let leaked = g1t_kit::wire::camel_case_keys(example);
919 assert!(leaked.is_empty(), "{method} {path} shows {leaked:?}");
920 }
921 }
922 }
923
API reference: no example reads as a real secret924 /// Examples never hold anything that reads as a real credential, which
925 /// secret scanners rightly flag in a public repository: they end in `…`
926 /// after the prefix, as `whsec_…` and `g1t_…` do.
927 #[test]
928 fn examples_hold_no_real_looking_secrets() {
Fast pages, required checks on the branch, self-hosted runners, honest incidents929 let prefixes = ["whsec_", "g1t_", "g1tr_", "g1trt_", "sk_live_", "sk_test_", "ghp_", "github_pat_", "xoxb-", "AKIA"];
API reference: no example reads as a real secret930 for (line, text) in REFERENCE.lines().enumerate() {
931 for prefix in prefixes {
932 let mut rest = text;
933 while let Some(at) = rest.find(prefix) {
934 let after = &rest[at + prefix.len()..];
935 let run = after.chars().take_while(|c| c.is_ascii_alphanumeric()).count();
936 assert!(
937 run < 12,
938 "reference.json line {}: `{prefix}` followed by {run} characters reads as a real secret; write `{prefix}…`",
939 line + 1
940 );
941 rest = after;
942 }
943 }
944 }
945 }
API and MCP server in Rust; a public index at the API root946}

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