Skip to content

g1t/apps/api/src/openapi.rs

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

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