| 1 | --- |
| 2 | title: Git |
| 3 | description: Remotes, credentials, private repositories, deploy keys and limits. |
| 4 | --- |
| 5 | |
| 6 | g1t speaks git's smart HTTP protocol. Any git client works. |
| 7 | |
| 8 | ## Remotes |
| 9 | |
| 10 | ```text |
| 11 | https://g1t.sh/<workspace>/<repo>.git |
| 12 | ``` |
| 13 | |
| 14 | Public repositories can be cloned without signing in: |
| 15 | |
| 16 | ```sh |
| 17 | git clone https://g1t.sh/flagon-io/g1t.git |
| 18 | ``` |
| 19 | |
| 20 | If the workspace is [renamed](/guides/workspaces/#rename-a-workspace), the |
| 21 | old remote redirects to the new one for 90 days. Git follows the redirect |
| 22 | and warns about it; point the remote at the new address: |
| 23 | |
| 24 | ```sh |
| 25 | git remote set-url origin https://g1t.sh/<new-workspace>/<repo>.git |
| 26 | ``` |
| 27 | |
| 28 | ## Authentication |
| 29 | |
| 30 | Pushing, and reading private repositories, needs credentials. Use your |
| 31 | username, and as the password either your account password or an |
| 32 | [access token](https://g1t.sh/settings/tokens). Tokens are recommended: they can be revoked |
| 33 | individually and they also work for the API. |
| 34 | |
| 35 | To avoid typing it each time, let git store it: |
| 36 | |
| 37 | ```sh |
| 38 | git config --global credential.helper store |
| 39 | ``` |
| 40 | |
| 41 | ## Creating a repository by pushing |
| 42 | |
| 43 | Pushing to a repository that does not exist, in a workspace you belong to, |
| 44 | creates it as a private repository, so nothing pushed by mistake is |
| 45 | published. To make it public, see |
| 46 | [change who can see a repository](/guides/managing-repositories/#change-who-can-see-a-repository). |
| 47 | |
| 48 | ```sh |
| 49 | git push https://g1t.sh/<workspace>/new-repo.git main |
| 50 | ``` |
| 51 | |
| 52 | ## Private repositories |
| 53 | |
| 54 | A private repository is visible only to people with a |
| 55 | [role](/guides/access-and-roles/) on it. Cloning and fetching need |
| 56 | Read, and pushing needs Write. To |
| 57 | everyone else it looks exactly like a repository that does not exist, both |
| 58 | on the site and to git. |
| 59 | |
| 60 | On the site, an address you cannot see gives the same page either way, with |
| 61 | status 404: |
| 62 | |
| 63 | | You are | The page says | |
| 64 | | --- | --- | |
| 65 | | Signed out | **Nothing here**: this page doesn't exist, or it's private; sign in if it's yours. **Sign in** brings you back to the same address. | |
| 66 | | Signed in | **Nothing here**: this page doesn't exist, or you don't have access to it, with which account you are signed in as and a link to switch account. If you should have access, ask someone with the Admin role on it to add you. | |
| 67 | |
| 68 | Issues, pull requests and workspace pages work the same way. The sidebar |
| 69 | does not open the project or workspace the address names, so nothing on |
| 70 | the page hints at whether it exists; a missing file, commit or issue in a |
| 71 | project you can see keeps that project's sidebar. A profile that does not |
| 72 | exist says **No one on g1t goes by that name**, since profiles are public. |
| 73 | |
| 74 | ## Download a ZIP |
| 75 | |
| 76 | On a repository's **Files** page, **Code** → **Download ZIP** downloads the |
| 77 | branch shown as one zip, its files in a folder named `<repo>-<branch>`. It |
| 78 | works for anyone who can see the repository, and for any branch, tag or |
| 79 | commit at `g1t.sh/<workspace>/<repo>/archive/<ref>.zip`. A ZIP holds the |
| 80 | files, not the history; clone for that. A repository with more than 10,000 |
| 81 | files or over 24 MB is too large to download this way, so clone it instead. |
| 82 | |
| 83 | ## Raw files |
| 84 | |
| 85 | **Raw**, above a file on its page, opens the file as it is, with nothing |
| 86 | around it. Any file is at: |
| 87 | |
| 88 | ```text |
| 89 | https://g1t.sh/<workspace>/<repo>/raw/<branch, tag or commit>/<path> |
| 90 | ``` |
| 91 | |
| 92 | That address sends you on to the commit the branch or tag names now, on |
| 93 | g1t's file host: |
| 94 | |
| 95 | ```text |
| 96 | https://g1tusercontent.com/<workspace>/<repo>/raw/<commit>/<path> |
| 97 | ``` |
| 98 | |
| 99 | g1tusercontent.com is a site of its own so that nothing in a repository |
| 100 | can reach your g1t.sh session: it never receives g1t.sh's cookies, and a |
| 101 | file opened there cannot run script. Images, video, audio and PDFs are |
| 102 | served as themselves; any other text, HTML, SVG source, XML and |
| 103 | JavaScript included, as plain text; anything else as a download. A file |
| 104 | is served up to 10 MB; clone the repository for larger ones. |
| 105 | |
| 106 | - **Public repositories.** The address works for anyone, and an address |
| 107 | at a commit can be kept for good. |
| 108 | - **Private repositories.** The address carries a `token` that g1t.sh |
| 109 | makes for someone who can read the repository. It is good for that one |
| 110 | file for an hour or two; after that, open the file on g1t.sh again for a |
| 111 | new address. Revoking someone's access does not end an address they |
| 112 | already have before then. |
| 113 | |
| 114 | Images in a file's page show from its raw address, and so do pictures in a |
| 115 | README that name a file in the repository (``, |
| 116 | relative to the README's folder, or `/docs/diagram.png` from the |
| 117 | repository's root): they show the file at the commit the page shows. |
| 118 | Uploaded avatars are on the same host, at |
| 119 | `https://g1tusercontent.com/avatars/<sha256>`. |
| 120 | |
| 121 | ## Browsing without an account |
| 122 | |
| 123 | Public projects, Explore, Search and profiles are open to everyone, in the |
| 124 | same sidebar members use. Signed out, the sidebar has Explore and Search, |
| 125 | and in a project its Code, Issues, Pull requests, Agents, Workflows and |
| 126 | Deployments; pages only people with a role on the repository see, such as |
| 127 | Security and Settings, are left out. **Sign in** and **Sign up** sit at the bottom, and both bring you |
| 128 | back to the page you were on. |
| 129 | |
| 130 | ## Protected branches |
| 131 | |
| 132 | A repository protects its branches and tags with [rulesets](/guides/rules/), |
| 133 | under **Settings → Rules**. A push that breaks a rule is refused for |
| 134 | everyone, whatever their role, and for agents, unless a ruleset lists them |
| 135 | as able to bypass it. Git prints which ruleset and rule refused it, and how |
| 136 | to fix it: |
| 137 | |
| 138 | ```text |
| 139 | remote: error: rules for refs/heads/main declined this push: |
| 140 | remote: - Changes to main must be made through a pull request. [ruleset "Protect main", pull_request] |
| 141 | remote: Push a branch, open a pull request into main, and merge it. |
| 142 | ! [remote rejected] main -> main (declined by ruleset "Protect main" (pull_request)) |
| 143 | ``` |
| 144 | |
| 145 | With **Require a pull request before merging**, changes reach a branch only |
| 146 | by merging a pull request. Creating the branch, such as the first push to |
| 147 | an empty repository, is still allowed. Rulesets also block force pushes and |
| 148 | deletions, restrict who creates branches and tags, check commit messages, |
| 149 | signatures and the files a push changes, and set what a merge needs. See |
| 150 | [rules](/guides/rules/). |
| 151 | |
| 152 | ## Branches |
| 153 | |
| 154 | Push any branch to a repository you can write to, and open a |
| 155 | [pull request](/concepts/overview/#pull-requests) from it on the |
| 156 | repository's **Pull requests** tab. |
| 157 | |
| 158 | ```sh |
| 159 | git switch -c my-change |
| 160 | git push origin my-change |
| 161 | ``` |
| 162 | |
| 163 | A repository's **Branches** tab, `g1t.sh/<workspace>/<repo>/branches`, lists |
| 164 | every branch: the default one first, then those with a commit in the last 90 |
| 165 | days (**Active**), then the rest (**Stale**). Each shows its last commit, how |
| 166 | many commits it is ahead of and behind the default branch, the pull request |
| 167 | open on it with its checks, and its preview when it has one. A branch with |
| 168 | no pull request links to opening one; one with nothing the default branch |
| 169 | lacks (ahead `0`) says **Nothing to merge** instead. The counts are exact, |
| 170 | merges included. g1t reads up to 1,000 commits of each history to find |
| 171 | where the two meet; a branch that left the default branch further back |
| 172 | than that shows no counts. Search narrows the list by name. |
| 173 | |
| 174 | The **Tags** tab lists tags newest first, up to 100, each with its commit |
| 175 | and a ZIP of its files. |
| 176 | |
| 177 | **Compare**, `g1t.sh/<workspace>/<repo>/compare/<base>...<head>`, shows |
| 178 | what one branch has that another does not: its commits, then every change. |
| 179 | Pick the two branches at the top; **Open a pull request** starts one from |
| 180 | the compared branch. |
| 181 | |
| 182 | On **Files**, each file and folder shows the commit that last changed it and |
| 183 | when, from the branch's whole history. On a long history the first view |
| 184 | can show some of them blank while g1t finishes reading it; a later view |
| 185 | fills them in, and after a push only the new commits are read. The branch menu at the top switches branch and keeps the |
| 186 | folder or file you are on. |
| 187 | |
| 188 | ## Pull request forks |
| 189 | |
| 190 | A pull request that was not opened from a branch has its own remote: |
| 191 | |
| 192 | ```text |
| 193 | https://g1t.sh/pulls/<pull request id>.git |
| 194 | ``` |
| 195 | |
| 196 | Only whoever opened the pull request can push to it, or, for one g1t |
| 197 | made, whoever asked for it. Pushes to a fork |
| 198 | update the pull request's head commit on its page. |
| 199 | |
| 200 | ## Limits |
| 201 | |
| 202 | ### Size limits |
| 203 | |
| 204 | Repositories are stored in Cloudflare Artifacts. g1t checks its limits |
| 205 | before a push is stored, and declines a push that would cross one. git |
| 206 | prints the reason beside each branch (`! [remote rejected] main (…)`), and |
| 207 | what to do as `remote:` lines. Nothing in a declined push is stored. |
| 208 | |
| 209 | | Limit | Size | What happens past it | |
| 210 | | --- | --- | --- | |
| 211 | | A file | 32 MB | The push is declined, naming the file's size. | |
| 212 | | A repository, with its pull requests' forks | 950 MB, as g1t counts what was pushed (the store holds 1 GB) | The push is declined; once full, pushes are refused with the reason before any data is sent. | |
| 213 | | A push that push protection can scan before it lands | Most pushes; very large ones are scanned after they land | A very large push goes through and is scanned after it lands; secrets found are open alerts. To have it checked first, push in parts, oldest commits first. | |
| 214 | | A push | 100 MB | Refused by the network with HTTP `413` before g1t sees it. | |
| 215 | |
| 216 | To push a large history in parts: |
| 217 | |
| 218 | ```sh |
| 219 | git rev-list --reverse HEAD | awk 'NR % 500 == 0' | xargs -I{} git push origin {}:refs/heads/main |
| 220 | git push origin main |
| 221 | ``` |
| 222 | |
| 223 | Each push sends only what the one before did not. |
| 224 | |
| 225 | ### Pushes of many branches or tags |
| 226 | |
| 227 | Every branch and tag in a push is stored. Each one is also announced as a |
| 228 | `git.push` event, which starts workflows, mirrors the repository and |
| 229 | calls webhooks, except in a push of many: |
| 230 | |
| 231 | | A push of | What is announced | |
| 232 | | --- | --- | |
| 233 | | Up to 3 tags | Each tag | |
| 234 | | More than 3 tags (`git push --tags`, say) | None of the tags | |
| 235 | | Up to 1,000 branches | Each branch | |
| 236 | | More than 1,000 branches | Only the default branch, if it moved | |
| 237 | |
| 238 | To have tags start workflows, push them 3 or fewer at a time. |
| 239 | |
| 240 | ### When the store is busy |
| 241 | |
| 242 | If Cloudflare Artifacts is rate limiting g1t or not answering, g1t tries |
| 243 | reads again for a moment, then answers git with HTTP `429` (rate limited) |
| 244 | or `503` (unavailable) and a `Retry-After` header saying how many seconds |
| 245 | to wait. Pushes are never tried again on your behalf: run `git push` |
| 246 | again. On g1t.sh the page says the git storage is busy instead of failing, |
| 247 | and [status.g1t.sh](https://status.g1t.sh) shows **Git storage**. |
| 248 | |
| 249 | ### Git operations |
| 250 | |
| 251 | Each clone, fetch and push is a git operation, your agents' included: |
| 252 | their sandboxes use the same git endpoints you do, and a pull request's |
| 253 | working copy counts for its repository's workspace. Every workspace has 50,000 |
| 254 | a month included. Past that, a workspace on the g1t plan pays $0.18 per |
| 255 | 1,000, and a free workspace is never charged: past 50,000 in a month, its |
| 256 | git requests past 60 in an hour are answered `429` with when to try again, |
| 257 | until the month turns. Counting starts on 2026-10-14. See |
| 258 | [git operations](/guides/usage-and-billing/#git-operations). |
| 259 | |
| 260 | ### Request limits |
| 261 | |
| 262 | Git requests without credentials are limited to 120 a minute from each IP |
| 263 | address, about 40 clones; with credentials, 1,200 a minute for each set of |
| 264 | credentials. Anonymous clones of one repository that g1t has not cached |
| 265 | are limited to 120 a minute, whoever makes them. Past a limit, git is |
| 266 | answered `429` with a message saying to wait a minute. Clone with |
| 267 | [credentials](#authentication) to count against your own limit. See |
| 268 | [rate limits](/reference/rate-limits/). |
| 269 | |
| 270 | What these limits mean in practice, and what to do instead, is on |
| 271 | [What g1t can't do yet](/about/limitations/#git). |
| 272 | |
| 273 | ## Where a slow request's time went |
| 274 | |
| 275 | Every answer g1t gives git carries a `Server-Timing` header: how many |
| 276 | milliseconds each step of the request took. To see it, run git with its |
| 277 | HTTP trace on: |
| 278 | |
| 279 | ```sh |
| 280 | GIT_TRACE_CURL=1 git ls-remote https://g1t.sh/<owner>/<repo>.git 2>&1 | grep -i server-timing |
| 281 | ``` |
| 282 | |
| 283 | | Step | What it is | |
| 284 | | --- | --- | |
| 285 | | `repo` | Finding the repository, and checking your credentials if you sent any | |
| 286 | | `moved` | Only for an address with no repository: looking for a renamed workspace or a transferred repository to send you to | |
| 287 | | `access` | Deciding whether you may fetch from or push to it | |
| 288 | | `kept` | A free workspace's limits, and looking for a ref listing and a store credential made a moment ago | |
| 289 | | `mint` | Only when no credential was kept: the git store making one for the request | |
| 290 | | `store` | The git store's answer to a clone or fetch | |
| 291 | | `recv` | Only for a push: receiving it from git | |
| 292 | | `checks` | Only for a push: checking it against the rules of the branches and tags it changes, for workflow files a token may not change, and for secrets and private email addresses, all at once | |
| 293 | | `upload` | Only for a push: handing it to the git store and its answer | |
| 294 | | `refs` | Only for a push: recording that the repository's refs changed | |
| 295 | | `total` | Everything g1t did | |
| 296 | | `repos` | The same, measured where your request arrived | |
| 297 | |
| 298 | A push's checks run side by side, so each also has its own entry, after |
| 299 | the steps and not counted in the total: `read` (reading the push's |
| 300 | objects), `rules`, `scan` (secrets and email addresses) and, for an access |
| 301 | token, `gate` (workflow files). |
| 302 | |
| 303 | g1t asks git to send each push whole: every object a delta in it builds on |
| 304 | is in the push too (the `no-thin` capability), so checking a push reads |
| 305 | nothing from the repository. A push that changes a large file a little |
| 306 | uploads more than it would otherwise, and stays within the |
| 307 | [size limits](#size-limits). If your git sends a push that builds on |
| 308 | objects outside it anyway, g1t reads up to 200 of those from the |
| 309 | repository to check it, and says `thin;desc=yes`. |
| 310 | |
| 311 | Some entries say how a step went rather than how long it took: |
| 312 | |
| 313 | | Entry | Values | |
| 314 | | --- | --- | |
| 315 | | `refs;desc=` | `hit-colo` or `hit-shared` when the ref listing came from g1t's cache, `miss` when the git store was asked | |
| 316 | | `pack;desc=` | Only for a fresh clone: `hit` when its pack came from g1t's cache, `miss` when the git store built it | |
| 317 | | `cred;desc=` | `isolate` or `shared` for a store credential made a moment ago, `mint` for a new one | |
| 318 | | `thin;desc=` | Only for a push: `no` when it came whole, `yes` when it built on objects outside it | |
| 319 | |
| 320 | The ref listing git asks for first on every clone and fetch is kept for up |
| 321 | to a minute, and only the same question about the same refs gets the same |
| 322 | answer: a push, a merge or any other change to a repository's branches and |
| 323 | tags makes the next fetch ask the git store again. A change can take up to |
| 324 | 5 seconds to reach every fetch. |
| 325 | |
| 326 | A fresh clone, one that has no objects yet (shallow clones such as |
| 327 | `git clone --depth=1` included), has its pack kept too, for up to 7 days |
| 328 | or until the repository's branches or tags next change. The next clone that |
| 329 | asks for the same commits in the same way gets the same pack without the |
| 330 | git store building it again. A fetch into a repository you already have, |
| 331 | and any pack over 200 MB, always goes to the git store. |
| 332 | |
| 333 | Include the header when you report a slow clone, fetch or push. |
| 334 | |
| 335 | ## SSH |
| 336 | |
| 337 | Git over SSH is not available yet. Use HTTPS, which works for clone, |
| 338 | fetch and push everywhere SSH would. |
| 339 | |
| 340 | Why: git over SSH needs raw TCP connections on port 22, and g1t runs |
| 341 | entirely on Cloudflare's network. Accepting inbound TCP traffic directly |
| 342 | into Workers is in a beta from Cloudflare that g1t has applied for and is |
| 343 | waiting on. SSH keys can already be added under |
| 344 | [Settings → SSH keys](https://g1t.sh/settings/keys), |
| 345 | and will be used once SSH is on. So can [deploy keys](#deploy-keys). |
| 346 | |
| 347 | ## Deploy keys |
| 348 | |
| 349 | A deploy key is an SSH key that reaches one repository and nothing else. |
| 350 | Give one to a server or a pipeline that needs to clone a repository, or |
| 351 | push to it, without a person's account behind it. A deploy key belongs to |
| 352 | the repository: it keeps working when the person who added it leaves the |
| 353 | workspace, and it is not tied to anyone's role. |
| 354 | |
| 355 | :::note |
| 356 | Deploy keys are used over SSH, which is [not on yet](#ssh). You can add |
| 357 | them now, and they will work as soon as SSH is. Until then, a machine can |
| 358 | clone and push over HTTPS with a |
| 359 | [workspace access token](/guides/workspaces/#workspace-access-tokens). |
| 360 | ::: |
| 361 | |
| 362 | ### Read-only or read and write |
| 363 | |
| 364 | A deploy key is read-only unless you choose **Allow write access** when |
| 365 | you add it: |
| 366 | |
| 367 | | Access | It can | |
| 368 | | --- | --- | |
| 369 | | **Read-only** (the default) | Clone and fetch the repository, private or not. | |
| 370 | | **Read and write** | Clone, fetch and push, workflow files under `.g1t/workflows/` and `.github/workflows/` included. | |
| 371 | |
| 372 | Either way it reaches only its own repository: any other address is |
| 373 | refused, in its workspace or anywhere else, and so is pushing to an |
| 374 | address with no repository, which would otherwise make one. |
| 375 | |
| 376 | A key with write access can change workflows, and workflows run with the |
| 377 | repository's secrets. Allow it only for a machine that must push. You |
| 378 | cannot change a key's access later: delete it and add it again. |
| 379 | |
| 380 | ### Add a deploy key |
| 381 | |
| 382 | You need the Admin role on the repository and a confirmed email address. |
| 383 | |
| 384 | 1. Make a key pair on the machine that will use it, without a passphrase |
| 385 | if it runs unattended: |
| 386 | |
| 387 | ```sh |
| 388 | ssh-keygen -t ed25519 -C "deploy@build-server" -f ~/.ssh/g1t_deploy -N "" |
| 389 | ``` |
| 390 | |
| 391 | 2. Open the repository's **Settings → Deploy keys**, |
| 392 | `g1t.sh/<workspace>/<repo>/settings/keys`. |
| 393 | 3. Under **Add a deploy key**, give it a **Title**, such as the machine that |
| 394 | uses it, and paste the public key (`~/.ssh/g1t_deploy.pub`) into **Key**. |
| 395 | Left without a title, it takes the key's comment. |
| 396 | 4. Choose **Allow write access** only if the machine must push. |
| 397 | 5. Choose **Add deploy key**. |
| 398 | |
| 399 | g1t takes `ssh-ed25519`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, |
| 400 | `ecdsa-sha2-nistp521` and `ssh-rsa` keys. A public key can be registered |
| 401 | once on g1t: a key that is already someone's SSH key, or a deploy key on |
| 402 | any repository, is refused with **Key is already in use.** Give each |
| 403 | machine, and each repository, its own key. A repository can have up to |
| 404 | 100 deploy keys. |
| 405 | |
| 406 | ### Manage deploy keys |
| 407 | |
| 408 | **Settings → Deploy keys** lists every key on the repository, oldest |
| 409 | first, with its title, fingerprint, whether it is **Read-only** or **Read |
| 410 | and write**, who added it and when, and when it was last used. **Last |
| 411 | used** is when the key last signed in over SSH, to within 5 minutes; |
| 412 | **Never used** means it never has. Use it to find keys nothing uses any |
| 413 | more. |
| 414 | |
| 415 | To remove a key, choose **Delete** beside it and confirm. Anything using |
| 416 | it stops at once. |
| 417 | |
| 418 | Only people with the Admin role on the repository see the page and |
| 419 | manage its keys. An agent's token never can, and neither can a deploy key. |
| 420 | A workspace's own access token can only when an owner gave it Admin. See |
| 421 | [access and roles](/guides/access-and-roles/#deploy-keys). Adding and |
| 422 | deleting a key is recorded in the workspace's |
| 423 | [audit log](/guides/audit-log/) as `repo.deploy_key_added` and |
| 424 | `repo.deploy_key_removed`. |
| 425 | |
| 426 | Deploy keys are kept by repository, so renaming or transferring the |
| 427 | repository keeps them. While a repository is deleted its keys do not work, |
| 428 | and when it is removed for good they go with it. |
| 429 | |
| 430 | ### Use a deploy key with git |
| 431 | |
| 432 | Once SSH is on, point git at the key for the repository's remote: |
| 433 | |
| 434 | ```sh |
| 435 | GIT_SSH_COMMAND="ssh -i ~/.ssh/g1t_deploy -o IdentitiesOnly=yes" \ |
| 436 | git clone git@g1t.sh:<workspace>/<repo>.git |
| 437 | ``` |
| 438 | |
| 439 | Or name it in `~/.ssh/config` for every git command on that machine: |
| 440 | |
| 441 | ```text |
| 442 | Host g1t.sh |
| 443 | User git |
| 444 | IdentityFile ~/.ssh/g1t_deploy |
| 445 | IdentitiesOnly yes |
| 446 | ``` |
| 447 | |
| 448 | A machine that needs several repositories needs a key for each; give each |
| 449 | a `Host` alias with its own `IdentityFile`. |
| 450 | |
| 451 | ### Through the API |
| 452 | |
| 453 | | Route | MCP tool and action | What it does | |
| 454 | | --- | --- | --- | |
| 455 | | `GET /repos/{owner}/{name}/keys` | `access` `list_deploy_keys` | The repository's deploy keys. | |
| 456 | | `GET /repos/{owner}/{name}/keys/{id}` | `access` `get_deploy_key` | One key, by its `id`. | |
| 457 | | `POST /repos/{owner}/{name}/keys` | `access` `add_deploy_key` | Add a key. Body: `title`, `key` and `read_only` (true unless you send false). | |
| 458 | | `DELETE /repos/{owner}/{name}/keys/{id}` | `access` `remove_deploy_key` | Delete a key. | |
| 459 | |
| 460 | Listing and reading need the `access:read` scope; adding and deleting |
| 461 | need `access:admin`. Each needs the Admin role on the repository. |
| 462 | |
| 463 | ```sh |
| 464 | curl https://api.g1t.sh/repos/acme/rocket/keys \ |
| 465 | -H "Authorization: Bearer $G1T_TOKEN" \ |
| 466 | -H "Content-Type: application/json" \ |
| 467 | -d '{"title": "Build server", "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE", "read_only": true}' |
| 468 | ``` |
| 469 | |
| 470 | ```json |
| 471 | { |
| 472 | "id": "dk_01kp3f2g3h4j5k6m7n8p9q0r1s", |
| 473 | "title": "Build server", |
| 474 | "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE", |
| 475 | "fingerprint": "SHA256:ubxEl41fJDnUoEPKSZE0y6R0ZjjAQf/wV5vZgeBV8qk", |
| 476 | "read_only": true, |
| 477 | "created_at": "2026-10-08T09:12:00.000Z", |
| 478 | "created_by": "ada", |
| 479 | "last_used_at": null |
| 480 | } |
| 481 | ``` |
| 482 | |
| 483 | See [create a deploy key](/reference/api/access/create-deploy-key/) in the |
| 484 | API reference. |