flagon-io/g1t

public

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

g1t/apps/api/src/openapi.rs

883 lines36,427 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 (
22 "Workspaces",
23 "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 daily24 &[Op::CreateWorkspace, Op::UpdateWorkspace, Op::DeleteWorkspace],
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look25 ),
26 (
27 "Invites",
28 "While g1t is invite-only, every new account needs an invite. Your invites, and inviting people into a workspace by email.",
29 &[
30 Op::ListInvites,
31 Op::CreateInvite,
32 Op::RevokeInvite,
33 Op::ListWorkspaceInvites,
34 Op::InviteMember,
35 Op::RevokeWorkspaceInvite,
36 ],
Merge branch 'worktree-agent-ab2e39e11a6493412'37 ),
38 (
39 "Repositories",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look40 "A repository, how it handles pull requests, and its timeline: renaming, archiving, moving and deleting it.",
Merge branch 'worktree-agent-ab2e39e11a6493412'41 &[
42 Op::ListRepos,
43 Op::CreateRepo,
44 Op::GetRepo,
45 Op::UpdateRepo,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look46 Op::RenameRepo,
47 Op::RenameBranch,
48 Op::SetRepoVisibility,
49 Op::ArchiveRepo,
50 Op::UnarchiveRepo,
51 Op::TransferRepo,
52 Op::DeleteRepo,
53 Op::ListDeletedRepos,
54 Op::RestoreRepo,
55 Op::PurgeRepo,
Merge branch 'worktree-agent-ab2e39e11a6493412'56 Op::GetRepoSettings,
57 Op::UpdateRepoSettings,
Fast pages, required checks on the branch, self-hosted runners, honest incidents58 Op::ListCheckNames,
Merge branch 'worktree-agent-ab2e39e11a6493412'59 Op::ListEvents,
60 ],
61 ),
62 (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look63 "Access",
64 "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.",
65 &[
66 Op::ListCollaborators,
67 Op::AddCollaborator,
68 Op::UpdateCollaborator,
69 Op::RemoveCollaborator,
70 Op::GetCollaboratorPermission,
71 Op::ListRepoInvitations,
72 Op::RevokeRepoInvitation,
73 Op::ListMyRepoInvitations,
74 Op::AcceptRepoInvitation,
75 Op::DeclineRepoInvitation,
76 Op::SetBasePermission,
77 Op::ListOutsideCollaborators,
78 ],
79 ),
80 (
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily81 "Security",
82 "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.",
83 &[Op::ListSecurityAlerts, Op::DismissSecurityAlert, Op::ReopenSecurityAlert],
84 ),
85 (
Merge branch 'worktree-agent-ab2e39e11a6493412'86 "Issues",
87 "What should change in a repository, with labels and comments. Issues and pull requests share one sequence of numbers.",
88 &[
89 Op::ListIssues,
90 Op::CreateIssue,
91 Op::GetIssue,
92 Op::UpdateIssue,
93 Op::CloseIssue,
94 Op::ReopenIssue,
95 Op::AssignIssue,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step96 Op::Delegate,
Merge branch 'worktree-agent-ab2e39e11a6493412'97 Op::AddComment,
98 Op::ListLabels,
99 ],
100 ),
101 (
102 "Plans",
103 "An outcome turned into the issues that would get there, with the order they must merge in.",
104 &[Op::PlanWork, Op::GetPlan, Op::ApplyPlan],
105 ),
106 (
107 "Pull requests",
108 "A proposed change in its own fork or on a branch. Several can be made for one issue; the one merged resolves it.",
109 &[
110 Op::ListPullRequests,
111 Op::CreatePullRequest,
112 Op::GetPullRequest,
113 Op::GetPullRequestChanges,
114 Op::MarkPullRequestReady,
115 Op::ReviewPullRequest,
116 Op::MergePullRequest,
117 Op::ClosePullRequest,
118 Op::GetMergeQueue,
119 Op::MessageAgent,
120 Op::AnswerMessage,
121 Op::TakeMessages,
122 ],
123 ),
124 (
125 "Sessions",
126 "The record of how a pull request was made: prompts, reasoning and the tools that ran.",
127 &[Op::ReadSession, Op::RecordSession],
128 ),
129 (
Agents and memory, checks and conflicts, profiles, slug renames, custom domains130 "Memory",
131 "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.",
132 &[Op::Remember, Op::Recall],
133 ),
134 (
Search across all of g1t, Explore, and a command palette135 "Search",
136 "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.",
137 &[Op::Search],
138 ),
139 (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API140 "Context",
141 "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.",
142 &[Op::SearchContext, Op::GetEntity],
143 ),
144 (
Merge branch 'worktree-agent-ab2e39e11a6493412'145 "Actions",
146 "GitHub Actions workflows in .g1t/workflows, their runs, and their jobs' logs.",
147 &[
148 Op::ListWorkflows,
149 Op::ListWorkflowRuns,
150 Op::GetWorkflowRun,
151 Op::GetJobLogs,
152 Op::DispatchWorkflow,
153 Op::CancelWorkflowRun,
154 Op::RerunWorkflowRun,
155 Op::UpdateWorkflow,
156 ],
157 ),
158 (
159 "Secrets and variables",
160 "Values that workflows and deployments read, per repository or for a whole workspace, with a row per environment.",
161 &[
162 Op::ListActionsSecrets,
163 Op::SetActionsSecret,
164 Op::DeleteActionsSecret,
165 Op::ListActionsVariables,
166 Op::SetActionsVariable,
167 Op::DeleteActionsVariable,
168 ],
169 ),
170 (
Fast pages, required checks on the branch, self-hosted runners, honest incidents171 "Runners",
172 "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.",
173 &[
174 Op::ListRunners,
175 Op::CreateRunnerRegistrationToken,
176 Op::RemoveRunner,
177 Op::ListRunnerGroups,
178 Op::CreateRunnerGroup,
179 Op::UpdateRunnerGroup,
180 Op::DeleteRunnerGroup,
181 Op::GetRunnerSettings,
182 Op::UpdateRunnerSettings,
183 ],
184 ),
185 (
Merge branch 'worktree-agent-ab2e39e11a6493412'186 "Webhooks",
187 "Signed HTTPS requests sent to your own address as things happen, for a repository or a whole workspace.",
188 &[
189 Op::ListWebhooks,
190 Op::CreateWebhook,
191 Op::UpdateWebhook,
192 Op::DeleteWebhook,
193 Op::PingWebhook,
194 Op::ListWebhookDeliveries,
195 Op::RedeliverWebhook,
196 ],
197 ),
198 (
199 "Integrations",
200 "A workspace's connections to outside systems: model providers, alert sources and issue trackers.",
201 &[
202 Op::ListIntegrations,
203 Op::ConnectIntegration,
204 Op::DisconnectIntegration,
205 Op::TestIntegration,
206 Op::GetModelRoutes,
207 Op::SetModelRoutes,
208 Op::GetContext,
209 Op::ImportIssue,
210 ],
211 ),
212];
213
API and MCP server in Rust; a public index at the API root214/// The section of the API reference an operation is listed under.
215fn tag(op: Op) -> &'static str {
Merge branch 'worktree-agent-ab2e39e11a6493412'216 SECTIONS
API and MCP server in Rust; a public index at the API root217 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'218 .find(|(_, _, ops)| ops.contains(&op))
219 .map_or("Repositories", |(name, _, _)| name)
220}
221
222/// What an operation's page is called, as a short sentence.
223fn title(op: Op) -> &'static str {
224 match op {
225 Op::Whoami => "Get the current user",
226 Op::CreateWorkspace => "Create a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look227 Op::DeleteWorkspace => "Delete a workspace",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily228 Op::UpdateWorkspace => "Update a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look229 Op::ListEmails => "List your email addresses",
230 Op::AddEmail => "Add an email address",
231 Op::RemoveEmail => "Remove an email address",
232 Op::UpdateEmailSettings => "Change your email settings",
233 Op::ListInvites => "List your invites",
234 Op::CreateInvite => "Create an invite",
235 Op::RevokeInvite => "Revoke an invite",
236 Op::ListWorkspaceInvites => "List a workspace's invites",
237 Op::InviteMember => "Invite someone to a workspace",
238 Op::RevokeWorkspaceInvite => "Revoke a workspace's invite",
239 Op::TransferRepo => "Transfer a repository",
240 Op::RenameRepo => "Rename a repository",
241 Op::RenameBranch => "Rename a branch",
242 Op::ArchiveRepo => "Archive a repository",
243 Op::UnarchiveRepo => "Unarchive a repository",
244 Op::SetRepoVisibility => "Change a repository's visibility",
245 Op::DeleteRepo => "Delete a repository",
246 Op::ListDeletedRepos => "List recently deleted repositories",
247 Op::RestoreRepo => "Restore a deleted repository",
248 Op::PurgeRepo => "Purge a deleted repository",
Merge branch 'worktree-agent-ab2e39e11a6493412'249 Op::ListRepos => "List repositories",
250 Op::GetRepo => "Get a repository",
251 Op::CreateRepo => "Create a repository",
252 Op::UpdateRepo => "Update a repository",
253 Op::GetRepoSettings => "Get repository settings",
254 Op::UpdateRepoSettings => "Update repository settings",
Fast pages, required checks on the branch, self-hosted runners, honest incidents255 Op::ListCheckNames => "List check names",
Merge branch 'worktree-agent-ab2e39e11a6493412'256 Op::GetMergeQueue => "Get the merge queue",
257 Op::MessageAgent => "Message an agent",
258 Op::AnswerMessage => "Answer a message",
259 Op::TakeMessages => "Take new messages",
Agents and memory, checks and conflicts, profiles, slug renames, custom domains260 Op::Remember => "Remember something",
261 Op::Recall => "Recall memory",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API262 Op::SearchContext => "Search the context hub",
263 Op::GetEntity => "Get a catalog entry",
Search across all of g1t, Explore, and a command palette264 Op::Search => "Search g1t",
Merge branch 'worktree-agent-ab2e39e11a6493412'265 Op::ListIssues => "List issues",
266 Op::GetIssue => "Get an issue",
267 Op::CreateIssue => "Create an issue",
268 Op::UpdateIssue => "Update an issue",
269 Op::CloseIssue => "Close an issue",
270 Op::ReopenIssue => "Reopen an issue",
271 Op::AssignIssue => "Assign an issue to the g1t agent",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step272 Op::Delegate => "Put an agent on it",
Merge branch 'worktree-agent-ab2e39e11a6493412'273 Op::PlanWork => "Plan work",
274 Op::GetPlan => "Get a plan",
275 Op::ApplyPlan => "Apply a plan",
276 Op::ListLabels => "List labels",
277 Op::AddComment => "Add a comment",
278 Op::ReviewPullRequest => "Review a pull request",
279 Op::ListPullRequests => "List pull requests",
280 Op::GetPullRequest => "Get a pull request",
281 Op::CreatePullRequest => "Create a pull request",
282 Op::RecordSession => "Record session entries",
283 Op::ReadSession => "Read a session",
284 Op::MarkPullRequestReady => "Mark a pull request ready",
285 Op::ClosePullRequest => "Close a pull request",
286 Op::GetPullRequestChanges => "Get a pull request's changes",
287 Op::MergePullRequest => "Merge a pull request",
288 Op::ListEvents => "List repository events",
289 Op::ListIntegrations => "List integrations",
290 Op::ConnectIntegration => "Connect an integration",
291 Op::DisconnectIntegration => "Disconnect an integration",
292 Op::TestIntegration => "Test an integration",
293 Op::GetContext => "Look up a ticket",
294 Op::ImportIssue => "Import an issue",
295 Op::GetModelRoutes => "Get model routes",
296 Op::SetModelRoutes => "Set model routes",
297 Op::ListWebhooks => "List webhooks",
298 Op::CreateWebhook => "Create a webhook",
299 Op::UpdateWebhook => "Update a webhook",
300 Op::DeleteWebhook => "Delete a webhook",
301 Op::PingWebhook => "Ping a webhook",
302 Op::ListWebhookDeliveries => "List webhook deliveries",
303 Op::RedeliverWebhook => "Redeliver a webhook delivery",
304 Op::ListWorkflows => "List workflows",
305 Op::ListWorkflowRuns => "List workflow runs",
306 Op::GetWorkflowRun => "Get a workflow run",
307 Op::GetJobLogs => "Get a job's log",
308 Op::DispatchWorkflow => "Run a workflow",
309 Op::CancelWorkflowRun => "Cancel a workflow run",
310 Op::RerunWorkflowRun => "Re-run a workflow run",
311 Op::UpdateWorkflow => "Turn a workflow on or off",
312 Op::ListActionsSecrets => "List secrets",
313 Op::SetActionsSecret => "Set a secret",
314 Op::DeleteActionsSecret => "Delete a secret",
315 Op::ListActionsVariables => "List variables",
316 Op::SetActionsVariable => "Set a variable",
317 Op::DeleteActionsVariable => "Delete a variable",
Fast pages, required checks on the branch, self-hosted runners, honest incidents318 Op::ListRunners => "List self-hosted runners",
319 Op::ListRunnerGroups => "List runner groups",
320 Op::GetRunnerSettings => "Get runner settings",
321 Op::CreateRunnerRegistrationToken => "Create a runner registration token",
322 Op::RemoveRunner => "Remove a self-hosted runner",
323 Op::CreateRunnerGroup => "Create a runner group",
324 Op::UpdateRunnerGroup => "Change a runner group",
325 Op::DeleteRunnerGroup => "Delete a runner group",
326 Op::UpdateRunnerSettings => "Change runner settings",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look327 Op::ListCollaborators => "List who has access",
328 Op::AddCollaborator => "Add a collaborator",
329 Op::UpdateCollaborator => "Change a collaborator's role",
330 Op::RemoveCollaborator => "Remove a collaborator",
331 Op::GetCollaboratorPermission => "Get someone's permission",
332 Op::ListRepoInvitations => "List a repository's invitations",
333 Op::RevokeRepoInvitation => "Revoke a repository invitation",
334 Op::ListMyRepoInvitations => "List your repository invitations",
335 Op::AcceptRepoInvitation => "Accept a repository invitation",
336 Op::DeclineRepoInvitation => "Decline a repository invitation",
337 Op::SetBasePermission => "Set the base permission",
338 Op::ListOutsideCollaborators => "List outside collaborators",
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily339 Op::ListSecurityAlerts => "List security alerts",
340 Op::DismissSecurityAlert => "Dismiss a security alert",
341 Op::ReopenSecurityAlert => "Reopen a security alert",
API and MCP server in Rust; a public index at the API root342 }
343}
344
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look345/// Why an operation can be refused with `402 payment_required`, if it
346/// can: the ones that start an agent, when the workspace has no credit,
347/// and the ones that make a repository private in a workspace, when a free
348/// workspace's private storage has no room for it.
349fn may_need_payment(op: Op) -> Option<&'static str> {
350 match op {
351 Op::AssignIssue | Op::PlanWork | Op::ApplyPlan => Some("The workspace has no agent credit."),
352 Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo => Some(
353 "A free workspace's private storage has no room for this private repository.",
354 ),
355 _ => None,
356 }
Merge branch 'worktree-agent-ab2e39e11a6493412'357}
358
359/// What the reference says beyond each operation's own description, keyed
360/// by operation id, written by hand from what the services return: `notes`
361/// (Markdown, added to the description) and example `params` (path),
362/// `query`, `request` (body) and `response`.
363const REFERENCE: &str = include_str!("reference.json");
364
365fn examples() -> Map<String, Value> {
366 match serde_json::from_str(REFERENCE) {
367 Ok(Value::Object(examples)) => examples,
368 _ => Map::new(),
API and MCP server in Rust; a public index at the API root369 }
370}
371
Agents as a team: lifecycle, merge queue, billing and a new shell372/// `/repos/:owner/:name` as OpenAPI writes it: `/repos/{owner}/{name}`.
API and MCP server in Rust; a public index at the API root373fn openapi_path(route: &Route) -> String {
374 route
375 .path
376 .split('/')
377 .map(|segment| match segment.strip_prefix(':') {
378 Some(name) => format!("{{{name}}}"),
379 None => segment.to_owned(),
380 })
381 .collect::<Vec<_>>()
382 .join("/")
383}
384
385fn error_response(description: &str) -> Value {
386 json!({
387 "description": description,
388 "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } },
389 })
390}
391
Merge branch 'worktree-agent-ab2e39e11a6493412'392/// A parameter in the path or the query, described by the operation's
393/// input schema where it has the same name.
394fn parameter(name: &str, place: &str, required: bool, schema: Option<&Value>) -> Value {
395 let mut schema = schema.cloned().unwrap_or_else(|| json!({ "type": "string" }));
396 let description = match name {
397 "owner" => Some(Value::from("The workspace that owns the repository.")),
398 "name" => Some(Value::from("The repository's name.")),
399 _ => schema.as_object_mut().and_then(|schema| schema.remove("description")),
400 };
401 let mut parameter = json!({
402 "name": name,
403 "in": place,
404 "required": required,
405 "schema": schema,
406 });
407 if let Some(description) = description {
408 parameter["description"] = description;
409 }
410 parameter
411}
412
413/// The operation id of a route. An operation reached at a workspace's
414/// address as well as a repository's is documented once for each, with its
415/// own id; GitHub's alternative addresses for one operation keep GitHub's
416/// names.
417fn operation_id(route: &Route) -> String {
418 let op = route.op;
419 let base = match (route.method, route.path.rsplit('/').next().unwrap_or_default()) {
420 ("PUT", "enable") => "enable_workflow".to_owned(),
421 ("PUT", "disable") => "disable_workflow".to_owned(),
422 ("POST", "rerun-failed-jobs") => "rerun_failed_jobs".to_owned(),
423 ("PATCH", ":setting") => "update_actions_variable".to_owned(),
424 ("GET", "runs") if route.path.contains("/workflows/:workflow/") => "list_runs_of_workflow".to_owned(),
425 _ => op.name().to_owned(),
426 };
427 if route.path.starts_with("/workspaces/") && ROUTES.iter().any(|other| other.op == op && other.path.starts_with("/repos/")) {
428 format!("{base}_for_workspace")
429 } else {
430 base
431 }
432}
433
434/// The summary of a route: its operation's title, or for one of GitHub's
435/// alternative addresses, what that address does.
436fn summary(route: &Route, id: &str) -> String {
437 let base = match id.trim_end_matches("_for_workspace") {
438 "enable_workflow" => "Turn a workflow on",
439 "disable_workflow" => "Turn a workflow off",
440 "rerun_failed_jobs" => "Re-run failed jobs",
441 "update_actions_variable" => "Update a variable",
442 "list_runs_of_workflow" => "List a workflow's runs",
443 _ => title(route.op),
444 };
445 if id.ends_with("_for_workspace") {
446 format!("{base} for a workspace")
447 } else {
448 base.to_owned()
449 }
450}
451
API and MCP server in Rust; a public index at the API root452fn operation(route: &Route) -> Value {
453 let op = route.op;
454 let path_params: Vec<&str> = route.params().collect();
455 // `owner` and `name` in the path stand for the operation's `repo` input.
456 let covered = |name: &str| name == "repo" || path_params.contains(&name);
Merge branch 'worktree-agent-ab2e39e11a6493412'457 let all_properties = op.properties();
458 let mut properties = all_properties.clone();
API and MCP server in Rust; a public index at the API root459 properties.retain(|name, _| !covered(name));
460 let required: Vec<String> = op
461 .required()
462 .into_iter()
463 .filter(|name| !covered(name))
464 .collect();
465
466 let mut parameters: Vec<Value> = path_params
467 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'468 .map(|name| parameter(name, "path", true, all_properties.get(*name)))
API and MCP server in Rust; a public index at the API root469 .collect();
470 let mut body = Value::Null;
471 if route.method == "GET" {
472 for (name, key) in route.query {
Merge branch 'worktree-agent-ab2e39e11a6493412'473 parameters.push(parameter(
474 name,
475 "query",
476 required.iter().any(|required| required == key),
477 properties.get(*key),
478 ));
API and MCP server in Rust; a public index at the API root479 }
480 } else if !properties.is_empty() {
481 let mut schema = json!({ "type": "object", "properties": properties });
482 if !required.is_empty() {
483 schema["required"] = json!(required);
484 }
485 body = json!({
486 "required": !required.is_empty(),
487 "content": { "application/json": { "schema": schema } },
488 });
489 }
490
Merge branch 'worktree-agent-ab2e39e11a6493412'491 let id = operation_id(route);
492 let mut responses = Map::new();
493 responses.insert(
494 "200".into(),
495 json!({
496 "description": "Success.",
497 "content": { "application/json": { "schema": {} } },
498 }),
499 );
500 responses.insert(
501 "401".into(),
502 error_response("A token is required, or the one sent is not valid."),
503 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look504 if let Some(reason) = may_need_payment(op) {
505 responses.insert("402".into(), error_response(reason));
Merge branch 'worktree-agent-ab2e39e11a6493412'506 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step507 responses.insert(
508 "403".into(),
509 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."),
510 );
Search across all of g1t, Explore, and a command palette511 if !matches!(op, Op::Whoami | Op::ListRepos | Op::Search) {
Merge branch 'worktree-agent-ab2e39e11a6493412'512 responses.insert("404".into(), error_response("It does not exist, or you cannot see it."));
513 }
514 if route.method != "GET" {
515 responses.insert(
516 "409".into(),
517 error_response("The request conflicts with the current state."),
518 );
519 }
520 if op != Op::Whoami {
521 responses.insert("422".into(), error_response("The input is not valid."));
522 }
523 // 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 step524 let scope: Vec<&str> = scope_for(op.name()).map(|scope| scope.as_str()).into_iter().collect();
Merge branch 'worktree-agent-ab2e39e11a6493412'525 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 step526 json!([{ "token": scope }])
Webhooks: every event, to your own addresses, signed and retried527 } else {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step528 json!([{ "token": scope }, {}])
Webhooks: every event, to your own addresses, signed and retried529 };
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step530 let (tool, action) = crate::tools::TOOLS
531 .iter()
532 .find_map(|tool| {
533 tool.actions
534 .iter()
535 .find(|action| action.op == op)
536 .map(|action| (tool.name, action.name))
537 })
538 .unwrap_or_default();
API and MCP server in Rust; a public index at the API root539 let mut described = json!({
Webhooks: every event, to your own addresses, signed and retried540 "operationId": id,
API and MCP server in Rust; a public index at the API root541 "tags": [tag(op)],
Merge branch 'worktree-agent-ab2e39e11a6493412'542 "summary": summary(route, &id),
API and MCP server in Rust; a public index at the API root543 "description": op.description(),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step544 "x-operation": op.name(),
545 "x-mcp-tool": tool,
546 "x-mcp-action": action,
547 "x-scope": scope.first().copied(),
Merge branch 'worktree-agent-ab2e39e11a6493412'548 "security": security,
API and MCP server in Rust; a public index at the API root549 "parameters": parameters,
Merge branch 'worktree-agent-ab2e39e11a6493412'550 "responses": responses,
API and MCP server in Rust; a public index at the API root551 });
552 if !body.is_null() {
553 described["requestBody"] = body;
554 }
555 described
556}
557
558/// Entries for device sign-in, which is not an operation.
559fn onboarding() -> Map<String, Value> {
560 let paths = json!({
Agents as a team: lifecycle, merge queue, billing and a new shell561 "/device/code": {
API and MCP server in Rust; a public index at the API root562 "post": {
563 "operationId": "device_code",
564 "tags": ["Accounts"],
565 "summary": "Start signing in",
Agents as a team: lifecycle, merge queue, billing and a new shell566 "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 root567 "security": [],
568 "requestBody": {
569 "content": { "application/json": { "schema": {
570 "type": "object",
571 "properties": {
572 "client_name": {
573 "type": "string",
574 "description": "What is asking, shown to the person approving. For example, Claude Code.",
575 },
576 },
577 } } },
578 },
579 "responses": { "200": {
580 "description": "The codes for this sign-in.",
581 "content": { "application/json": { "schema": {
582 "type": "object",
583 "properties": {
Agents as a team: lifecycle, merge queue, billing and a new shell584 "device_code": { "type": "string", "description": "Secret. Send it to /device/token." },
API and MCP server in Rust; a public index at the API root585 "user_code": { "type": "string", "description": "Shown to the person, like WDJB-MJHT." },
586 "verification_uri": { "type": "string" },
587 "verification_uri_complete": {
588 "type": "string",
589 "description": "The link to give the person; it carries the code.",
590 },
591 "expires_in": { "type": "integer", "description": "Seconds until the codes expire." },
592 "interval": { "type": "integer", "description": "Seconds to wait between polls." },
593 },
594 } } },
595 } },
596 },
597 },
Agents as a team: lifecycle, merge queue, billing and a new shell598 "/device/token": {
API and MCP server in Rust; a public index at the API root599 "post": {
600 "operationId": "device_token",
601 "tags": ["Accounts"],
602 "summary": "Finish signing in",
603 "description": "Asks whether the person has approved. Poll no faster than the interval. The token is returned once.",
604 "security": [],
605 "requestBody": {
606 "required": true,
607 "content": { "application/json": { "schema": {
608 "type": "object",
609 "required": ["device_code"],
610 "properties": { "device_code": { "type": "string" } },
611 } } },
612 },
613 "responses": { "200": {
614 "description": "The state of the sign-in.",
615 "content": { "application/json": { "schema": {
616 "type": "object",
617 "required": ["status"],
618 "properties": {
619 "status": { "type": "string", "enum": ["pending", "approved", "denied", "expired"] },
620 "token": { "type": "string", "description": "Present when approved." },
621 "username": { "type": "string" },
622 "verified": {
623 "type": "boolean",
624 "description": "Whether the account's email is confirmed.",
625 },
626 },
627 } } },
628 } },
629 },
630 },
631 });
632 match paths {
633 Value::Object(paths) => paths,
634 _ => Map::new(),
635 }
636}
637
Merge branch 'worktree-agent-ab2e39e11a6493412'638
639/// Puts each operation's examples, where it has them, into its request
640/// and response. Path and query values go under `x-example-params` and
641/// `x-example-query`, which tools that build a request can use.
642fn attach_examples(paths: &mut Map<String, Value>) {
643 let examples = examples();
644 for methods in paths.values_mut() {
645 let Some(methods) = methods.as_object_mut() else { continue };
646 for operation in methods.values_mut() {
647 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 step648 let name = operation["x-operation"].as_str().unwrap_or_default().to_owned();
649 let Some(example) = examples.get(&id).or_else(|| examples.get(&name)) else {
Merge branch 'worktree-agent-ab2e39e11a6493412'650 continue;
651 };
652 if let Some(notes) = example.get("notes").and_then(Value::as_str) {
653 let description = operation["description"].as_str().unwrap_or_default();
654 operation["description"] = json!(format!("{description}\n\n{notes}"));
655 }
656 if let Some(response) = example.get("response") {
657 let content = &mut operation["responses"]["200"]["content"]["application/json"];
658 if content.is_object() {
659 content["example"] = response.clone();
660 }
661 }
662 if let Some(request) = example.get("request") {
663 let content = &mut operation["requestBody"]["content"]["application/json"];
664 if content.is_object() {
665 content["example"] = request.clone();
666 }
667 }
668 for (key, extension) in [("params", "x-example-params"), ("query", "x-example-query")] {
669 if let Some(values) = example.get(key) {
670 operation[extension] = values.clone();
671 }
672 }
673 }
674 }
675}
676
API and MCP server in Rust; a public index at the API root677pub fn document() -> Value {
678 let mut paths = onboarding();
679 for route in ROUTES {
680 let entry = paths
681 .entry(openapi_path(route))
682 .or_insert_with(|| json!({}));
683 entry[route.method.to_lowercase()] = operation(route);
684 }
Merge branch 'worktree-agent-ab2e39e11a6493412'685 attach_examples(&mut paths);
686 let tags: Vec<Value> = SECTIONS
687 .iter()
688 .map(|(name, description, ops)| {
689 json!({
690 "name": name,
691 "description": description,
692 // The section's operations in reading order, by MCP tool name.
693 "x-tools": ops.iter().map(|op| op.name()).collect::<Vec<_>>(),
694 })
695 })
696 .collect();
697 let codes = ["unauthenticated", "payment_required", "forbidden", "not_found", "conflict", "invalid"];
API and MCP server in Rust; a public index at the API root698 json!({
699 "openapi": "3.1.0",
700 "info": {
701 "title": "g1t API",
702 "version": "1",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API703 "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 root704 "license": { "name": "MIT", "identifier": "MIT" },
705 },
706 "servers": [{ "url": "https://api.g1t.sh" }],
707 "security": [{ "token": [] }, {}],
Merge branch 'worktree-agent-ab2e39e11a6493412'708 "tags": tags,
API and MCP server in Rust; a public index at the API root709 "paths": paths,
710 "components": {
711 "securitySchemes": {
712 "token": {
713 "type": "http",
714 "scheme": "bearer",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step715 "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 root716 },
717 },
718 "schemas": {
719 "Error": {
720 "type": "object",
721 "required": ["error"],
722 "properties": {
723 "error": {
724 "type": "object",
725 "required": ["code", "message"],
726 "properties": {
Merge branch 'worktree-agent-ab2e39e11a6493412'727 "code": { "type": "string", "enum": codes },
API and MCP server in Rust; a public index at the API root728 "message": { "type": "string" },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step729 "needed_scope": {
730 "type": "string",
731 "description": "On a 403 for an access token without the scope the call needs: that scope, such as `issues:write`.",
732 },
API and MCP server in Rust; a public index at the API root733 },
734 },
735 },
736 },
737 },
738 },
739 })
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn every_route_is_documented_once() {
748 let document = document();
749 let mut ids = Vec::new();
750 for (_, methods) in document["paths"].as_object().unwrap() {
751 for (_, operation) in methods.as_object().unwrap() {
752 ids.push(operation["operationId"].as_str().unwrap().to_owned());
753 }
754 }
755 for op in Op::ALL {
756 assert_eq!(
757 ids.iter().filter(|id| *id == op.name()).count(),
758 1,
759 "{}",
760 op.name()
761 );
762 }
Webhooks: every event, to your own addresses, signed and retried763 let mut unique = ids.clone();
764 unique.sort();
765 unique.dedup();
766 assert_eq!(unique.len(), ids.len(), "operation ids repeat");
API and MCP server in Rust; a public index at the API root767 }
768
769 #[test]
770 fn path_and_query_inputs_are_not_repeated_in_the_body() {
771 let document = document();
Agents as a team: lifecycle, merge queue, billing and a new shell772 let merge = &document["paths"]["/repos/{owner}/{name}/pulls/{number}/merge"]["post"];
API and MCP server in Rust; a public index at the API root773 let body = &merge["requestBody"]["content"]["application/json"]["schema"]["properties"];
774 assert!(body.get("keep_issue_open").is_some());
775 assert!(body.get("repo").is_none() && body.get("number").is_none());
Agents as a team: lifecycle, merge queue, billing and a new shell776 let list = &document["paths"]["/repos"]["get"];
API and MCP server in Rust; a public index at the API root777 assert_eq!(list["parameters"][0]["name"], "q");
778 assert!(list.get("requestBody").is_none());
779 }
780
781 #[test]
Merge branch 'worktree-agent-ab2e39e11a6493412'782 fn every_operation_is_in_one_section() {
783 for op in Op::ALL {
784 let sections = SECTIONS
785 .iter()
786 .filter(|(_, _, ops)| ops.contains(&op))
787 .count();
788 assert_eq!(sections, 1, "{}", op.name());
789 }
790 }
791
792 #[test]
API and MCP server in Rust; a public index at the API root793 fn titles_read_as_sentences() {
Merge branch 'worktree-agent-ab2e39e11a6493412'794 assert_eq!(title(Op::CreateIssue), "Create an issue");
API and MCP server in Rust; a public index at the API root795 assert_eq!(title(Op::Whoami), "Get the current user");
796 }
Merge branch 'worktree-agent-ab2e39e11a6493412'797
798 #[test]
799 fn every_operation_has_an_example_response() {
800 let examples = examples();
801 assert!(!examples.is_empty(), "reference.json does not parse");
802 let document = document();
803 let mut known = Vec::new();
804 for (path, methods) in document["paths"].as_object().unwrap() {
805 for (method, operation) in methods.as_object().unwrap() {
806 known.push(operation["operationId"].as_str().unwrap().to_owned());
807 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
808 assert!(!example.is_null(), "{method} {path} has no example response");
809 }
810 }
811 for id in examples.keys() {
812 assert!(known.contains(id), "reference.json names {id}, which is not an operation");
813 }
814 }
815
816 #[test]
817 fn example_requests_send_only_what_the_body_takes() {
818 let document = document();
819 for (path, methods) in document["paths"].as_object().unwrap() {
820 for (method, operation) in methods.as_object().unwrap() {
821 let content = &operation["requestBody"]["content"]["application/json"];
822 let Some(example) = content["example"].as_object() else { continue };
823 let properties = &content["schema"]["properties"];
824 for key in example.keys() {
825 assert!(!properties[key].is_null(), "{method} {path}: {key} is not in the body");
826 }
827 }
828 }
829 }
830
831 /// The docs site's copy of the document. Run with `G1T_WRITE_OPENAPI=1`
832 /// to rewrite it after changing an operation.
833 #[test]
834 fn the_docs_copy_is_current() {
835 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../docs/src/data/openapi.json");
836 let current = serde_json::to_string_pretty(&document()).unwrap() + "\n";
837 if std::env::var_os("G1T_WRITE_OPENAPI").is_some() {
838 std::fs::write(path, &current).unwrap();
839 return;
840 }
841 let copy = std::fs::read_to_string(path).unwrap_or_default().replace("\r\n", "\n");
842 assert!(
843 copy == current,
844 "apps/docs/src/data/openapi.json is out of date: run G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi"
845 );
846 }
API reference: no example reads as a real secret847
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API848 /// The reference shows responses as they are sent: `snake_case`.
849 #[test]
850 fn example_responses_are_snake_case() {
851 let document = document();
852 for (path, methods) in document["paths"].as_object().unwrap() {
853 for (method, operation) in methods.as_object().unwrap() {
854 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
855 let leaked = g1t_kit::wire::camel_case_keys(example);
856 assert!(leaked.is_empty(), "{method} {path} shows {leaked:?}");
857 }
858 }
859 }
860
API reference: no example reads as a real secret861 /// Examples never hold anything that reads as a real credential, which
862 /// secret scanners rightly flag in a public repository: they end in `…`
863 /// after the prefix, as `whsec_…` and `g1t_…` do.
864 #[test]
865 fn examples_hold_no_real_looking_secrets() {
Fast pages, required checks on the branch, self-hosted runners, honest incidents866 let prefixes = ["whsec_", "g1t_", "g1tr_", "g1trt_", "sk_live_", "sk_test_", "ghp_", "github_pat_", "xoxb-", "AKIA"];
API reference: no example reads as a real secret867 for (line, text) in REFERENCE.lines().enumerate() {
868 for prefix in prefixes {
869 let mut rest = text;
870 while let Some(at) = rest.find(prefix) {
871 let after = &rest[at + prefix.len()..];
872 let run = after.chars().take_while(|c| c.is_ascii_alphanumeric()).count();
873 assert!(
874 run < 12,
875 "reference.json line {}: `{prefix}` followed by {run} characters reads as a real secret; write `{prefix}…`",
876 line + 1
877 );
878 rest = after;
879 }
880 }
881 }
882 }
API and MCP server in Rust; a public index at the API root883}

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