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

874 lines35,877 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.",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look24 &[Op::CreateWorkspace, Op::DeleteWorkspace],
25 ),
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 (
Merge branch 'worktree-agent-ab2e39e11a6493412'81 "Issues",
82 "What should change in a repository, with labels and comments. Issues and pull requests share one sequence of numbers.",
83 &[
84 Op::ListIssues,
85 Op::CreateIssue,
86 Op::GetIssue,
87 Op::UpdateIssue,
88 Op::CloseIssue,
89 Op::ReopenIssue,
90 Op::AssignIssue,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step91 Op::Delegate,
Merge branch 'worktree-agent-ab2e39e11a6493412'92 Op::AddComment,
93 Op::ListLabels,
94 ],
95 ),
96 (
97 "Plans",
98 "An outcome turned into the issues that would get there, with the order they must merge in.",
99 &[Op::PlanWork, Op::GetPlan, Op::ApplyPlan],
100 ),
101 (
102 "Pull requests",
103 "A proposed change in its own fork or on a branch. Several can be made for one issue; the one merged resolves it.",
104 &[
105 Op::ListPullRequests,
106 Op::CreatePullRequest,
107 Op::GetPullRequest,
108 Op::GetPullRequestChanges,
109 Op::MarkPullRequestReady,
110 Op::ReviewPullRequest,
111 Op::MergePullRequest,
112 Op::ClosePullRequest,
113 Op::GetMergeQueue,
114 Op::MessageAgent,
115 Op::AnswerMessage,
116 Op::TakeMessages,
117 ],
118 ),
119 (
120 "Sessions",
121 "The record of how a pull request was made: prompts, reasoning and the tools that ran.",
122 &[Op::ReadSession, Op::RecordSession],
123 ),
124 (
Agents and memory, checks and conflicts, profiles, slug renames, custom domains125 "Memory",
126 "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.",
127 &[Op::Remember, Op::Recall],
128 ),
129 (
Search across all of g1t, Explore, and a command palette130 "Search",
131 "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.",
132 &[Op::Search],
133 ),
134 (
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API135 "Context",
136 "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.",
137 &[Op::SearchContext, Op::GetEntity],
138 ),
139 (
Merge branch 'worktree-agent-ab2e39e11a6493412'140 "Actions",
141 "GitHub Actions workflows in .g1t/workflows, their runs, and their jobs' logs.",
142 &[
143 Op::ListWorkflows,
144 Op::ListWorkflowRuns,
145 Op::GetWorkflowRun,
146 Op::GetJobLogs,
147 Op::DispatchWorkflow,
148 Op::CancelWorkflowRun,
149 Op::RerunWorkflowRun,
150 Op::UpdateWorkflow,
151 ],
152 ),
153 (
154 "Secrets and variables",
155 "Values that workflows and deployments read, per repository or for a whole workspace, with a row per environment.",
156 &[
157 Op::ListActionsSecrets,
158 Op::SetActionsSecret,
159 Op::DeleteActionsSecret,
160 Op::ListActionsVariables,
161 Op::SetActionsVariable,
162 Op::DeleteActionsVariable,
163 ],
164 ),
165 (
Fast pages, required checks on the branch, self-hosted runners, honest incidents166 "Runners",
167 "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.",
168 &[
169 Op::ListRunners,
170 Op::CreateRunnerRegistrationToken,
171 Op::RemoveRunner,
172 Op::ListRunnerGroups,
173 Op::CreateRunnerGroup,
174 Op::UpdateRunnerGroup,
175 Op::DeleteRunnerGroup,
176 Op::GetRunnerSettings,
177 Op::UpdateRunnerSettings,
178 ],
179 ),
180 (
Merge branch 'worktree-agent-ab2e39e11a6493412'181 "Webhooks",
182 "Signed HTTPS requests sent to your own address as things happen, for a repository or a whole workspace.",
183 &[
184 Op::ListWebhooks,
185 Op::CreateWebhook,
186 Op::UpdateWebhook,
187 Op::DeleteWebhook,
188 Op::PingWebhook,
189 Op::ListWebhookDeliveries,
190 Op::RedeliverWebhook,
191 ],
192 ),
193 (
194 "Integrations",
195 "A workspace's connections to outside systems: model providers, alert sources and issue trackers.",
196 &[
197 Op::ListIntegrations,
198 Op::ConnectIntegration,
199 Op::DisconnectIntegration,
200 Op::TestIntegration,
201 Op::GetModelRoutes,
202 Op::SetModelRoutes,
203 Op::GetContext,
204 Op::ImportIssue,
205 ],
206 ),
207];
208
API and MCP server in Rust; a public index at the API root209/// The section of the API reference an operation is listed under.
210fn tag(op: Op) -> &'static str {
Merge branch 'worktree-agent-ab2e39e11a6493412'211 SECTIONS
API and MCP server in Rust; a public index at the API root212 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'213 .find(|(_, _, ops)| ops.contains(&op))
214 .map_or("Repositories", |(name, _, _)| name)
215}
216
217/// What an operation's page is called, as a short sentence.
218fn title(op: Op) -> &'static str {
219 match op {
220 Op::Whoami => "Get the current user",
221 Op::CreateWorkspace => "Create a workspace",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look222 Op::DeleteWorkspace => "Delete a workspace",
223 Op::ListEmails => "List your email addresses",
224 Op::AddEmail => "Add an email address",
225 Op::RemoveEmail => "Remove an email address",
226 Op::UpdateEmailSettings => "Change your email settings",
227 Op::ListInvites => "List your invites",
228 Op::CreateInvite => "Create an invite",
229 Op::RevokeInvite => "Revoke an invite",
230 Op::ListWorkspaceInvites => "List a workspace's invites",
231 Op::InviteMember => "Invite someone to a workspace",
232 Op::RevokeWorkspaceInvite => "Revoke a workspace's invite",
233 Op::TransferRepo => "Transfer a repository",
234 Op::RenameRepo => "Rename a repository",
235 Op::RenameBranch => "Rename a branch",
236 Op::ArchiveRepo => "Archive a repository",
237 Op::UnarchiveRepo => "Unarchive a repository",
238 Op::SetRepoVisibility => "Change a repository's visibility",
239 Op::DeleteRepo => "Delete a repository",
240 Op::ListDeletedRepos => "List recently deleted repositories",
241 Op::RestoreRepo => "Restore a deleted repository",
242 Op::PurgeRepo => "Purge a deleted repository",
Merge branch 'worktree-agent-ab2e39e11a6493412'243 Op::ListRepos => "List repositories",
244 Op::GetRepo => "Get a repository",
245 Op::CreateRepo => "Create a repository",
246 Op::UpdateRepo => "Update a repository",
247 Op::GetRepoSettings => "Get repository settings",
248 Op::UpdateRepoSettings => "Update repository settings",
Fast pages, required checks on the branch, self-hosted runners, honest incidents249 Op::ListCheckNames => "List check names",
Merge branch 'worktree-agent-ab2e39e11a6493412'250 Op::GetMergeQueue => "Get the merge queue",
251 Op::MessageAgent => "Message an agent",
252 Op::AnswerMessage => "Answer a message",
253 Op::TakeMessages => "Take new messages",
Agents and memory, checks and conflicts, profiles, slug renames, custom domains254 Op::Remember => "Remember something",
255 Op::Recall => "Recall memory",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API256 Op::SearchContext => "Search the context hub",
257 Op::GetEntity => "Get a catalog entry",
Search across all of g1t, Explore, and a command palette258 Op::Search => "Search g1t",
Merge branch 'worktree-agent-ab2e39e11a6493412'259 Op::ListIssues => "List issues",
260 Op::GetIssue => "Get an issue",
261 Op::CreateIssue => "Create an issue",
262 Op::UpdateIssue => "Update an issue",
263 Op::CloseIssue => "Close an issue",
264 Op::ReopenIssue => "Reopen an issue",
265 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 step266 Op::Delegate => "Put an agent on it",
Merge branch 'worktree-agent-ab2e39e11a6493412'267 Op::PlanWork => "Plan work",
268 Op::GetPlan => "Get a plan",
269 Op::ApplyPlan => "Apply a plan",
270 Op::ListLabels => "List labels",
271 Op::AddComment => "Add a comment",
272 Op::ReviewPullRequest => "Review a pull request",
273 Op::ListPullRequests => "List pull requests",
274 Op::GetPullRequest => "Get a pull request",
275 Op::CreatePullRequest => "Create a pull request",
276 Op::RecordSession => "Record session entries",
277 Op::ReadSession => "Read a session",
278 Op::MarkPullRequestReady => "Mark a pull request ready",
279 Op::ClosePullRequest => "Close a pull request",
280 Op::GetPullRequestChanges => "Get a pull request's changes",
281 Op::MergePullRequest => "Merge a pull request",
282 Op::ListEvents => "List repository events",
283 Op::ListIntegrations => "List integrations",
284 Op::ConnectIntegration => "Connect an integration",
285 Op::DisconnectIntegration => "Disconnect an integration",
286 Op::TestIntegration => "Test an integration",
287 Op::GetContext => "Look up a ticket",
288 Op::ImportIssue => "Import an issue",
289 Op::GetModelRoutes => "Get model routes",
290 Op::SetModelRoutes => "Set model routes",
291 Op::ListWebhooks => "List webhooks",
292 Op::CreateWebhook => "Create a webhook",
293 Op::UpdateWebhook => "Update a webhook",
294 Op::DeleteWebhook => "Delete a webhook",
295 Op::PingWebhook => "Ping a webhook",
296 Op::ListWebhookDeliveries => "List webhook deliveries",
297 Op::RedeliverWebhook => "Redeliver a webhook delivery",
298 Op::ListWorkflows => "List workflows",
299 Op::ListWorkflowRuns => "List workflow runs",
300 Op::GetWorkflowRun => "Get a workflow run",
301 Op::GetJobLogs => "Get a job's log",
302 Op::DispatchWorkflow => "Run a workflow",
303 Op::CancelWorkflowRun => "Cancel a workflow run",
304 Op::RerunWorkflowRun => "Re-run a workflow run",
305 Op::UpdateWorkflow => "Turn a workflow on or off",
306 Op::ListActionsSecrets => "List secrets",
307 Op::SetActionsSecret => "Set a secret",
308 Op::DeleteActionsSecret => "Delete a secret",
309 Op::ListActionsVariables => "List variables",
310 Op::SetActionsVariable => "Set a variable",
311 Op::DeleteActionsVariable => "Delete a variable",
Fast pages, required checks on the branch, self-hosted runners, honest incidents312 Op::ListRunners => "List self-hosted runners",
313 Op::ListRunnerGroups => "List runner groups",
314 Op::GetRunnerSettings => "Get runner settings",
315 Op::CreateRunnerRegistrationToken => "Create a runner registration token",
316 Op::RemoveRunner => "Remove a self-hosted runner",
317 Op::CreateRunnerGroup => "Create a runner group",
318 Op::UpdateRunnerGroup => "Change a runner group",
319 Op::DeleteRunnerGroup => "Delete a runner group",
320 Op::UpdateRunnerSettings => "Change runner settings",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look321 Op::ListCollaborators => "List who has access",
322 Op::AddCollaborator => "Add a collaborator",
323 Op::UpdateCollaborator => "Change a collaborator's role",
324 Op::RemoveCollaborator => "Remove a collaborator",
325 Op::GetCollaboratorPermission => "Get someone's permission",
326 Op::ListRepoInvitations => "List a repository's invitations",
327 Op::RevokeRepoInvitation => "Revoke a repository invitation",
328 Op::ListMyRepoInvitations => "List your repository invitations",
329 Op::AcceptRepoInvitation => "Accept a repository invitation",
330 Op::DeclineRepoInvitation => "Decline a repository invitation",
331 Op::SetBasePermission => "Set the base permission",
332 Op::ListOutsideCollaborators => "List outside collaborators",
API and MCP server in Rust; a public index at the API root333 }
334}
335
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look336/// Why an operation can be refused with `402 payment_required`, if it
337/// can: the ones that start an agent, when the workspace has no credit,
338/// and the ones that make a repository private in a workspace, when a free
339/// workspace's private storage has no room for it.
340fn may_need_payment(op: Op) -> Option<&'static str> {
341 match op {
342 Op::AssignIssue | Op::PlanWork | Op::ApplyPlan => Some("The workspace has no agent credit."),
343 Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo => Some(
344 "A free workspace's private storage has no room for this private repository.",
345 ),
346 _ => None,
347 }
Merge branch 'worktree-agent-ab2e39e11a6493412'348}
349
350/// What the reference says beyond each operation's own description, keyed
351/// by operation id, written by hand from what the services return: `notes`
352/// (Markdown, added to the description) and example `params` (path),
353/// `query`, `request` (body) and `response`.
354const REFERENCE: &str = include_str!("reference.json");
355
356fn examples() -> Map<String, Value> {
357 match serde_json::from_str(REFERENCE) {
358 Ok(Value::Object(examples)) => examples,
359 _ => Map::new(),
API and MCP server in Rust; a public index at the API root360 }
361}
362
Agents as a team: lifecycle, merge queue, billing and a new shell363/// `/repos/:owner/:name` as OpenAPI writes it: `/repos/{owner}/{name}`.
API and MCP server in Rust; a public index at the API root364fn openapi_path(route: &Route) -> String {
365 route
366 .path
367 .split('/')
368 .map(|segment| match segment.strip_prefix(':') {
369 Some(name) => format!("{{{name}}}"),
370 None => segment.to_owned(),
371 })
372 .collect::<Vec<_>>()
373 .join("/")
374}
375
376fn error_response(description: &str) -> Value {
377 json!({
378 "description": description,
379 "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } },
380 })
381}
382
Merge branch 'worktree-agent-ab2e39e11a6493412'383/// A parameter in the path or the query, described by the operation's
384/// input schema where it has the same name.
385fn parameter(name: &str, place: &str, required: bool, schema: Option<&Value>) -> Value {
386 let mut schema = schema.cloned().unwrap_or_else(|| json!({ "type": "string" }));
387 let description = match name {
388 "owner" => Some(Value::from("The workspace that owns the repository.")),
389 "name" => Some(Value::from("The repository's name.")),
390 _ => schema.as_object_mut().and_then(|schema| schema.remove("description")),
391 };
392 let mut parameter = json!({
393 "name": name,
394 "in": place,
395 "required": required,
396 "schema": schema,
397 });
398 if let Some(description) = description {
399 parameter["description"] = description;
400 }
401 parameter
402}
403
404/// The operation id of a route. An operation reached at a workspace's
405/// address as well as a repository's is documented once for each, with its
406/// own id; GitHub's alternative addresses for one operation keep GitHub's
407/// names.
408fn operation_id(route: &Route) -> String {
409 let op = route.op;
410 let base = match (route.method, route.path.rsplit('/').next().unwrap_or_default()) {
411 ("PUT", "enable") => "enable_workflow".to_owned(),
412 ("PUT", "disable") => "disable_workflow".to_owned(),
413 ("POST", "rerun-failed-jobs") => "rerun_failed_jobs".to_owned(),
414 ("PATCH", ":setting") => "update_actions_variable".to_owned(),
415 ("GET", "runs") if route.path.contains("/workflows/:workflow/") => "list_runs_of_workflow".to_owned(),
416 _ => op.name().to_owned(),
417 };
418 if route.path.starts_with("/workspaces/") && ROUTES.iter().any(|other| other.op == op && other.path.starts_with("/repos/")) {
419 format!("{base}_for_workspace")
420 } else {
421 base
422 }
423}
424
425/// The summary of a route: its operation's title, or for one of GitHub's
426/// alternative addresses, what that address does.
427fn summary(route: &Route, id: &str) -> String {
428 let base = match id.trim_end_matches("_for_workspace") {
429 "enable_workflow" => "Turn a workflow on",
430 "disable_workflow" => "Turn a workflow off",
431 "rerun_failed_jobs" => "Re-run failed jobs",
432 "update_actions_variable" => "Update a variable",
433 "list_runs_of_workflow" => "List a workflow's runs",
434 _ => title(route.op),
435 };
436 if id.ends_with("_for_workspace") {
437 format!("{base} for a workspace")
438 } else {
439 base.to_owned()
440 }
441}
442
API and MCP server in Rust; a public index at the API root443fn operation(route: &Route) -> Value {
444 let op = route.op;
445 let path_params: Vec<&str> = route.params().collect();
446 // `owner` and `name` in the path stand for the operation's `repo` input.
447 let covered = |name: &str| name == "repo" || path_params.contains(&name);
Merge branch 'worktree-agent-ab2e39e11a6493412'448 let all_properties = op.properties();
449 let mut properties = all_properties.clone();
API and MCP server in Rust; a public index at the API root450 properties.retain(|name, _| !covered(name));
451 let required: Vec<String> = op
452 .required()
453 .into_iter()
454 .filter(|name| !covered(name))
455 .collect();
456
457 let mut parameters: Vec<Value> = path_params
458 .iter()
Merge branch 'worktree-agent-ab2e39e11a6493412'459 .map(|name| parameter(name, "path", true, all_properties.get(*name)))
API and MCP server in Rust; a public index at the API root460 .collect();
461 let mut body = Value::Null;
462 if route.method == "GET" {
463 for (name, key) in route.query {
Merge branch 'worktree-agent-ab2e39e11a6493412'464 parameters.push(parameter(
465 name,
466 "query",
467 required.iter().any(|required| required == key),
468 properties.get(*key),
469 ));
API and MCP server in Rust; a public index at the API root470 }
471 } else if !properties.is_empty() {
472 let mut schema = json!({ "type": "object", "properties": properties });
473 if !required.is_empty() {
474 schema["required"] = json!(required);
475 }
476 body = json!({
477 "required": !required.is_empty(),
478 "content": { "application/json": { "schema": schema } },
479 });
480 }
481
Merge branch 'worktree-agent-ab2e39e11a6493412'482 let id = operation_id(route);
483 let mut responses = Map::new();
484 responses.insert(
485 "200".into(),
486 json!({
487 "description": "Success.",
488 "content": { "application/json": { "schema": {} } },
489 }),
490 );
491 responses.insert(
492 "401".into(),
493 error_response("A token is required, or the one sent is not valid."),
494 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look495 if let Some(reason) = may_need_payment(op) {
496 responses.insert("402".into(), error_response(reason));
Merge branch 'worktree-agent-ab2e39e11a6493412'497 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step498 responses.insert(
499 "403".into(),
500 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."),
501 );
Search across all of g1t, Explore, and a command palette502 if !matches!(op, Op::Whoami | Op::ListRepos | Op::Search) {
Merge branch 'worktree-agent-ab2e39e11a6493412'503 responses.insert("404".into(), error_response("It does not exist, or you cannot see it."));
504 }
505 if route.method != "GET" {
506 responses.insert(
507 "409".into(),
508 error_response("The request conflicts with the current state."),
509 );
510 }
511 if op != Op::Whoami {
512 responses.insert("422".into(), error_response("The input is not valid."));
513 }
514 // 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 step515 let scope: Vec<&str> = scope_for(op.name()).map(|scope| scope.as_str()).into_iter().collect();
Merge branch 'worktree-agent-ab2e39e11a6493412'516 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 step517 json!([{ "token": scope }])
Webhooks: every event, to your own addresses, signed and retried518 } else {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step519 json!([{ "token": scope }, {}])
Webhooks: every event, to your own addresses, signed and retried520 };
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step521 let (tool, action) = crate::tools::TOOLS
522 .iter()
523 .find_map(|tool| {
524 tool.actions
525 .iter()
526 .find(|action| action.op == op)
527 .map(|action| (tool.name, action.name))
528 })
529 .unwrap_or_default();
API and MCP server in Rust; a public index at the API root530 let mut described = json!({
Webhooks: every event, to your own addresses, signed and retried531 "operationId": id,
API and MCP server in Rust; a public index at the API root532 "tags": [tag(op)],
Merge branch 'worktree-agent-ab2e39e11a6493412'533 "summary": summary(route, &id),
API and MCP server in Rust; a public index at the API root534 "description": op.description(),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step535 "x-operation": op.name(),
536 "x-mcp-tool": tool,
537 "x-mcp-action": action,
538 "x-scope": scope.first().copied(),
Merge branch 'worktree-agent-ab2e39e11a6493412'539 "security": security,
API and MCP server in Rust; a public index at the API root540 "parameters": parameters,
Merge branch 'worktree-agent-ab2e39e11a6493412'541 "responses": responses,
API and MCP server in Rust; a public index at the API root542 });
543 if !body.is_null() {
544 described["requestBody"] = body;
545 }
546 described
547}
548
549/// Entries for device sign-in, which is not an operation.
550fn onboarding() -> Map<String, Value> {
551 let paths = json!({
Agents as a team: lifecycle, merge queue, billing and a new shell552 "/device/code": {
API and MCP server in Rust; a public index at the API root553 "post": {
554 "operationId": "device_code",
555 "tags": ["Accounts"],
556 "summary": "Start signing in",
Agents as a team: lifecycle, merge queue, billing and a new shell557 "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 root558 "security": [],
559 "requestBody": {
560 "content": { "application/json": { "schema": {
561 "type": "object",
562 "properties": {
563 "client_name": {
564 "type": "string",
565 "description": "What is asking, shown to the person approving. For example, Claude Code.",
566 },
567 },
568 } } },
569 },
570 "responses": { "200": {
571 "description": "The codes for this sign-in.",
572 "content": { "application/json": { "schema": {
573 "type": "object",
574 "properties": {
Agents as a team: lifecycle, merge queue, billing and a new shell575 "device_code": { "type": "string", "description": "Secret. Send it to /device/token." },
API and MCP server in Rust; a public index at the API root576 "user_code": { "type": "string", "description": "Shown to the person, like WDJB-MJHT." },
577 "verification_uri": { "type": "string" },
578 "verification_uri_complete": {
579 "type": "string",
580 "description": "The link to give the person; it carries the code.",
581 },
582 "expires_in": { "type": "integer", "description": "Seconds until the codes expire." },
583 "interval": { "type": "integer", "description": "Seconds to wait between polls." },
584 },
585 } } },
586 } },
587 },
588 },
Agents as a team: lifecycle, merge queue, billing and a new shell589 "/device/token": {
API and MCP server in Rust; a public index at the API root590 "post": {
591 "operationId": "device_token",
592 "tags": ["Accounts"],
593 "summary": "Finish signing in",
594 "description": "Asks whether the person has approved. Poll no faster than the interval. The token is returned once.",
595 "security": [],
596 "requestBody": {
597 "required": true,
598 "content": { "application/json": { "schema": {
599 "type": "object",
600 "required": ["device_code"],
601 "properties": { "device_code": { "type": "string" } },
602 } } },
603 },
604 "responses": { "200": {
605 "description": "The state of the sign-in.",
606 "content": { "application/json": { "schema": {
607 "type": "object",
608 "required": ["status"],
609 "properties": {
610 "status": { "type": "string", "enum": ["pending", "approved", "denied", "expired"] },
611 "token": { "type": "string", "description": "Present when approved." },
612 "username": { "type": "string" },
613 "verified": {
614 "type": "boolean",
615 "description": "Whether the account's email is confirmed.",
616 },
617 },
618 } } },
619 } },
620 },
621 },
622 });
623 match paths {
624 Value::Object(paths) => paths,
625 _ => Map::new(),
626 }
627}
628
Merge branch 'worktree-agent-ab2e39e11a6493412'629
630/// Puts each operation's examples, where it has them, into its request
631/// and response. Path and query values go under `x-example-params` and
632/// `x-example-query`, which tools that build a request can use.
633fn attach_examples(paths: &mut Map<String, Value>) {
634 let examples = examples();
635 for methods in paths.values_mut() {
636 let Some(methods) = methods.as_object_mut() else { continue };
637 for operation in methods.values_mut() {
638 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 step639 let name = operation["x-operation"].as_str().unwrap_or_default().to_owned();
640 let Some(example) = examples.get(&id).or_else(|| examples.get(&name)) else {
Merge branch 'worktree-agent-ab2e39e11a6493412'641 continue;
642 };
643 if let Some(notes) = example.get("notes").and_then(Value::as_str) {
644 let description = operation["description"].as_str().unwrap_or_default();
645 operation["description"] = json!(format!("{description}\n\n{notes}"));
646 }
647 if let Some(response) = example.get("response") {
648 let content = &mut operation["responses"]["200"]["content"]["application/json"];
649 if content.is_object() {
650 content["example"] = response.clone();
651 }
652 }
653 if let Some(request) = example.get("request") {
654 let content = &mut operation["requestBody"]["content"]["application/json"];
655 if content.is_object() {
656 content["example"] = request.clone();
657 }
658 }
659 for (key, extension) in [("params", "x-example-params"), ("query", "x-example-query")] {
660 if let Some(values) = example.get(key) {
661 operation[extension] = values.clone();
662 }
663 }
664 }
665 }
666}
667
API and MCP server in Rust; a public index at the API root668pub fn document() -> Value {
669 let mut paths = onboarding();
670 for route in ROUTES {
671 let entry = paths
672 .entry(openapi_path(route))
673 .or_insert_with(|| json!({}));
674 entry[route.method.to_lowercase()] = operation(route);
675 }
Merge branch 'worktree-agent-ab2e39e11a6493412'676 attach_examples(&mut paths);
677 let tags: Vec<Value> = SECTIONS
678 .iter()
679 .map(|(name, description, ops)| {
680 json!({
681 "name": name,
682 "description": description,
683 // The section's operations in reading order, by MCP tool name.
684 "x-tools": ops.iter().map(|op| op.name()).collect::<Vec<_>>(),
685 })
686 })
687 .collect();
688 let codes = ["unauthenticated", "payment_required", "forbidden", "not_found", "conflict", "invalid"];
API and MCP server in Rust; a public index at the API root689 json!({
690 "openapi": "3.1.0",
691 "info": {
692 "title": "g1t API",
693 "version": "1",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API694 "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 root695 "license": { "name": "MIT", "identifier": "MIT" },
696 },
697 "servers": [{ "url": "https://api.g1t.sh" }],
698 "security": [{ "token": [] }, {}],
Merge branch 'worktree-agent-ab2e39e11a6493412'699 "tags": tags,
API and MCP server in Rust; a public index at the API root700 "paths": paths,
701 "components": {
702 "securitySchemes": {
703 "token": {
704 "type": "http",
705 "scheme": "bearer",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step706 "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 root707 },
708 },
709 "schemas": {
710 "Error": {
711 "type": "object",
712 "required": ["error"],
713 "properties": {
714 "error": {
715 "type": "object",
716 "required": ["code", "message"],
717 "properties": {
Merge branch 'worktree-agent-ab2e39e11a6493412'718 "code": { "type": "string", "enum": codes },
API and MCP server in Rust; a public index at the API root719 "message": { "type": "string" },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step720 "needed_scope": {
721 "type": "string",
722 "description": "On a 403 for an access token without the scope the call needs: that scope, such as `issues:write`.",
723 },
API and MCP server in Rust; a public index at the API root724 },
725 },
726 },
727 },
728 },
729 },
730 })
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736
737 #[test]
738 fn every_route_is_documented_once() {
739 let document = document();
740 let mut ids = Vec::new();
741 for (_, methods) in document["paths"].as_object().unwrap() {
742 for (_, operation) in methods.as_object().unwrap() {
743 ids.push(operation["operationId"].as_str().unwrap().to_owned());
744 }
745 }
746 for op in Op::ALL {
747 assert_eq!(
748 ids.iter().filter(|id| *id == op.name()).count(),
749 1,
750 "{}",
751 op.name()
752 );
753 }
Webhooks: every event, to your own addresses, signed and retried754 let mut unique = ids.clone();
755 unique.sort();
756 unique.dedup();
757 assert_eq!(unique.len(), ids.len(), "operation ids repeat");
API and MCP server in Rust; a public index at the API root758 }
759
760 #[test]
761 fn path_and_query_inputs_are_not_repeated_in_the_body() {
762 let document = document();
Agents as a team: lifecycle, merge queue, billing and a new shell763 let merge = &document["paths"]["/repos/{owner}/{name}/pulls/{number}/merge"]["post"];
API and MCP server in Rust; a public index at the API root764 let body = &merge["requestBody"]["content"]["application/json"]["schema"]["properties"];
765 assert!(body.get("keep_issue_open").is_some());
766 assert!(body.get("repo").is_none() && body.get("number").is_none());
Agents as a team: lifecycle, merge queue, billing and a new shell767 let list = &document["paths"]["/repos"]["get"];
API and MCP server in Rust; a public index at the API root768 assert_eq!(list["parameters"][0]["name"], "q");
769 assert!(list.get("requestBody").is_none());
770 }
771
772 #[test]
Merge branch 'worktree-agent-ab2e39e11a6493412'773 fn every_operation_is_in_one_section() {
774 for op in Op::ALL {
775 let sections = SECTIONS
776 .iter()
777 .filter(|(_, _, ops)| ops.contains(&op))
778 .count();
779 assert_eq!(sections, 1, "{}", op.name());
780 }
781 }
782
783 #[test]
API and MCP server in Rust; a public index at the API root784 fn titles_read_as_sentences() {
Merge branch 'worktree-agent-ab2e39e11a6493412'785 assert_eq!(title(Op::CreateIssue), "Create an issue");
API and MCP server in Rust; a public index at the API root786 assert_eq!(title(Op::Whoami), "Get the current user");
787 }
Merge branch 'worktree-agent-ab2e39e11a6493412'788
789 #[test]
790 fn every_operation_has_an_example_response() {
791 let examples = examples();
792 assert!(!examples.is_empty(), "reference.json does not parse");
793 let document = document();
794 let mut known = Vec::new();
795 for (path, methods) in document["paths"].as_object().unwrap() {
796 for (method, operation) in methods.as_object().unwrap() {
797 known.push(operation["operationId"].as_str().unwrap().to_owned());
798 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
799 assert!(!example.is_null(), "{method} {path} has no example response");
800 }
801 }
802 for id in examples.keys() {
803 assert!(known.contains(id), "reference.json names {id}, which is not an operation");
804 }
805 }
806
807 #[test]
808 fn example_requests_send_only_what_the_body_takes() {
809 let document = document();
810 for (path, methods) in document["paths"].as_object().unwrap() {
811 for (method, operation) in methods.as_object().unwrap() {
812 let content = &operation["requestBody"]["content"]["application/json"];
813 let Some(example) = content["example"].as_object() else { continue };
814 let properties = &content["schema"]["properties"];
815 for key in example.keys() {
816 assert!(!properties[key].is_null(), "{method} {path}: {key} is not in the body");
817 }
818 }
819 }
820 }
821
822 /// The docs site's copy of the document. Run with `G1T_WRITE_OPENAPI=1`
823 /// to rewrite it after changing an operation.
824 #[test]
825 fn the_docs_copy_is_current() {
826 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../docs/src/data/openapi.json");
827 let current = serde_json::to_string_pretty(&document()).unwrap() + "\n";
828 if std::env::var_os("G1T_WRITE_OPENAPI").is_some() {
829 std::fs::write(path, &current).unwrap();
830 return;
831 }
832 let copy = std::fs::read_to_string(path).unwrap_or_default().replace("\r\n", "\n");
833 assert!(
834 copy == current,
835 "apps/docs/src/data/openapi.json is out of date: run G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi"
836 );
837 }
API reference: no example reads as a real secret838
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API839 /// The reference shows responses as they are sent: `snake_case`.
840 #[test]
841 fn example_responses_are_snake_case() {
842 let document = document();
843 for (path, methods) in document["paths"].as_object().unwrap() {
844 for (method, operation) in methods.as_object().unwrap() {
845 let example = &operation["responses"]["200"]["content"]["application/json"]["example"];
846 let leaked = g1t_kit::wire::camel_case_keys(example);
847 assert!(leaked.is_empty(), "{method} {path} shows {leaked:?}");
848 }
849 }
850 }
851
API reference: no example reads as a real secret852 /// Examples never hold anything that reads as a real credential, which
853 /// secret scanners rightly flag in a public repository: they end in `…`
854 /// after the prefix, as `whsec_…` and `g1t_…` do.
855 #[test]
856 fn examples_hold_no_real_looking_secrets() {
Fast pages, required checks on the branch, self-hosted runners, honest incidents857 let prefixes = ["whsec_", "g1t_", "g1tr_", "g1trt_", "sk_live_", "sk_test_", "ghp_", "github_pat_", "xoxb-", "AKIA"];
API reference: no example reads as a real secret858 for (line, text) in REFERENCE.lines().enumerate() {
859 for prefix in prefixes {
860 let mut rest = text;
861 while let Some(at) = rest.find(prefix) {
862 let after = &rest[at + prefix.len()..];
863 let run = after.chars().take_while(|c| c.is_ascii_alphanumeric()).count();
864 assert!(
865 run < 12,
866 "reference.json line {}: `{prefix}` followed by {run} characters reads as a real secret; write `{prefix}…`",
867 line + 1
868 );
869 rest = after;
870 }
871 }
872 }
873 }
API and MCP server in Rust; a public index at the API root874}

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