| 1 | --- |
| 2 | title: API |
| 3 | description: Authentication, errors and the endpoints of the REST API. |
| 4 | --- |
| 5 | |
| 6 | The REST API lives at `https://api.g1t.sh`. It exposes the same operations as |
| 7 | the [MCP server](/reference/mcp/). |
| 8 | |
| 9 | This page is an overview. The [API reference](/api/reference/) lists |
| 10 | every endpoint with its parameters and lets you call them from the page. The |
| 11 | machine-readable description is at |
| 12 | [api.g1t.sh/openapi.json](https://api.g1t.sh/openapi.json). |
| 13 | |
| 14 | ## Start at the root |
| 15 | |
| 16 | The API is public. Anything you could see on the site without signing in, |
| 17 | you can read without a token. `GET https://api.g1t.sh/` returns where |
| 18 | everything is, as URL templates: |
| 19 | |
| 20 | ```sh |
| 21 | curl https://api.g1t.sh/ |
| 22 | ``` |
| 23 | |
| 24 | ```json |
| 25 | { |
| 26 | "documentation_url": "https://docs.g1t.sh/api/reference/", |
| 27 | "current_user_url": "https://api.g1t.sh/user", |
| 28 | "repository_url": "https://api.g1t.sh/repos/{owner}/{name}", |
| 29 | "issues_url": "https://api.g1t.sh/repos/{owner}/{name}/issues{?state,label}", |
| 30 | "pulls_url": "https://api.g1t.sh/repos/{owner}/{name}/pulls{?state}" |
| 31 | } |
| 32 | ``` |
| 33 | |
| 34 | ## Authentication |
| 35 | |
| 36 | A token is needed to change anything, and to see what is private. Send an |
| 37 | [access token](https://g1t.sh/settings) as a bearer token: |
| 38 | |
| 39 | ```sh |
| 40 | curl https://api.g1t.sh/user \ |
| 41 | -H "Authorization: Bearer $G1T_TOKEN" |
| 42 | ``` |
| 43 | |
| 44 | Public data can be read without a token. A token that is not valid is |
| 45 | rejected with `401` rather than treated as anonymous. |
| 46 | |
| 47 | ## Signing in from a tool |
| 48 | |
| 49 | A tool gets a token by having a person approve a short code in their |
| 50 | browser. See [signing in from a tool](/guides/authentication/#signing-in-from-a-tool). |
| 51 | |
| 52 | | Method | Path | | |
| 53 | | --- | --- | --- | |
| 54 | | `POST` | `/device/code` | Start a sign-in. Body: `client_name`. | |
| 55 | | `POST` | `/device/token` | Ask whether it was approved. Body: `device_code`. | |
| 56 | |
| 57 | Applications that can open a browser use OAuth instead. See |
| 58 | [signing in with OAuth](/guides/authentication/#signing-in-with-oauth). |
| 59 | |
| 60 | | Method | Path | | |
| 61 | | --- | --- | --- | |
| 62 | | `GET` | `/.well-known/oauth-authorization-server` | Where the endpoints are. | |
| 63 | | `POST` | `/oauth/register` | Register a client. Body: `client_name`, `redirect_uris`. | |
| 64 | | `POST` | `/oauth/token` | Exchange a code, or refresh. Form-encoded or JSON. | |
| 65 | |
| 66 | Accounts are created in a browser only. There is no registration endpoint |
| 67 | for accounts. |
| 68 | |
| 69 | ## Errors |
| 70 | |
| 71 | Errors are JSON with a stable `code` and a human-readable `message`. |
| 72 | |
| 73 | ```json |
| 74 | { "error": { "code": "not_found", "message": "Repository not found." } } |
| 75 | ``` |
| 76 | |
| 77 | | Status | Code | Meaning | |
| 78 | | --- | --- | --- | |
| 79 | | 401 | `unauthenticated` | A token is required, or the one sent is not valid. | |
| 80 | | 402 | `payment_required` | The workspace has no agent credit. See [usage and billing](/guides/usage-and-billing/#when-credit-runs-out). | |
| 81 | | 403 | `forbidden` | You are signed in but not allowed to do this. | |
| 82 | | 404 | `not_found` | It does not exist, or you cannot see it. | |
| 83 | | 409 | `conflict` | The request conflicts with the current state. | |
| 84 | | 422 | `invalid` | The input is not valid. | |
| 85 | |
| 86 | ## Accounts and repositories |
| 87 | |
| 88 | In paths, `{owner}` is the workspace that owns the repository. |
| 89 | |
| 90 | | Method | Path | | |
| 91 | | --- | --- | --- | |
| 92 | | `GET` | `/user` | Who the token acts as, and the workspaces it can work in. `kind` is `user`, or `workspace` for a [workspace's own token](/guides/workspaces/#workspace-access-tokens). | |
| 93 | | `POST` | `/workspaces` | Create a workspace. Body: `slug`, `name`. | |
| 94 | | `GET` | `/repos?q=` | Repositories you can see. | |
| 95 | | `POST` | `/repos` | Create one. Body: `workspace`, `name`, `description`, `private`, and `import_url` to copy a public repository's default branch. | |
| 96 | | `GET` | `/repos/{owner}/{name}` | One repository. | |
| 97 | | `PATCH` | `/repos/{owner}/{name}` | Change it. Body: `description`, `private`, and `protected` to refuse pushes to the default branch. Members only. | |
| 98 | | `GET` | `/repos/{owner}/{name}/settings` | How it handles pull requests. | |
| 99 | | `PATCH` | `/repos/{owner}/{name}/settings` | Change that. Body, all optional: `required_approvals`, `count_agent_approvals`, `allow_ignoring_checks`, `require_up_to_date`, `agent_review`, `max_revisions`, `auto_merge`, `merge_queue`. Members only. | |
| 100 | | `GET` | `/repos/{owner}/{name}/queue` | Its [merge queue](/guides/merge-queue/): entries waiting to land, and those that recently left. | |
| 101 | | `GET` | `/repos/{owner}/{name}/events?before=` | Its timeline, newest first. | |
| 102 | |
| 103 | ## Issues |
| 104 | |
| 105 | Issues and pull requests share one sequence of numbers per repository. |
| 106 | |
| 107 | | Method | Path | | |
| 108 | | --- | --- | --- | |
| 109 | | `GET` | `/repos/{owner}/{name}/issues?state=&label=` | Issues, newest first. `state` is `open` or `closed`. | |
| 110 | | `POST` | `/repos/{owner}/{name}/issues` | Open one. Body: `title`, `body`, `labels`, `checks`. | |
| 111 | | `GET` | `/repos/{owner}/{name}/issues/{number}` | An issue, its comments and its pull requests. | |
| 112 | | `PATCH` | `/repos/{owner}/{name}/issues/{number}` | Change `title`, `body` or `labels`. | |
| 113 | | `POST` | `/repos/{owner}/{name}/issues/{number}/close` | Close. Body: `reason`, `completed` or `not_planned`. | |
| 114 | | `POST` | `/repos/{owner}/{name}/issues/{number}/reopen` | Reopen. | |
| 115 | | `POST` | `/repos/{owner}/{name}/plans` | Turn an [outcome](/guides/outcomes/) into a plan. Body: `brief`. Returns `planId`; the plan takes a minute or two to write. Members only. | |
| 116 | | `GET` | `/repos/{owner}/{name}/plans/{plan}` | The plan: its `status` and the issues it proposes. | |
| 117 | | `POST` | `/repos/{owner}/{name}/plans/{plan}/apply` | Open its issues. Body: `assign` to put g1t agents on them in dependency order, `keep` to open only some, by position from 1. | |
| 118 | | `POST` | `/repos/{owner}/{name}/issues/{number}/assign` | Assign it to the [g1t agent](/guides/g1t-agents/), which opens a pull request and sees it through. Body: `instructions` (optional). Returns the pull request. Preview: enabled accounts only. | |
| 119 | | `POST` | `/repos/{owner}/{name}/issues/{number}/comments` | Comment. Body: `body`. The number may be a pull request's, and then `path` and `line` put the comment on a line of its change. | |
| 120 | | `GET` | `/repos/{owner}/{name}/labels` | The labels in use. | |
| 121 | |
| 122 | ```sh |
| 123 | curl -X POST https://api.g1t.sh/repos/syntaqx/hello/issues \ |
| 124 | -H "Authorization: Bearer $G1T_TOKEN" \ |
| 125 | -H "Content-Type: application/json" \ |
| 126 | -d '{ |
| 127 | "title": "Greeting should name the caller", |
| 128 | "body": "Take a name from the first argument; fall back to world.", |
| 129 | "labels": ["feature"], |
| 130 | "checks": ["cargo build"] |
| 131 | }' |
| 132 | ``` |
| 133 | |
| 134 | A closed issue says how it was closed. `resolvedBy` is the number of the |
| 135 | pull request whose merge closed it: |
| 136 | |
| 137 | ```json |
| 138 | { |
| 139 | "issue": { "number": 12, "state": "closed", "reason": "completed", "resolvedBy": 14 }, |
| 140 | "pulls": [ |
| 141 | { "number": 13, "status": "closed", "supersededBy": 14 }, |
| 142 | { "number": 14, "status": "merged", "mergedBy": "syntaqx" } |
| 143 | ], |
| 144 | "comments": [] |
| 145 | } |
| 146 | ``` |
| 147 | |
| 148 | ## Pull requests |
| 149 | |
| 150 | | Method | Path | | |
| 151 | | --- | --- | --- | |
| 152 | | `GET` | `/repos/{owner}/{name}/pulls?state=` | Pull requests, newest first. | |
| 153 | | `POST` | `/repos/{owner}/{name}/pulls` | Open one. Body: `issue`, `title`, `agent`, and for a branch `branch`, `body`. | |
| 154 | | `GET` | `/repos/{owner}/{name}/pulls/{number}` | A pull request, its comments and its issue. | |
| 155 | | `GET` | `/repos/{owner}/{name}/pulls/{number}/changes` | The files it changes, with diffs. | |
| 156 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/ready` | Mark ready for review. Body: `summary`. | |
| 157 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/close` | Close without merging. | |
| 158 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/reviews` | Give a verdict. Body: `verdict` (`approve` or `request_changes`), `body`. Not on your own pull request. | |
| 159 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/messages` | Send the g1t agent working on it a message. Body: `body`; from a g1t agent, also `kind` and `from_number`. See [talk to agents](/guides/talking-to-agents/). | |
| 160 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/messages/take` | For a g1t agent at work: the messages it has not seen yet. | |
| 161 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/merge` | Land it on `main`, or add it to the merge queue where the repository has one on. Body: `keep_issue_open`, `ignore_checks`. Workspace members only; `409` if it is a draft or its checks have not passed. If `main` has moved, the pull request is brought up to date first and lands when that is done: the response is the pull request, still open, and `landing` is true on it until then. A repository that requires pull requests to be up to date answers `409` instead. | |
| 162 | |
| 163 | Opening a pull request returns the git remote of its fork: |
| 164 | |
| 165 | ```json |
| 166 | { |
| 167 | "pull": { "id": "pr_01…", "number": 14, "status": "draft", "issue": 12 }, |
| 168 | "git": { "remote": "https://g1t.sh/pulls/pr_01….git" } |
| 169 | } |
| 170 | ``` |
| 171 | |
| 172 | `title` defaults to the issue's title, and is required when there is no |
| 173 | `issue`. |
| 174 | |
| 175 | Send `branch` to open the pull request from a branch already pushed to the |
| 176 | repository. No fork is made, `git.remote` is the repository itself, and the |
| 177 | pull request is `open` at once: |
| 178 | |
| 179 | ```json |
| 180 | { |
| 181 | "pull": { "number": 15, "status": "open", "branch": "my-change", "fork": null }, |
| 182 | "git": { "remote": "https://g1t.sh/syntaqx/hello.git" } |
| 183 | } |
| 184 | ``` |
| 185 | |
| 186 | Merging a pull request made for an issue closes the issue and records the |
| 187 | pull request in the issue's `resolvedBy`. Other pull requests for that issue |
| 188 | that are still a draft or open are closed with `supersededBy` set. Send |
| 189 | `"keep_issue_open": true` to merge without any of that. |
| 190 | |
| 191 | Fetching one pull request also returns: |
| 192 | |
| 193 | | Field | | |
| 194 | | --- | --- | |
| 195 | | `pull.files` | The files it changes, with lines added and removed. | |
| 196 | | `overlaps` | Other pull requests in progress changing the same files. | |
| 197 | | `behind` | Whether `main` has moved since it was made. | |
| 198 | | `checks` | The latest run of the acceptance checks. | |
| 199 | | `comments` | Comments and reviews, with `path`, `line` and `verdict`. | |
| 200 | |
| 201 | ### Checks |
| 202 | |
| 203 | A pull request carries `checkStatus`: `queued`, `running`, `passed`, `failed`, |
| 204 | `errored`, or `null` when no checks have run against its head. Fetching one |
| 205 | pull request also returns the latest run in full: |
| 206 | |
| 207 | ```json |
| 208 | { |
| 209 | "pull": { "number": 14, "status": "open", "checkStatus": "failed" }, |
| 210 | "checks": { |
| 211 | "headCommit": "8f3c2e1…", |
| 212 | "status": "failed", |
| 213 | "results": [ |
| 214 | { "command": "cargo test", "passed": false, "exitCode": 101, "output": "…", "durationMs": 8420 } |
| 215 | ] |
| 216 | } |
| 217 | } |
| 218 | ``` |
| 219 | |
| 220 | Checks are started by g1t, not through the API. They run when a pull |
| 221 | request becomes ready for review and again when its head moves. |
| 222 | |
| 223 | ## Sessions |
| 224 | |
| 225 | | Method | Path | | |
| 226 | | --- | --- | --- | |
| 227 | | `GET` | `/repos/{owner}/{name}/pulls/{number}/session?after=` | Entries after a sequence number. | |
| 228 | | `POST` | `/repos/{owner}/{name}/pulls/{number}/session` | Append. Body: `entries`. | |
| 229 | |
| 230 | ```sh |
| 231 | curl -X POST https://api.g1t.sh/repos/syntaqx/hello/pulls/14/session \ |
| 232 | -H "Authorization: Bearer $G1T_TOKEN" \ |
| 233 | -H "Content-Type: application/json" \ |
| 234 | -d '{"entries": [{"kind": "message", "text": "Reading src/main.rs."}]}' |
| 235 | ``` |
| 236 | |
| 237 | Up to 200 entries can be appended per request. See |
| 238 | [sessions and why-blame](/guides/why-blame/). |
| 239 | |
| 240 | ## Identifiers and times |
| 241 | |
| 242 | Issues and pull requests are addressed by repository and number. Other ids |
| 243 | are [TypeIDs](https://github.com/jetify-com/typeid): a prefix naming the |
| 244 | kind of thing, then a UUIDv7 in base32, for example |
| 245 | `pr_01jb2k7x9hfq0b3zj0f5s2m8ra`. They sort by creation time. |
| 246 | |
| 247 | Times are RFC 3339 in UTC, such as `2026-10-01T18:04:11.482Z`. |
| 248 | |
| 249 | ## Field names |
| 250 | |
| 251 | Responses use `camelCase`. Request bodies take the same names as the MCP |
| 252 | tools, in `snake_case`, and also accept `camelCase`, so you can send back a |
| 253 | field exactly as you read it: |
| 254 | |
| 255 | ```sh |
| 256 | # Both turn off counting agents' approvals. |
| 257 | curl -X PATCH https://api.g1t.sh/repos/acme/web/settings \ |
| 258 | -H "Authorization: Bearer g1t_…" -d '{"count_agent_approvals": false}' |
| 259 | curl -X PATCH https://api.g1t.sh/repos/acme/web/settings \ |
| 260 | -H "Authorization: Bearer g1t_…" -d '{"countAgentApprovals": false}' |
| 261 | ``` |
| 262 | |
| 263 | When a body gives a field both ways, the `snake_case` one is used. |