| 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](/guides/bring-your-own-agent/). |
| 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 | ## Authentication |
| 15 | |
| 16 | Send an [access token](https://g1t.sh/settings) as a bearer token: |
| 17 | |
| 18 | ```sh |
| 19 | curl https://api.g1t.sh/v1/user \ |
| 20 | -H "Authorization: Bearer $G1T_TOKEN" |
| 21 | ``` |
| 22 | |
| 23 | Public data can be read without a token. A token that is not valid is |
| 24 | rejected with `401` rather than treated as anonymous. |
| 25 | |
| 26 | ## Signing in from a tool |
| 27 | |
| 28 | A tool gets a token by having a person approve a short code in their |
| 29 | browser. See [signing in from a tool](/guides/authentication/#signing-in-from-a-tool). |
| 30 | |
| 31 | | Method | Path | | |
| 32 | | --- | --- | --- | |
| 33 | | `POST` | `/v1/device/code` | Start a sign-in. Body: `client_name`. | |
| 34 | | `POST` | `/v1/device/token` | Ask whether it was approved. Body: `device_code`. | |
| 35 | |
| 36 | Applications that can open a browser use OAuth instead. See |
| 37 | [signing in with OAuth](/guides/authentication/#signing-in-with-oauth). |
| 38 | |
| 39 | | Method | Path | | |
| 40 | | --- | --- | --- | |
| 41 | | `GET` | `/.well-known/oauth-authorization-server` | Where the endpoints are. | |
| 42 | | `POST` | `/oauth/register` | Register a client. Body: `client_name`, `redirect_uris`. | |
| 43 | | `POST` | `/oauth/token` | Exchange a code, or refresh. Form-encoded or JSON. | |
| 44 | |
| 45 | Accounts are created in a browser only. There is no registration endpoint |
| 46 | for accounts. |
| 47 | |
| 48 | ## Errors |
| 49 | |
| 50 | Errors are JSON with a stable `code` and a human-readable `message`. |
| 51 | |
| 52 | ```json |
| 53 | { "error": { "code": "not_found", "message": "Repository not found." } } |
| 54 | ``` |
| 55 | |
| 56 | | Status | Code | Meaning | |
| 57 | | --- | --- | --- | |
| 58 | | 401 | `unauthenticated` | A token is required, or the one sent is not valid. | |
| 59 | | 403 | `forbidden` | You are signed in but not allowed to do this. | |
| 60 | | 404 | `not_found` | It does not exist, or you cannot see it. | |
| 61 | | 409 | `conflict` | The request conflicts with the current state. | |
| 62 | | 422 | `invalid` | The input is not valid. | |
| 63 | |
| 64 | ## Accounts and repositories |
| 65 | |
| 66 | In paths, `{owner}` is the workspace that owns the repository. |
| 67 | |
| 68 | | Method | Path | | |
| 69 | | --- | --- | --- | |
| 70 | | `GET` | `/v1/user` | The account the token belongs to, and its workspaces. | |
| 71 | | `POST` | `/v1/workspaces` | Create a workspace. Body: `slug`, `name`. | |
| 72 | | `GET` | `/v1/repos?q=` | Repositories you can see. | |
| 73 | | `POST` | `/v1/repos` | Create one. Body: `workspace`, `name`, `description`, `private`. | |
| 74 | | `GET` | `/v1/repos/{owner}/{name}` | One repository. | |
| 75 | | `GET` | `/v1/repos/{owner}/{name}/events?before=` | Its timeline, newest first. | |
| 76 | |
| 77 | ## Issues |
| 78 | |
| 79 | Issues and pull requests share one sequence of numbers per repository. |
| 80 | |
| 81 | | Method | Path | | |
| 82 | | --- | --- | --- | |
| 83 | | `GET` | `/v1/repos/{owner}/{name}/issues?state=&label=` | Issues, newest first. `state` is `open` or `closed`. | |
| 84 | | `POST` | `/v1/repos/{owner}/{name}/issues` | Open one. Body: `title`, `body`, `labels`, `checks`. | |
| 85 | | `GET` | `/v1/repos/{owner}/{name}/issues/{number}` | An issue, its comments and its pull requests. | |
| 86 | | `PATCH` | `/v1/repos/{owner}/{name}/issues/{number}` | Change `title`, `body` or `labels`. | |
| 87 | | `POST` | `/v1/repos/{owner}/{name}/issues/{number}/close` | Close. Body: `reason`, `completed` or `not_planned`. | |
| 88 | | `POST` | `/v1/repos/{owner}/{name}/issues/{number}/reopen` | Reopen. | |
| 89 | | `POST` | `/v1/repos/{owner}/{name}/issues/{number}/comments` | Comment. Body: `body`. The number may be a pull request's. | |
| 90 | | `GET` | `/v1/repos/{owner}/{name}/labels` | The labels in use. | |
| 91 | |
| 92 | ```sh |
| 93 | curl -X POST https://api.g1t.sh/v1/repos/syntaqx/hello/issues \ |
| 94 | -H "Authorization: Bearer $G1T_TOKEN" \ |
| 95 | -H "Content-Type: application/json" \ |
| 96 | -d '{ |
| 97 | "title": "Greeting should name the caller", |
| 98 | "body": "Take a name from the first argument; fall back to world.", |
| 99 | "labels": ["feature"], |
| 100 | "checks": ["cargo build"] |
| 101 | }' |
| 102 | ``` |
| 103 | |
| 104 | A closed issue says how it was closed. `resolvedBy` is the number of the |
| 105 | pull request whose merge closed it: |
| 106 | |
| 107 | ```json |
| 108 | { |
| 109 | "issue": { "number": 12, "state": "closed", "reason": "completed", "resolvedBy": 14 }, |
| 110 | "pulls": [ |
| 111 | { "number": 13, "status": "closed", "supersededBy": 14 }, |
| 112 | { "number": 14, "status": "merged", "mergedBy": "syntaqx" } |
| 113 | ], |
| 114 | "comments": [] |
| 115 | } |
| 116 | ``` |
| 117 | |
| 118 | ## Pull requests |
| 119 | |
| 120 | | Method | Path | | |
| 121 | | --- | --- | --- | |
| 122 | | `GET` | `/v1/repos/{owner}/{name}/pulls?state=` | Pull requests, newest first. | |
| 123 | | `POST` | `/v1/repos/{owner}/{name}/pulls` | Open one. Body: `issue`, `title`, `agent`, and for a branch `branch`, `body`. | |
| 124 | | `GET` | `/v1/repos/{owner}/{name}/pulls/{number}` | A pull request, its comments and its issue. | |
| 125 | | `GET` | `/v1/repos/{owner}/{name}/pulls/{number}/changes` | The files it changes, with diffs. | |
| 126 | | `POST` | `/v1/repos/{owner}/{name}/pulls/{number}/ready` | Mark ready for review. Body: `summary`. | |
| 127 | | `POST` | `/v1/repos/{owner}/{name}/pulls/{number}/close` | Close without merging. | |
| 128 | | `POST` | `/v1/repos/{owner}/{name}/pulls/{number}/merge` | Land it on `main`. Body: `keep_issue_open`. Workspace members only; `409` if it is a draft or `main` has moved. | |
| 129 | |
| 130 | Opening a pull request returns the git remote of its fork: |
| 131 | |
| 132 | ```json |
| 133 | { |
| 134 | "pull": { "id": "pr_01…", "number": 14, "status": "draft", "issue": 12 }, |
| 135 | "git": { "remote": "https://g1t.sh/pulls/pr_01….git" } |
| 136 | } |
| 137 | ``` |
| 138 | |
| 139 | `title` defaults to the issue's title, and is required when there is no |
| 140 | `issue`. |
| 141 | |
| 142 | Send `branch` to open the pull request from a branch already pushed to the |
| 143 | repository. No fork is made, `git.remote` is the repository itself, and the |
| 144 | pull request is `open` at once: |
| 145 | |
| 146 | ```json |
| 147 | { |
| 148 | "pull": { "number": 15, "status": "open", "branch": "my-change", "fork": null }, |
| 149 | "git": { "remote": "https://g1t.sh/syntaqx/hello.git" } |
| 150 | } |
| 151 | ``` |
| 152 | |
| 153 | Merging a pull request made for an issue closes the issue and records the |
| 154 | pull request in the issue's `resolvedBy`. Other pull requests for that issue |
| 155 | that are still a draft or open are closed with `supersededBy` set. Send |
| 156 | `"keep_issue_open": true` to merge without any of that. |
| 157 | |
| 158 | ## Sessions |
| 159 | |
| 160 | | Method | Path | | |
| 161 | | --- | --- | --- | |
| 162 | | `GET` | `/v1/repos/{owner}/{name}/pulls/{number}/session?after=` | Entries after a sequence number. | |
| 163 | | `POST` | `/v1/repos/{owner}/{name}/pulls/{number}/session` | Append. Body: `entries`. | |
| 164 | |
| 165 | ```sh |
| 166 | curl -X POST https://api.g1t.sh/v1/repos/syntaqx/hello/pulls/14/session \ |
| 167 | -H "Authorization: Bearer $G1T_TOKEN" \ |
| 168 | -H "Content-Type: application/json" \ |
| 169 | -d '{"entries": [{"kind": "message", "text": "Reading src/main.rs."}]}' |
| 170 | ``` |
| 171 | |
| 172 | Up to 200 entries can be appended per request. |
| 173 | |
| 174 | ## Identifiers and times |
| 175 | |
| 176 | Issues and pull requests are addressed by repository and number. Other ids |
| 177 | are [TypeIDs](https://github.com/jetify-com/typeid): a prefix naming the |
| 178 | kind of thing, then a UUIDv7 in base32, for example |
| 179 | `pr_01jb2k7x9hfq0b3zj0f5s2m8ra`. They sort by creation time. |
| 180 | |
| 181 | Times are RFC 3339 in UTC, such as `2026-10-01T18:04:11.482Z`. |