Repositories
- Create a repository
- Find a repository
- List the repositories that are yours
- Rename a repository or change its description
- Change who can see a repository
- Give a repository an avatar
- Add someone to a repository
- See who has direct access
- Change or remove someone's access
- Browse the files
- See what a repository has
- Search the code
- See when each file last changed
- Read the history
- Tag a commit
- Delete a tag
- See what a repository is written in
- See who has contributed
- Look at a contribution heatmap
- Star a repository
- Propose a CODEOWNERS file
- Move a repository to another group
- Delete a repository
Overview
A repository holds your code and everything the platform knows about it: who may read and write it, what it is written in, who has contributed, and every commit, tag and file in its history. You create one inside a group, push to it over git, and from then on you can read it without a clone — a file at a ref, a page of history, the tag a release was cut from — and hand out access one person at a time.
The same repository is also the unit almost everything else attaches to. Merge requests, work items, pipelines, releases, branch rules and service accounts all live inside one, so getting its group, its visibility and its members right is the thing you do first and rarely have to revisit.
Quick start
Create a repository
Choose Create in the top bar, then New repository — or New repository on a group's own page.
Pick the group and a name: lowercase letters, digits and hyphens. The full path updates as you type.
Choose Create repository. You land on the empty repository, which shows the exact commands for pushing to it.
Push an existing repository
In your existing clone, add this repository as a remote:
git remote add origin gitmoap://git.example.com/acme/platform/checkoutPush your default branch and track it. The files and the history appear as soon as the push lands.
git push -u origin main
Start from an empty folder
Make a folder with a first commit in it:
mkdir checkout && cd checkout git init -b main echo "# checkout" > README.md git add README.md && git commit -m "First commit"
Point it at this repository and push. The repository's home page shows the README once the push lands.
git remote add origin gitmoap://git.example.com/acme/platform/checkout git push -u origin main
Give someone access to a repository
Start at Members in the repository's side navigation.
Choose Add member.
Find the person by name, username or email, and pick the lowest level that does the job — Reporter reads, Developer pushes, Maintainer manages.
Choose Add member. The access applies straight away.
Tag a release point
Tag the commit you want to mark, with a message saying what it is:
git tag -a v1.0.0 -m "First release"
Push the tag. It appears under Tags, ready to cut a release from.
git push origin v1.0.0
Add a README
Write a
README.mdat the root of your clone that says what the repository is for and how to use it.Commit it and push it to the default branch. It renders on the repository's home page.
git add README.md && git commit -m "Add a README" && git push origin main
Before you start
- A group to create it in.Every repository belongs to exactly one group, and you need Maintainer on that group to create a repository inside it. Signing up gives you a personal namespace you already own, so you always have somewhere to start.
- The access level for what you are doing.Reading follows visibility. Creating and deleting a tag both need Developer. Renaming, describing, uploading an avatar and managing members need Maintainer. Changing visibility, transferring and deleting the repository need Owner, and so does any membership change that grants or removes the Owner level. A transfer needs Owner on the repository and Maintainer on the group you are moving it to.
- A token with the right scopes, if you are calling the API or the CLI instead of clicking:
repo:readto read,repo:create,repo:update,repo:transfer,repo:deleteandrepo:starfor the matching actions,repo_member:read/:add/:update/:removefor membership,commit:read,tag:read/tag:create/tag:delete,tree:readfor files,user:readfor your own or another person's contribution heatmap,service_account:readfor a service account's,merge_request:readfor a merge request's participants, andcatalog:readfor a service's language breakdown. Scopes are fixed when a token is minted, so give a script only the ones it needs. - git, and the remote helper on your
PATH, if you intend to push. A new repository is created empty;git remote add origin gitmoap://<group>/<repo>thengit pushfills it. - Ids, not paths, for most calls.Almost every endpoint takes the repository's id. If all you have is
acme/platform/checkout, look it up first — there is a by-path read and a path resolver for exactly this.
How it works
Where a repository lives, and what its name means
Groups nest, repositories do not. A repository's full path is its group's path plus its own name, and that path is unique across every group and every repository on the platform — so a group and a repository can never claim the same one.
acme group (the namespace: quotas and storage are counted here)└── platform group ├── checkout repository full path: acme/platform/checkout └── billing repository full path: acme/platform/billingThe path is a display and navigation concern. The id is the identity: it never changes, not when you rename the repository, not when you move it to another group. That is why the API takes ids — a script holding an id keeps working through a reorganisation, and a script holding a path breaks the moment somebody renames a group above it.
Who can do what
Two things decide it, and they are independent.
Visibility decides who can read at all. A public repository answers anyone, signed in or not. An internal one answers anyone signed in. A private one answers only its members, and answers everyone else 404 rather than 403 — so a stranger cannot use the API to discover that a repository exists. A repository can never be more visible than the group above it; the platform refuses the change rather than quietly widening the group.
Access level decides what you can do once you are in. The five levels are ordered — Guest, Reporter, Developer, Maintainer, Owner — and your effective level on a repository is the highest of your direct membership on it and your membership on any group above it. A Developer on acme/platform who is also a direct Maintainer on acme/platform/checkout is a Maintainer there.
| You want to | You need |
|---|---|
| See that a private repository exists | Guest |
| Read files, history, tags, contributors | Reporter, or anyone signed in through visibility on a public or internal repository |
| See who the members are | Reporter — visibility does not open the member list, even on a public repository |
| Create or delete a tag | Developer |
| Rename, describe, set the avatar, manage members | Maintainer |
| Change visibility, transfer, delete the repository | Owner |
The member list shows direct members only. Somebody who reaches the repository through a group is not on it, and removing their direct membership does not take away what the group gives them.
Git data is read live
Commits, tags and files are not a second copy kept in step with git — they are read out of the repository itself on every request. That has one consequence worth knowing: what you read is exactly what a clone would show, including after a force-push, and there is nothing to resynchronise when history is rewritten.
Because file and tree reads are addressed by a ref, asking for main gets you an answer that changes when main does. Ask by commit sha and the answer can never change, which is why a request naming a branch may be pointed at the same request pinned to the commit the branch resolves to. Send If-None-Match with the value you got last time and an unchanged ref costs you a header exchange instead of a body.
Two reads on this page are not live, and knowing which saves you a confusing minute. The contributor list and the language breakdown are maintained as your pushes land rather than recomputed when you ask, and both describe the default branch only. The language breakdown names the commit it describes, so you can always tell how current it is, and it rebuilds itself whenever a push is not a clean continuation of what it last indexed. The contributor list has no equivalent repair: a force-push that rewinds the default branch leaves it crediting commits nothing reaches any more.
How to
Create a repository
You need Maintainer on the group it goes in. The name is a slug — lowercase letters, digits and hyphens, one to a hundred characters, no leading or trailing hyphen — and the display name is the free-text one people read. Leave visibility out and the repository inherits the group's; set it and it must be no wider than the group's.
You are added as Owner in the same act, so a repository can never exist without somebody able to manage it. It is created empty, with main as its default branch.
Choose New repository from the + menu in the top bar, or New repository on a group's own page. Pick the group, give the repository a name — the path underneath the field updates as you type, so you can see the full path you are claiming — and optionally a display name and description. Visibility is preset to the group's and cannot be set wider. Create repository takes you to the empty repository, which shows the commands for pushing an existing clone into it.
gitmoap repos create \ --group-id <group id> \ --repository-name <repository name>curl -X POST https://api.gitmoap.example/repositories \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "group_id": "<uuid>", "repository_name": "<string>" }'Then push into it:
git remote add origin gitmoap://acme/platform/checkoutgit push -u origin mainFind a repository
Listing is scoped to one group, and you name that group either by id or by path — one of the two, never both. You get back everything in that group you are allowed to see: a member of the group sees its private repositories, a signed-in stranger sees the public and internal ones plus any private one they are a direct member of, and an anonymous caller sees only the public ones. For a signed-in caller who is not a member of the group, private repositories are dropped after the page is cut, so a page can come back shorter than the limit you asked for. Follow next_token until a response carries none, rather than reading a short page as the end of the list.
Open a group and its Repositories tab lists what it holds, most recently created first. The listing itself has no name filter — to find a repository by name across the groups you can reach, use the search box at the top instead.
gitmoap repos listcurl -X GET https://api.gitmoap.example/repositories \ -H "Authorization: Bearer $GITMOAP_TOKEN"To read one repository, use its id. What comes back is the repository's own record — its path, display name and description, its visibility and default branch, its star count and its measured size — and empty, which is true while the repository has no branch and no tag. Read empty before you ask for files: a repository nothing has been pushed to has no tree at any ref, so the thing to show is how to push to it. remote_url is the address to push it at: gitmoap:// and its path, with Gitmoap's host in front. Print it rather than assembling your own.
Open the repository from any listing, or from a link. The header carries its path, its visibility and its star count; everything else hangs off the tabs beneath it.
gitmoap repos get --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"To go from a path a colleague pasted into a chat, use the by-path read.
Paste the path into the address bar after the site's host — acme/platform/checkout resolves to the repository page directly, which is why paths are what people share.
gitmoap repos get-by-path --full-path <id>curl -X GET https://api.gitmoap.example/repositories/by-path/{full_path} \ -H "Authorization: Bearer $GITMOAP_TOKEN"When you do not know whether a path is a group or a repository — building a breadcrumb, or handling a pasted link — resolve it first. The resolver answers with the type and the id, and follows the same visibility rules as everything else.
Nothing to click: the web app does this for you when you open a path, and shows a group page or a repository page accordingly.
gitmoap resolve resolve-path --full-path <id>curl -X GET https://api.gitmoap.example/resolve/{full_path} \ -H "Authorization: Bearer $GITMOAP_TOKEN"List the repositories that are yours
Two personal listings, and they answer different questions. One returns every repository you reach through membership — direct, or through any group above it — most recently active first. A public repository you can read but have no membership on is not yours and does not appear.
Repositories in the left sidebar of your dashboard lists them, most recently active first, with the group path under each name.
gitmoap repos list-mycurl -X GET https://api.gitmoap.example/user/repositories \ -H "Authorization: Bearer $GITMOAP_TOKEN"The other returns the repositories you starred, most recently starred first.
Starred in the left sidebar of your dashboard.
gitmoap repos list-my-starredcurl -X GET https://api.gitmoap.example/user/starred \ -H "Authorization: Bearer $GITMOAP_TOKEN"Rename a repository or change its description
Renaming changes the full path immediately, and the old path stops resolving — anything that hard-codes it, including a git remote, needs updating. The id is unaffected, so anything holding the id keeps working. If the new path is already taken by a group or a repository, the rename is refused and nothing changes.
Open the repository, then Settings → General. Edit the name, display name or description and choose Save changes. Renaming shows the new full path before you confirm.
gitmoap repos update --repo-id <id>curl -X PUT https://api.gitmoap.example/repositories/{repo_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Change who can see a repository
Visibility is the one setting on this screen that needs Owner rather than Maintainer, because widening it publishes code — and Owner is what the change costs in both directions, narrowing included. Narrowing is otherwise unconstrained; widening reaches only as far as the group above allows, so a private group cannot hold a public repository, and the request is refused rather than partially applied.
Open the repository, then Settings → General → Visibility. The options wider than the parent group's are disabled, with the group named beside them. Changing to a narrower level asks you to confirm, since people who could read the repository a moment ago no longer can.
gitmoap repos update --repo-id <id>curl -X PUT https://api.gitmoap.example/repositories/{repo_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Give a repository an avatar
Uploading is two steps: ask for an upload link, declaring the exact byte size and content type, then send the image to that link. The size you declare is bound into the link, so a file that grew between the two steps is rejected rather than stored. Images may be PNG, JPEG, GIF or WebP, and at most 5 MiB. The link expires five minutes after it is issued, so upload straight away rather than holding on to it.
Open the repository, then Settings → General, and choose the avatar tile. Pick an image and it uploads as soon as you confirm the crop. Anything over 5 MiB is refused before the upload starts.
gitmoap repos upload-avatar \ --repo-id <id> \ --content-length <n> \ --content-type <content type>curl -X PUT https://api.gitmoap.example/repositories/{repo_id}/avatar \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "content_length": "<int64>", "content_type": "<string>" }'Removing the avatar deletes the image you uploaded.
In Settings → General, choose Remove under the avatar tile.
gitmoap repos delete-avatar --repo-id <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/avatar \ -H "Authorization: Bearer $GITMOAP_TOKEN"Add someone to a repository
Adding a member is create-only: if they are already a direct member, the request is refused and you change their level instead. Adding somebody at Owner needs you to be an Owner; every other level needs Maintainer. The person has to have an account already — there is no invite-by-email on this call.
Adding a direct membership never takes anything away. If they already reach the repository as a Maintainer through a group, adding them as a direct Reporter changes nothing about what they can do, because the effective level is the higher of the two.
Open the repository, choose Members in the side navigation, then Add member. Start typing a name, username or email to pick the person, choose the access level, and confirm. The list shows the level you are granting and warns when the person already has a higher one through a group.
gitmoap repo-members add \ --repo-id <id> \ --access-level guest|reporter|developer|maintainer|owner \ --user-id <user id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/members \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "access_level": "<one of developer | guest | maintainer | owner | reporter>", "user_id": "<uuid>" }'See who has direct access
The member list is direct memberships only, with the level and when it was granted. Use the search fragment to narrow by username or email when a repository has a long list. Reading it needs Reporter or above on the repository, whatever its visibility: a public repository shows its code to everyone, not who works on it.
Settings → Members lists them, with the access level beside each name and a filter box above.
gitmoap repo-members list --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/members \ -H "Authorization: Bearer $GITMOAP_TOKEN"To check one specific person — a permission bug report, usually — read the single membership rather than paging the list.
Settings → Members, then use the filter box; a person with no direct membership does not appear at all, which is itself the answer.
gitmoap repo-members get --repo-id <id> --user-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/members/{user_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Change or remove someone's access
Raising or lowering a level is one call. Any change that touches the Owner level — granting it, or taking it away from somebody who holds it — needs Owner; every other change needs Maintainer. A repository may be left with no direct Owner: the Owners of its organization — the top-level group — always administer it, so demoting the last one strands nothing.
Settings → Members, then choose a new level from the dropdown at the end of the person's row. The change applies immediately.
gitmoap repo-members update \ --repo-id <id> \ --user-id <id> \ --access-level guest|reporter|developer|maintainer|ownercurl -X PUT https://api.gitmoap.example/repositories/{repo_id}/members/{user_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "access_level": "<one of developer | guest | maintainer | owner | reporter>" }'Removal is symmetric. You may always remove yourself without any elevated permission — leaving is not a privileged act — even as the last direct Owner. Removing a direct membership leaves any access the person has through a group untouched.
Settings → Members, then the bin icon at the end of the row. Your own row offers Leave repository instead.
gitmoap repo-members remove --repo-id <id> --user-id <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/members/{user_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"To see what one person is a direct member of, read their repository memberships. This one is self-only: you can ask about yourself, and nobody else.
Your own memberships appear under Settings → Memberships on your account page.
gitmoap repo-members list-repos-for-user --user-id <id>curl -X GET https://api.gitmoap.example/users/{user_id}/repositories \ -H "Authorization: Bearer $GITMOAP_TOKEN"Browse the files
A tree listing takes a ref — a branch, a tag or a commit sha, defaulting to the repository's head — and a directory path, defaulting to the root. Entries come back as files or directories with their size and mode, up to a hundred per page.
A ref that names nothing is 404 NOT_FOUND — unless the repository has no branch and no tag at all, in which case a tree listing, its commit column and a file read all answer 409 REPOSITORY_EMPTY instead, whatever ref you name. The two ask different things of you: the first means pick another ref, the second means push something first. The repository's own empty tells you the same before you ask.
Open the repository. The Code tab shows the tree at the default branch; the ref picker at the top left switches branch or tag, and clicking a directory walks into it. The breadcrumb above the listing takes you back up.
gitmoap tree list-tree --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/tree \ -H "Authorization: Bearer $GITMOAP_TOKEN"Reading a file gives you its contents, marked as text or as base64 when it is binary, with its size, its mode, and the two ids a later commit needs: the file's blob_sha and the commit_sha the ref resolved to. Text that is not valid UTF-8 comes back with lossy set — the bytes that could not be decoded are shown as replacement characters, so a copy of that content is not the file. Files over 1 MiB are refused rather than truncated — clone the repository or read them through git for those. To change a file without a clone, see Edit files in the browser.
Click a file in the Code tab. Text renders with line numbers; a binary file offers a download instead. The ref picker applies here too, so you can read the same file as it was at a tag.
gitmoap tree get-blob --repo-id <id> --path <path>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/blob \ -H "Authorization: Bearer $GITMOAP_TOKEN"Note
Ask for a file or a tree by branch name and the request may be answered by pointing you at the identical request pinned to the commit that branch resolves to. Clients that follow redirects see nothing unusual. A client that cannot follow one — a browser fetch sending an authorization header, typically — should pass redirect=false and get the pinned location as an ordinary response body.
See what a repository has
The repository's overview shows a Project info card: the README, the licence and which one it is, a changelog, a contributing guide, a code of conduct, a security policy, code owners and a service catalog — each present or missing, with Add next to a missing one — and how the repository is set up: its pipelines, whether its default branch is protected, its wiki and its latest release.
gitmoap repos get-profile --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/profile \ -H "Authorization: Bearer $GITMOAP_TOKEN"The licence is recognised by its text, compared with the common licences — MIT, Apache-2.0, the GPL family, the BSD licences and the others offered as templates — and named with its SPDX identifier. A licence that is none of them is shown as a licence file without a name, never as the nearest one.
Search the code
A search finds text in the files at a ref and returns each matching line with the two lines either side of it, the file it is in and its line number. The text is matched literally; pass regex=true to use a regular expression instead, and ignore_case=true to ignore case. Give one or more path values to search only those files or directories.
The answer names the commit it searched, so you can search again at exactly that commit. A search returns at most 200 matches and stops early on a very large result or after a few seconds; when it does, truncated is true and more matches may exist — narrow the search with a path or a more specific pattern to see them. Binary files are not searched.
The web app has no screen for this; use the CLI or the API.
gitmoap tree grep-repo --repo-id <id> --q <q>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/grep \ -H "Authorization: Bearer $GITMOAP_TOKEN"See when each file last changed
A tree listing tells you what is in a directory. A second request tells you what happened to it: for every entry, the last commit that touched it, and for a directory entry, the last commit that touched anything inside it. It also returns the last commit for the directory you are looking at, which is the line most file browsers put above the table.
Ask for the same ref, path, limit and page you asked the tree listing for, and the two responses line up entry for entry. Each row carries its path, so you can match on that instead of on position if you prefer.
The Code tab fills the Last commit and Last update columns in as they arrive, so the file list appears immediately and the commit messages follow a moment later.
gitmoap tree list-tree-last-commits --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/tree/last-commits \ -H "Authorization: Bearer $GITMOAP_TOKEN"This is the one read here that has to search backwards through history, and how far back depends on the directory: files people change often are answered in a moment, a file nobody has touched in years takes longer to find. The search gives up rather than running indefinitely, and when it does the response sets truncated and the entries it did not reach come back with no commit. That means not found yet, never never changed — every file in a repository was added by some commit.
The search stops for one of two reasons, and they are kept differently. A search that reached its limit on how many commits it reads stops at the same commit every time, so that answer is kept, and asking again for the same commit is fast. A search that ran out of time is not kept: the response says not to store it and carries no ETag, so asking again searches again, and the second search usually gets further. A file moved from one directory to another counts as a change to both.
Read the history
History is listed from a ref, newest first, and includes every commit a merge brought in: merge a branch without squashing and its commits appear under their own authors and dates, with the merge commit above them. Each commit carries its author and committer — separate identities, because a rebase or a cherry-pick keeps the original author — the message, its parents, and any tags pointing at it.
The Commits tab shows history for whichever ref the picker is on, grouped by day. Each entry links to the commit.
gitmoap commits list --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/commits \ -H "Authorization: Bearer $GITMOAP_TOKEN"To see each merge as a single entry instead, ask for first_parent=true. Only the first parent of each commit is followed, so the commits a merge brought in drop out and the history reads as what landed on the branch, one merge at a time.
Add a path and you get the history of one file or one directory instead of the whole repository. A change made on a branch and merged in is credited to whoever wrote it, on the day they wrote it, not to whoever pressed merge. Combine it with first_parent=true and you get the merges that brought changes to that path instead.
Click History while viewing a file or a directory in the Code tab.
gitmoap commits list --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/commits \ -H "Authorization: Bearer $GITMOAP_TOKEN"Pass next_token back exactly as you received it, with the same first_parent: a token from one kind of listing is refused by the other. A page deep into a long history takes longer than the first, because it is found by counting from the newest commit, and a push between two pages can show a commit twice where they meet.
A single commit can be read by full sha or by an abbreviation of at least seven characters. An abbreviation matching more than one object is treated as not found, so use the full sha in anything automated.
Click any commit in the Commits tab. Search does not index commits, so a sha you were handed elsewhere is reached by putting it in the ref picker or by opening the commit's own address.
gitmoap commits get --repo-id <id> --commit-ref <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/commits/{commit_ref} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Tag a commit
Creating a tag needs Developer. Give it a name and a target — a commit sha or a branch name, resolved when the request arrives. Include a message and you get an annotated tag carrying you as the tagger and the date; leave the message out and you get a lightweight tag, which is just a name pointing at the commit.
Tags are immutable here, as they are in git: there is no update. Moving one means deleting it and creating it again, which is deliberate — a tag that silently moves makes a release unreproducible.
Open the repository, choose Tags, then New tag. Name the tag, pick the branch or paste the commit it points at, and optionally write a message — filling that field in creates an annotated tag. Create tag returns you to the tag list.
gitmoap tags create --repo-id <id> --tag-name <tag name> --target <target>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/tags \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tag_name": "<string>", "target": "<string>" }'Listing tags supports a name filter and sorts by name ascending or descending; each entry carries the commit it points at.
Tags in the repository's side navigation. The filter box narrows by name, and the sort control flips the order.
gitmoap tags list --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/tags \ -H "Authorization: Bearer $GITMOAP_TOKEN"Click a tag in the list to see its target commit, and, for an annotated tag, who created it and what they wrote.
gitmoap tags get --repo-id <id> --tag-name <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/tags/{tag_name} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Delete a tag
Warning
Deleting a tag is immediate and cannot be undone through Gitmoap. Anything that resolves that tag — a release, a deployment pipeline, a dependency pinned to it, a colleague's git fetch — stops resolving it. Recreating a tag with the same name is not the same act: anyone who already fetched the old one keeps the old target until they prune.
Deleting needs Developer, the same level as creating, and it is refused if no tag by that name exists.
Tags, then the bin icon at the end of the tag's row. The confirmation names the tag and the commit it points at.
gitmoap tags delete --repo-id <id> --tag-name <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/tags/{tag_name} \ -H "Authorization: Bearer $GITMOAP_TOKEN"See what a repository is written in
The language breakdown is kept up to date as you push rather than computed when you ask, so it comes back with the commit it describes and the time it was written. Vendored code, generated files, documentation and everything the classifier could not place — binaries, symlinks, submodules, data and prose formats — are excluded from the bar and counted separately by reason, so a bar dominated by one language is explicable rather than mysterious. A repository that has never been indexed answers with an empty breakdown and no commit, which is a different thing from a missing repository, and a very large tree can be indexed only in part — the reply says so rather than quietly under-reporting.
The coloured bar under the repository's description on the Code tab. Hover a segment for the language, its share and its byte count; click through for the full breakdown including what was excluded and why.
gitmoap languages get-repo-languages --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/languages \ -H "Authorization: Bearer $GITMOAP_TOKEN"In a repository holding several services, the same breakdown is available for the paths one service owns, which is the useful number in a monorepo. A service spanning more than one repository reports the commit each was indexed at separately, since they are indexed independently.
Open the service from the catalog; its overview carries the same bar, computed over the paths that service owns.
gitmoap languages get-service-languages --service-id <id>curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/languages \ -H "Authorization: Bearer $GITMOAP_TOKEN"See who has contributed
Contributors are derived from the commits on the default branch, ranked by commit count, with additions, deletions and the first and last time each person committed. Work on a branch that has not landed credits nobody; merging it credits the original authors, since their commits become reachable at that moment. Co-authored-by: trailers are counted, so pairing shows up — a co-author is credited with the commit and with no lines, which is why the commit counts across a repository add up to more than the commits it holds while the line counts add up to exactly the diff it received.
Contributors are keyed by the email in the commit. When that address matches an account the entry is linked to it, and when it does not the person is still listed — an outside contributor is a contributor. The link is made when you read, so somebody who signs up long after their commits landed appears linked on the next request with no backfill.
The Contributors panel in the repository sidebar shows the top few with a total; See all opens the ranked list with a search box over names and addresses.
gitmoap contributors list-repo-contributors --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/contributors \ -H "Authorization: Bearer $GITMOAP_TOKEN"For one merge request, the participant list is the other question — everybody who authored, reviewed, approved, commented, reacted, wrote a commit, was credited as a co-author, merged or closed it, each appearing once with every role they hold.
The avatar row at the top of a merge request. Hover an avatar for the roles that person holds on it.
gitmoap contributors list-mr-participants --repo-id <id> --mr-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/participants \ -H "Authorization: Bearer $GITMOAP_TOKEN"Look at a contribution heatmap
A heatmap is the per-day count of commits, merge requests, reviews, releases and work items credited to one person, over a range you choose: a whole calendar year, or an explicit start and end no more than 366 days apart. Ask for no range at all and you get the trailing 365 days ending today. It comes back with the daily cells, the totals by type, the busiest single day and the current and longest streaks. Commits are credited to their author on the author's date, not to whoever pushed them; a work item counts once when it is filed and again when it is closed.
Your own heatmap counts everything you have done.
The calendar on your profile page. Use the year selector above it to move between years.
gitmoap contributions get-my-contributionscurl -X GET https://api.gitmoap.example/user/contributions \ -H "Authorization: Bearer $GITMOAP_TOKEN"Somebody else's is filtered to what you can see: work in repositories and groups you have no access to is not counted, so the same profile shows different totals to different viewers.
Open the person's profile from any avatar or mention. The calendar is the same control as your own, minus the work you cannot see.
gitmoap contributions get-user-contributions --user-id <id>curl -X GET https://api.gitmoap.example/users/{user_id}/contributions \ -H "Authorization: Bearer $GITMOAP_TOKEN"Service accounts are contributors too — automation that opens merge requests and publishes releases builds a history like anybody else. Reading one needs access to the service account itself.
Open the service account from its owning group or repository's Settings → Service accounts, and its profile carries the same calendar.
gitmoap contributions get-sa-contributions --sa-id <id>curl -X GET https://api.gitmoap.example/service-accounts/{sa_id}/contributions \ -H "Authorization: Bearer $GITMOAP_TOKEN"Star a repository
Starring is a private bookmark and a public count. It needs only the ability to see the repository, so anyone signed in can star a public one. Starring twice is the same as starring once — the call succeeds either way and the count moves only on the first — so a retrying script cannot inflate anything.
The Star button at the top right of the repository page. The number beside it is the total.
gitmoap repos star --repo-id <id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/star \ -H "Authorization: Bearer $GITMOAP_TOKEN"Checking a star answers starred: true, with the moment you starred it in starred_at, or starred: false. Not having starred a repository is an answer rather than an error, so the check never answers 404 for it.
The button renders as Starred when you have starred it and Star when you have not.
gitmoap repos check-star --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/star \ -H "Authorization: Bearer $GITMOAP_TOKEN"Starring, unstarring and checking a star all need that same read access. A private repository you have since been removed from therefore answers 404 to all three, and it drops out of your starred list at the same moment, because that list is filtered to what you can still see. Unstarring something you had not starred is reported as not found too.
Choose Starred on the repository page to toggle it off, or use the star control on any row of your starred list.
gitmoap repos unstar --repo-id <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/star \ -H "Authorization: Bearer $GITMOAP_TOKEN"Propose a CODEOWNERS file
A repository with no CODEOWNERS file still has owners, because the service catalog knows which paths belong to which service and which group owns each service. This read renders the CODEOWNERS text those bindings would produce, so you can review it in a merge request and commit it yourself. Nothing is written to your repository — the platform proposes, and the repository decides.
The response tells you whether a CODEOWNERS file already exists on the default branch, which is the thing to check first: an existing file wins wherever it matches, so committing this one over it is a merge rather than an adoption. The rule count covers bindings whose service has an owner; paths whose service has none appear as comments and are not counted.
Open the repository, then Settings → Ownership → Preview CODEOWNERS. The panel shows the proposed text with a copy button, and a banner when the repository already has a file of its own.
gitmoap repos get-rendered-codeowners --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/codeowners/rendered \ -H "Authorization: Bearer $GITMOAP_TOKEN"Move a repository to another group
A transfer changes which group the repository belongs to, and with it the inherited permissions: people who reached it through the old group's hierarchy lose that access, and people in the new one gain it. Direct members are untouched. The full path changes, so remotes and links pointing at the old one need updating; the id does not, so nothing holding an id breaks.
You need Owner on the repository and Maintainer on the destination group. The repository must not be more visible than its new parent — narrow its visibility first if it is — and its path must be free at the destination. Its stored bytes move to the destination's namespace budget, so a transfer into a namespace with no room is refused rather than half-applied.
Open the repository, then Settings → General → Transfer repository. Pick the destination group from the groups where you have Maintainer, type the repository's name to confirm, and the new full path is shown before you commit to it.
gitmoap repos transfer --repo-id <id> --new-group-id <new group id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/transfer \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "new_group_id": "<uuid>" }'To see who would gain or lose access before you move anything, ask for a preview with the same destination. It lists each person whose access would change, with what they would gain and lose, and moves nothing. Grants made on the repository itself move with it, except across organisations, where a team's grant, a custom role's grant and a service account's grant cannot follow.
The web app has no screen for this; use the CLI or the API.
gitmoap repos preview-transfer --repo-id <id> --new-group-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/transfer-preview \ -H "Authorization: Bearer $GITMOAP_TOKEN"Delete a repository
Warning
Deleting a repository destroys the git history and everything attached to it, immediately and permanently. That includes every branch and tag, every merge request with its comments, reviews and approvals, every work item with its discussion and criteria, all releases and their assets, branch and push rules, webhooks, pipelines and their run history, service accounts, provider connections and their stored credentials, memberships and stars. There is no bin and no undelete. If any of it matters, clone the repository and export what you need before you start.
Deleting needs Owner on the repository. The repository's stored bytes are returned to its namespace's budget. Work items in other repositories or groups that referenced this repository's commits survive, with those references removed.
Open the repository, then Settings → General, scroll to Danger zone and choose Delete this repository. You are asked to type the full path to confirm; the deletion happens as soon as you do.
gitmoap repos delete --repo-id <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Reference
Repositories themselves — creating, reading, changing, moving and deleting one.
12 endpoints
POST/repositoriesrepo:create
Create a new repository
Token scope
repo:create
Counts against
repos_per_group
Send
Body
- avatarobjectoptional
- descriptionobjectoptional
- display_nameobjectoptional
- group_iduuid
- repository_namestring
- visibilityobjectoptional
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404The group named by `group_id` does not exist409A group or repository already holds that path429Repository limit reached for this group
Receive
- avatarobjectoptional
- created_atstring
- created_byuuid
- default_branchstring
- descriptionobjectoptional
- display_nameobjectoptional
- full_pathstring
- group_iduuid
- repository_iduuid
- repository_namestring
- size_bytesint64optionalCached on-disk git object size in bytes, refreshed after each push. Rolls up to the namespace (root group) storage quota.
- starsint32
- updated_atstring
- visibilityinternalprivatepublic
GET/repositoriesrepo:read
List repositories
Token scope
repo:read
Send
Query
- group_idstringoptionalFilter by group ID (mutually exclusive with group_path; one is required)
- group_pathstringoptionalFilter by group path (mutually exclusive with group_id; one is required)
- limitint32optionalMaximum number of items to return
- next_tokenstringoptionalPagination token
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404The parent group does not exist
Receive
items[]object14 fields
- avatarobjectoptional
- created_atstring
- created_byuuid
- default_branchstring
- descriptionobjectoptional
- display_nameobjectoptional
- full_pathstring
- group_iduuid
- repository_iduuid
- repository_namestring
- size_bytesint64optionalCached on-disk git object size in bytes, refreshed after each push. Rolls up to the namespace (root group) storage quota.
- starsint32
- updated_atstring
- visibilityinternalprivatepublic
- next_tokenobjectoptional
GET/repositories/{repo_id}repo:read
Get a repository by ID
Token scope
repo:read
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found
Receive
No body.
GET/repositories/by-path/{full_path}repo:read
Get a repository by its full path
Token scope
repo:read
Send
Path
- full_pathstringFull hierarchical path (e.g. acme/platform/my-repo)
Refuses
400Invalid path401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found
Receive
No body.
GET/resolve/{full_path}repo:read
Resolve a full path to its entity type and ID
Token scope
repo:read
Send
Path
- full_pathstringFull hierarchical path (e.g. acme/platform/my-repo)
Refuses
400Invalid path401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Path not found
Receive
- full_pathstring
- idobjectA resolved entity ID — either a repository or group UUID.
- typegrouprepositoryThe entity type that a path resolves to.
PUT/repositories/{repo_id}repo:update
Update a repository
Token scope
repo:update
Send
Path
- repo_iduuidRepository ID
Body
- avatarobjectoptional
- descriptionobjectoptional
- display_nameobjectoptional
- repository_nameobjectoptional
- visibilityobjectoptional
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found409The new path is already held by another group or repository
Receive
The same shape create_repo returns — 14 fields. Go to it
Show it here
- avatarobjectoptional
- created_atstring
- created_byuuid
- default_branchstring
- descriptionobjectoptional
- display_nameobjectoptional
- full_pathstring
- group_iduuid
- repository_iduuid
- repository_namestring
- size_bytesint64optionalCached on-disk git object size in bytes, refreshed after each push. Rolls up to the namespace (root group) storage quota.
- starsint32
- updated_atstring
- visibilityinternalprivatepublic
POST/repositories/{repo_id}/transferrepo:transfer
Transfer a repository to a different group
Token scope
repo:transfer
Counts against
namespace_storage
Send
Path
- repo_iduuidRepository ID
Body
- new_group_iduuid
Refuses
400Invalid request or same group401Unauthenticated — no credential, or an invalid one403Forbidden404Repository or destination group not found409Path conflict or visibility conflict429The destination namespace has insufficient storage quota
Receive
The same shape create_repo returns — 14 fields. Go to it
Show it here
- avatarobjectoptional
- created_atstring
- created_byuuid
- default_branchstring
- descriptionobjectoptional
- display_nameobjectoptional
- full_pathstring
- group_iduuid
- repository_iduuid
- repository_namestring
- size_bytesint64optionalCached on-disk git object size in bytes, refreshed after each push. Rolls up to the namespace (root group) storage quota.
- starsint32
- updated_atstring
- visibilityinternalprivatepublic
GET/repositories/{repo_id}/transfer-previewrepo:transfer
Who would gain or lose access if a repository moved to another group, without moving it
Token scope
repo:transfer
Send
Path
- repo_iduuidRepository ID
Query
- new_group_iduuidThe group the repository would move to
Refuses
400The repository is already in that group401Unauthenticated — no credential, or an invalid one403The caller could not make this transfer404Repository or destination group not found409The repository is more visible than the destination group
Receive
changes[]object9 fields
principalobject4 fields
- avatar_urlobjectoptional
- iduuid
- kindagentservice_accountuserA person, a service account or an AI organization's agent.
- usernamestring
- rank_afterobjectoptional
- rank_beforeobjectoptional
scopeobject3 fields
- full_pathstring
- idstring
- typegrouprepository
summaryobject3 fields
- people_gainingint32
- people_losingint32
- scopes_affectedint64The group or repository the change is made on, plus every one beneath it that inherits access and so changes with it.
- truncatedbooleanWhether
changesstops short of everyone affected. The summary still counts them all.
DELETE/repositories/{repo_id}repo:delete
Delete a repository
Token scope
repo:delete
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
PUT/repositories/{repo_id}/avatarrepo:update
Get an upload URL for a repository avatar
Token scope
repo:update
Counts against
avatar_bytes
Send
Path
- repo_iduuidRepository ID
Body
- content_lengthint64Exact byte size of the upload. Bound into the presigned PUT, so the upload must send exactly this many bytes; rejected up front if over the avatar cap.
- content_typestring
Refuses
400Invalid content type401Unauthenticated — no credential, or an invalid one403Forbidden (Maintainer required)404Repository not found409The request conflicts with the resource's current state; `reason` says how501Avatar uploads are not available on this deployment
Receive
- avatar_urlstring
- upload_urlstring
DELETE/repositories/{repo_id}/avatarrepo:update
Delete a repository avatar
Token scope
repo:update
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden (Maintainer required)404Repository not found409The request conflicts with the resource's current state; `reason` says how500Avatar upload not configured
Receive
No body.
GET/repositories/{repo_id}/codeowners/renderedrepo:read
Render the CODEOWNERS file the service catalog would produce for a repository
Token scope
repo:read
Send
Path
- repo_iduuidThe repository to render a proposal for
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404No such repository, or it is not visible to the caller
Receive
- contentstringThe file, ready to commit. Empty when nothing this repository holds has an owner.
- has_existing_filebooleanWhether the repository already has a CODEOWNERS file on its default branch. An existing file takes precedence wherever it matches, so adopting this text would mean merging the two.
- rule_countint32Rules the text carries — bindings whose service is owned. Excludes and unowned services appear as comments and are not counted.
The two personal listings, answering "what is mine" and "what did I star".
2 endpoints
GET/user/repositoriesrepo:read
List every repository the caller reaches through membership, most recently active first
Token scope
repo:read
Send
Query
- limitint32optionalMaximum number of items to return
- next_tokenstringoptionalPagination token
Refuses
401Unauthenticated403Forbidden — the caller's access level or token scope is insufficient
Receive
The same shape list_repos returns — 2 fields. Go to it
Show it here
items[]object14 fields
- avatarobjectoptional
- created_atstring
- created_byuuid
- default_branchstring
- descriptionobjectoptional
- display_nameobjectoptional
- full_pathstring
- group_iduuid
- repository_iduuid
- repository_namestring
- size_bytesint64optionalCached on-disk git object size in bytes, refreshed after each push. Rolls up to the namespace (root group) storage quota.
- starsint32
- updated_atstring
- visibilityinternalprivatepublic
- next_tokenobjectoptional
GET/user/starredrepo:read
List the repositories the caller has starred, most recently starred first
Token scope
repo:read
Send
Query
- limitint32optionalMaximum number of items to return
- next_tokenstringoptionalPagination token
Refuses
401Unauthenticated403Forbidden — the caller's access level or token scope is insufficient
Receive
The same shape list_repos returns — 2 fields. Go to it
Show it here
items[]object14 fields
- avatarobjectoptional
- created_atstring
- created_byuuid
- default_branchstring
- descriptionobjectoptional
- display_nameobjectoptional
- full_pathstring
- group_iduuid
- repository_iduuid
- repository_namestring
- size_bytesint64optionalCached on-disk git object size in bytes, refreshed after each push. Rolls up to the namespace (root group) storage quota.
- starsint32
- updated_atstring
- visibilityinternalprivatepublic
- next_tokenobjectoptional
Stars.
3 endpoints
POST/repositories/{repo_id}/starrepo:star
Star a repository
Token scope
repo:star
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthorized403Forbidden — the caller's access level or token scope is insufficient404Repository not found409The request conflicts with the resource's current state; `reason` says how
Receive
- repository_iduuid
- starred_atstring
- user_iduuid
GET/repositories/{repo_id}/starrepo:read
Check if the caller has starred a repository
Token scope
repo:read
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthorized403Forbidden — the caller's access level or token scope is insufficient404Repository not found
Receive
- starredboolean
- starred_atobjectoptionalWhen you starred it. Absent when
starredisfalse.
DELETE/repositories/{repo_id}/starrepo:star
Unstar a repository
Token scope
repo:star
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthorized403Forbidden — the caller's access level or token scope is insufficient404Repository not found or not starred409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
Direct membership. Access through a group is resolved, not stored, so it appears in none of these.
6 endpoints
POST/repositories/{repo_id}/membersrepo_member:add
Add a member to a repository
Token scope
repo_member:add
Counts against
members_per_scope
Send
Path
- repo_iduuidRepository ID
Body
- access_leveldeveloperguestmaintainerownerreporter
- user_iduuid
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden404Repository or user not found409User is already a member429Member limit reached for this repository
Receive
- access_leveldeveloperguestmaintainerownerreporter
- created_atstring
- group_iduuid
- repository_iduuid
- user_iduuid
- visibilityinternalprivatepublic
GET/repositories/{repo_id}/membersrepo_member:read
List repository members
Token scope
repo_member:read
Send
Path
- repo_iduuidRepository ID
Query
- searchstringoptionalAutocomplete fragment — substring match on the member's username or email
- limitint32optionalMaximum number of items to return
- next_tokenstringoptionalPagination token
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Authentication required — a roster is not public403Caller is not a member with at least Reporter404Repository not found
Receive
items[]object6 fields
- access_leveldeveloperguestmaintainerownerreporter
- created_atstring
- group_iduuid
- repository_iduuid
- user_iduuid
- visibilityinternalprivatepublic
- next_tokenobjectoptional
GET/repositories/{repo_id}/members/{user_id}repo_member:read
Get a specific repository member
Token scope
repo_member:read
Send
Path
- repo_iduuidRepository ID
- user_iduuidUser ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Authentication required — a roster is not public403Caller is not a member with at least Reporter404Repository or member not found
Receive
The same shape add_repo_member returns — 6 fields. Go to it
Show it here
- access_leveldeveloperguestmaintainerownerreporter
- created_atstring
- group_iduuid
- repository_iduuid
- user_iduuid
- visibilityinternalprivatepublic
PUT/repositories/{repo_id}/members/{user_id}repo_member:update
Update a repository member's access level
Token scope
repo_member:update
Send
Path
- repo_iduuidRepository ID
- user_iduuidUser ID
Body
- access_leveldeveloperguestmaintainerownerreporter
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden404Repository or member not found409The person holds a custom role here; change it through access grants
Receive
The same shape add_repo_member returns — 6 fields. Go to it
Show it here
- access_leveldeveloperguestmaintainerownerreporter
- created_atstring
- group_iduuid
- repository_iduuid
- user_iduuid
- visibilityinternalprivatepublic
DELETE/repositories/{repo_id}/members/{user_id}repo_member:remove
Remove a member from a repository
Token scope
repo_member:remove
Send
Path
- repo_iduuidRepository ID
- user_iduuidUser ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden404Repository or member not found409The person holds a custom role here; revoke it through access grants
Receive
No body.
GET/users/{user_id}/repositoriesrepo_member:read
List repositories the user is a direct member of
Token scope
repo_member:read
Send
Path
- user_iduuidUser ID
Query
- limitint32optionalMaximum number of items to return
- next_tokenstringoptionalPagination token
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — not the target user404Not found — or found and private, which is masked as absent
Receive
The same shape list_repo_members returns — 2 fields. Go to it
Show it here
items[]object6 fields
- access_leveldeveloperguestmaintainerownerreporter
- created_atstring
- group_iduuid
- repository_iduuid
- user_iduuid
- visibilityinternalprivatepublic
- next_tokenobjectoptional
Files and history, read live from git.
7 endpoints
GET/repositories/{repo_id}/treetree:read
List tree entries in a repository at a given path and ref
Token scope
tree:read
Send
Path
- repo_iduuidRepository ID
Query
- refstringoptionalGit ref (branch, tag, or commit SHA). Default: HEAD
- pathstringoptionalDirectory path within the repo. Default: root
- redirectbooleanoptional
falseanswers with the pinned URL and commit SHA in the body instead of a 302, for clients that cannot follow a redirect. Default: true - limitintegeroptionalEntries per page (max 100). Default: 100
- pageintegeroptionalPage number (0-based). Default: 0
Refuses
302`ref` names a branch, tag or HEAD: redirect to the same request with `ref` pinned to the commit SHA it resolves to304The ref has not moved since the caller's `If-None-Match`400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository or tree not found409The repository is empty: nothing has been pushed to it, so there is no tree to list
Receive
- has_moreboolean
items[]object6 fields
- modestring
- namestring
- oidstring
- pathstring
- sizeint64optional
- typeblobtree
- pageinteger
- total_countinteger
GET/repositories/{repo_id}/tree/last-commitstree:read
The last commit that touched each file and folder in a directory
Token scope
tree:read
Send
Path
- repo_iduuidRepository ID
Query
- refstringoptionalGit ref (branch, tag, or commit SHA). Default: HEAD
- pathstringoptionalDirectory path within the repo. Default: root
- redirectbooleanoptional
falseanswers with the pinned URL and commit SHA in the body instead of a 302, for clients that cannot follow a redirect. Default: true - limitintegeroptionalEntries per page (max 100). Default: 100. Pass the same value as the tree listing so the two pages line up
- pageintegeroptionalPage number (0-based). Default: 0
Refuses
302`ref` names a branch, tag or HEAD: redirect to the same request with `ref` pinned to the commit SHA it resolves to304The ref has not moved since the caller's `If-None-Match`400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository or directory not found409The repository is empty: nothing has been pushed to it, so there is no history to search
Receive
- commitobjectoptional
- has_moreboolean
items[]object4 fields
- commitobjectoptional
- namestringThe entry's name within the directory being listed.
- pathstringThe entry's full path from the repository root — the key to join against a tree listing.
- typeblobtree
- pageinteger
- total_countinteger
- truncatedbooleanThe search reached its limit before every entry had an answer. The entries it did not
reach carry no
commit; they have one, it was simply too far back to find in time. A search that ran out of time is answered withCache-Control: no-storeand noETag, so asking again searches again; one that reached its limit on commits read gives the same answer every time.
GET/repositories/{repo_id}/blobtree:read
Get a blob (file content) from a repository at a given path and ref
Token scope
tree:read
Send
Path
- repo_iduuidRepository ID
Query
- refstringoptionalGit ref (branch, tag, or commit SHA). Default: HEAD
- pathstringFile path within the repo (required)
- redirectbooleanoptional
falseanswers with the pinned URL and commit SHA in the body instead of a 302, for clients that cannot follow a redirect. Default: true
Refuses
302`ref` names a branch, tag or HEAD: redirect to the same request with `ref` pinned to the commit SHA it resolves to304The ref has not moved since the caller's `If-None-Match`400Invalid request or file too large401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository or file not found409The repository is empty: nothing has been pushed to it, so there is no file to read
Receive
- blob_shastring
- commit_shastring
- contentstring
- encodingbase64utf8
- lossyboolean
truewhen the file is text but not valid UTF-8, andcontentshows replacement characters where its bytes could not be decoded. Writing such content back would change the file. - mode100644100755120000160000A tree entry's mode: what kind of file git records at a path.
- pathstring
- sizeint64
- truncatedboolean
GET/repositories/{repo_id}/profilerepo:read
Get a repository's profile
Token scope
repo:read
Send
Path
- repo_iduuidRepository ID
Query
- refstringoptionalBranch, tag, or commit SHA the files are read at. Default: HEAD
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository or ref not found409The repository is empty: nothing has been pushed to it yet
Receive
- commit_shastring
filesobject8 fields
- catalogobjectoptional
- changelogobjectoptional
- code_of_conductobjectoptional
- codeownersobjectoptional
- contributingobjectoptional
- licenseobjectoptional
- readmeobjectoptional
- securityobjectoptional
- refstringThe ref the files were read at, as asked.
setupobject5 fields
- default_branch_protectedbooleanA branch rule protects the default branch.
- latest_releaseobjectoptional
- pipeline_countint32Its published pipelines, counted up to 100.
wikiobject2 fields
- enabledboolean
- page_countint32
GET/repositories/{repo_id}/greptree:read
Search file content in a repository at a ref
Token scope
tree:read
Send
Path
- repo_iduuidRepository ID
Query
- qstringThe text to find (required). Matched literally unless
regex=true - refstringoptionalGit ref (branch, tag, or commit SHA). Default: HEAD
- patharrayoptionalLimit the search to this file or directory; repeat for several. Default: the whole repository
- regexbooleanoptionalRead
qas an extended regular expression. Default: false - ignore_casebooleanoptionalMatch regardless of case. Default: false
Refuses
400Missing or overlong pattern, an invalid regular expression, or an invalid path401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository or ref not found409The repository is empty: nothing has been pushed to it, so there is nothing to search
Receive
matches[]object3 fields
- lineintegerThe line number of the match, counting from 1.
- pathstringThe file the match is in, relative to the repository root.
- textstringThe matching line. A very long line is shortened and ends with
….
- shastring
- truncatedbooleanThe search stopped before it finished — it reached its match limit, its size limit or its time limit. More matches may exist; narrow the search with a path or a more specific pattern to see them.
GET/repositories/{repo_id}/commitscommit:read
List commits in a repository
Token scope
commit:read
Send
Path
- repo_iduuidRepository ID
Query
- refstringoptionalBranch name, tag, or commit SHA (defaults to HEAD)
- pathstringoptionalShow only commits that changed this file, or anything inside this directory. Follows the change through merges, so you see who made it rather than who merged it.
- first_parentbooleanoptionalFollow only the first parent of each commit, so a merge appears once and the commits it brought in do not. Defaults to false: every commit reachable from the ref.
- limitint32optionalMaximum number of items to return (1-100)
- next_tokenstringoptionalPagination token from the previous page
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found — or found and private, which is masked as absent
Receive
items[]object13 fields
- author_avatar_urlobjectoptional
- author_emailstring
- author_namestring
- authored_atstring
- commit_shastring
- committed_atstring
- committer_avatar_urlobjectoptional
- committer_emailstring
- committer_namestring
- messageobjectoptional
- repository_iduuid
- short_shastring
- titlestring
- next_tokenobjectoptional
GET/repositories/{repo_id}/commits/{commit_ref}commit:read
Get a commit by SHA
Token scope
commit:read
Send
Path
- repo_iduuidRepository ID
- commit_refstringCommit SHA (7-40 hex chars)
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Commit not found
Receive
- author_avatar_urlobjectoptional
- author_emailstring
- author_namestring
- authored_atstring
- commit_shastring
- committed_atstring
- committer_avatar_urlobjectoptional
- committer_emailstring
- committer_namestring
- messageobjectoptional
- repository_iduuid
- short_shastring
- titlestring
Tags.
4 endpoints
POST/repositories/{repo_id}/tagstag:create
Create a tag in a repository
Token scope
tag:create
Will count against
tags_per_repo
not enforced today
Send
Path
- repo_iduuidRepository ID
Body
- messageobjectoptional
- tag_namestring
- targetstring
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found — or found and private, which is masked as absent409Tag already exists
Receive
commitobject13 fields
- author_avatar_urlobjectoptional
- author_emailstring
- author_namestring
- authored_atstring
- commit_shastring
- committed_atstring
- committer_avatar_urlobjectoptional
- committer_emailstring
- committer_namestring
- messageobjectoptional
- repository_iduuid
- short_shastring
- titlestring
- repository_iduuid
- tag_messageobjectoptional
- tag_namestring
- tag_typeannotatedlightweight
- taggerobjectoptional
GET/repositories/{repo_id}/tags/{tag_name}tag:read
Get a tag by name
Token scope
tag:read
Send
Path
- repo_iduuidRepository ID
- tag_namestringTag name
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Tag not found
Receive
The same shape create_tag returns — 6 fields. Go to it
Show it here
commitobject13 fields
- author_avatar_urlobjectoptional
- author_emailstring
- author_namestring
- authored_atstring
- commit_shastring
- committed_atstring
- committer_avatar_urlobjectoptional
- committer_emailstring
- committer_namestring
- messageobjectoptional
- repository_iduuid
- short_shastring
- titlestring
- repository_iduuid
- tag_messageobjectoptional
- tag_namestring
- tag_typeannotatedlightweight
- taggerobjectoptional
DELETE/repositories/{repo_id}/tags/{tag_name}tag:delete
Delete a tag
Token scope
tag:delete
Send
Path
- repo_iduuidRepository ID
- tag_namestringTag name
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Tag not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
What the code is made of, and who made it.
7 endpoints
GET/repositories/{repo_id}/languagesrepo:read
A repository's programming-language breakdown, as of a named commit
Token scope
repo:read
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden404Repository not found
Receive
excludedobject4 fields
- documentationint64Docs, licences, changelogs.
- generatedint64Lock files, minified bundles, source maps.
- unclassifiedint64Binaries, symlinks, submodules, data and prose languages, and anything the vocabulary could not settle.
- vendoredint64Third-party code the repository carries but did not write.
- indexed_atobjectoptionalWhen that index was written.
- indexed_shaobjectoptionalThe commit this describes.
nullwhen the repository has never been indexed. items[]object6 fields
- bytesint64Bytes attributed to this language. Authoritative;
shareis a convenience. - colorobjectoptionalThe CSS colour upstream renders this language with, when it defines one.
- filesint64How many files contributed.
- languagestringThe language's name, as the vocabulary spells it.
- sharedoublePercentage of
total_bytes, to two places. - typestring
programmingormarkup— the only two kinds that reach a breakdown.
- bytesint64Bytes attributed to this language. Authoritative;
- total_bytesint64Total counted bytes — the denominator of every
share. - total_filesint64Total counted files.
- truncatedbooleanWhether the tree exceeded the index cap, so this describes part of the repository.
GET/catalog/services/{service_id}/languagescatalog:read
The language breakdown for the paths one catalog service owns
Token scope
catalog:read
Send
Path
- service_iduuidService ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden404Service not found
Receive
excludedobject4 fields
- documentationint64Docs, licences, changelogs.
- generatedint64Lock files, minified bundles, source maps.
- unclassifiedint64Binaries, symlinks, submodules, data and prose languages, and anything the vocabulary could not settle.
- vendoredint64Third-party code the repository carries but did not write.
items[]object6 fields
- bytesint64Bytes attributed to this language. Authoritative;
shareis a convenience. - colorobjectoptionalThe CSS colour upstream renders this language with, when it defines one.
- filesint64How many files contributed.
- languagestringThe language's name, as the vocabulary spells it.
- sharedoublePercentage of
total_bytes, to two places. - typestring
programmingormarkup— the only two kinds that reach a breakdown.
- bytesint64Bytes attributed to this language. Authoritative;
sources[]object4 fields
- indexed_atobjectoptionalWhen that index was written.
- indexed_shaobjectoptionalThe commit its index describes.
- repository_iduuid
- truncatedbooleanWhether that repository's index is truncated.
- total_bytesint64Total counted bytes across every contributing repository.
- total_filesint64Total counted files.
GET/repositories/{repo_id}/contributorsrepo:read
List a repository's contributors, ranked by commit count
Token scope
repo:read
Send
Path
- repo_iduuidRepository ID
Query
- searchstringoptionalSubstring match on the contributor's git name or email
- limitint32optionalMaximum number of items to return
- next_tokenstringoptionalPagination token
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden404Repository not found
Receive
items[]object9 fields
- additionsint64
- avatar_urlobjectoptional
- commitsint64
- deletionsint64
- emailstring
- first_commit_atstring
- last_commit_atstring
- namestringThe author name from git, as spelled in the most recent attributed commit.
- principalobjectoptional
- next_tokenobjectoptional
- totalint64Every contributor the repository has, not just this page — what a sidebar renders as "N contributors" without walking the pages.
GET/repositories/{repo_id}/merge-requests/{mr_id}/participantsmerge_request:read
List everyone involved in a merge request, with the roles they hold
list_mr_participantsGET /repositories/{repo_id}/merge-requests/{mr_id}/participantsOpen in the API referenceToken scope
merge_request:read
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request IID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden404Repository or merge request not found
Receive
items[]object4 fields
- avatar_urlobjectoptional
- emailstring
- namestring
- principalobjectoptional
GET/user/contributionsuser:read
The calling principal's own contribution heatmap.
Token scope
user:read
Send
Query
- yearint32optionalConvenience: whole calendar year. Exclusive with from/to.
- fromstringoptionalInclusive range start (YYYY-MM-DD).
- tostringoptionalInclusive range end (YYYY-MM-DD).
Refuses
400Invalid query401Unauthenticated403Forbidden — the caller's access level or token scope is insufficient
Receive
cells[]object7 fields
by_typeobject5 fields
- commitsint64
- issuesint64
- mrsint64
- releasesint64
- reviewsint64
- countint64
- datestringThe UTC calendar day,
YYYY-MM-DD.
- current_streakint64Consecutive active days ending at
to. - fromstringResolved inclusive range start,
YYYY-MM-DD. - longest_streakint64Longest run of consecutive active days within the range.
- max_per_dayint64Largest single-day
count(frontend color-scale denominator). - principal_idstringThe subject user or service account id.
- principal_typestring
userorservice_account. - tostringResolved inclusive range end,
YYYY-MM-DD. - totalint64Sum of
countacross all cells in range.
GET/users/{user_id}/contributionsuser:read
Any user's public contribution heatmap.
Token scope
user:read
Send
Path
- user_iduuidUser ID
Query
- yearint32optionalConvenience: whole calendar year. Exclusive with from/to.
- fromstringoptionalInclusive range start (YYYY-MM-DD).
- tostringoptionalInclusive range end (YYYY-MM-DD).
Refuses
400Invalid query401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404User not found
Receive
The same shape get_my_contributions returns — 9 fields. Go to it
Show it here
cells[]object7 fields
by_typeobject5 fields
- commitsint64
- issuesint64
- mrsint64
- releasesint64
- reviewsint64
- countint64
- datestringThe UTC calendar day,
YYYY-MM-DD.
- current_streakint64Consecutive active days ending at
to. - fromstringResolved inclusive range start,
YYYY-MM-DD. - longest_streakint64Longest run of consecutive active days within the range.
- max_per_dayint64Largest single-day
count(frontend color-scale denominator). - principal_idstringThe subject user or service account id.
- principal_typestring
userorservice_account. - tostringResolved inclusive range end,
YYYY-MM-DD. - totalint64Sum of
countacross all cells in range.
GET/service-accounts/{sa_id}/contributionsservice_account:read
A service account's contribution heatmap.
Token scope
service_account:read
Send
Path
- sa_iduuidService account ID
Query
- yearint32optionalConvenience: whole calendar year. Exclusive with from/to.
- fromstringoptionalInclusive range start (YYYY-MM-DD).
- tostringoptionalInclusive range end (YYYY-MM-DD).
Refuses
400Invalid query401Unauthenticated403Forbidden404Service account not found
Receive
The same shape get_my_contributions returns — 9 fields. Go to it
Show it here
cells[]object7 fields
by_typeobject5 fields
- commitsint64
- issuesint64
- mrsint64
- releasesint64
- reviewsint64
- countint64
- datestringThe UTC calendar day,
YYYY-MM-DD.
- current_streakint64Consecutive active days ending at
to. - fromstringResolved inclusive range start,
YYYY-MM-DD. - longest_streakint64Longest run of consecutive active days within the range.
- max_per_dayint64Largest single-day
count(frontend color-scale denominator). - principal_idstringThe subject user or service account id.
- principal_typestring
userorservice_account. - tostringResolved inclusive range end,
YYYY-MM-DD. - totalint64Sum of
countacross all cells in range.
Limits
Four plan-and-platform limits apply to the operations on this page, plus two caps on reading files. Three of the four are plan limits, resolved for the namespace — the root group at the top of the hierarchy your repository sits in, which for a personal repository is your own namespace. The fourth is a flat platform limit that does not vary by plan.
| Limit | Applies to | Free | Pro | Team | Enterprise |
|---|---|---|---|---|---|
| Repositories in one group | each group, counted separately | 20 | 200 | 2 000 | 20 000 |
| Members on one repository | each repository | 50 | 200 | 1 000 | 5 000 |
| Stored bytes | the whole namespace | 5 GiB | 100 GiB | 1 TiB | 10 TiB |
| Avatar image size | each upload | 5 MiB | 5 MiB | 5 MiB | 5 MiB |
At a count limit the request is refused with 429 TOO_MANY_REQUESTS and nothing is created. There is no partial repository and no half-added member — the check and the write are one act, so two people creating the twentieth repository at the same moment produce one repository and one refusal, never twenty-one. The repository count is per group, not per namespace, so a group at its limit does not stop you creating repositories in a sibling group.
Storage is measured after each push, from the repository's own git objects, and rolls up to the namespace. A push that would take the namespace over its limit is rejected and the refs are rolled back, so the push fails cleanly rather than leaving half a branch. Deleting a repository returns its bytes; transferring one moves them to the destination's namespace.
The avatar limit is checked before the upload starts. The exact byte count you declare is bound into the upload link, so an image over 5 MiB is refused up front rather than after you have sent it.
To raise a limit, an Owner of the namespace can move it to a higher plan, or request an increase on a single limit without changing plan. See Billing and plans.
Reading files has two further caps, and neither varies by plan. A tree listing and its commit column return at most a hundred entries per page; a directory with more is paged. And the search for each entry's last commit gives up rather than running indefinitely — when it does, the response sets truncated and the entries it did not reach come back with no commit. Those entries do have one; it was simply further back than the search went. Everything else on the page is unaffected, and asking again for the same commit is answered from what was already found.
Troubleshooting
400 BAD_REQUESTmentioning repo_id or user_id
- Cause
- A path like
acme/platform/checkoutwas pasted where an id belongs - What to do
- Use the id, or the by-path read, or the path resolver. The CLI's
--repo-idaccepts either form and resolves the path for you
400 BAD_REQUESTon the by-path read or the path resolver
- Cause
- A segment of the path is not a valid name — an uppercase letter, an underscore, a space, or a leading or trailing slash
- What to do
- Lowercase it, and send the path with no slash at either end. Each segment allows only lowercase letters, digits and hyphens
400 BAD_REQUESTon create, mentioning the name
- Cause
- The name is not a valid slug
- What to do
- Use lowercase letters, digits and hyphens, one to a hundred characters, with no leading or trailing hyphen. Put the human-readable form in the display name instead
400 BAD_REQUESTon create or update, mentioning visibility
- Cause
- You asked for a visibility wider than the group above allows
- What to do
- Widen the group first, or pick a narrower visibility. A private group cannot hold a public repository
400 BAD_REQUESTon the repository listing
- Cause
- Neither
group_idnorgroup_pathwas given, or both were - What to do
- Supply exactly one. The listing is always scoped to a single group
400 BAD_REQUESTon transfer
- Cause
- The destination is the group the repository is already in
- What to do
- Nothing to do — it is already there. Check you picked the group you meant
400 BAD_REQUESTon an avatar upload
- Cause
- The content type is not PNG, JPEG, GIF or WebP, or the declared size is zero or over 5 MiB
- What to do
- Convert or resize the image, and declare its real byte count — the size is bound into the upload link and cannot be exceeded
400 BAD_REQUESTon a file read, mentioning size
- Cause
- The file is over 1 MiB, the ceiling for reading a file through the API
- What to do
- Read it through a clone. The tree listing still shows the file and its size
400 BAD_REQUESTon a file read, mentioning path
- Cause
- The
pathparameter is missing — unlike the tree listing, it has no default - What to do
- Add the file's path, relative to the repository root, with no leading slash
400 BAD_REQUESTon a commit read
- Cause
- The reference is not between 7 and 40 hexadecimal characters
- What to do
- Use the full sha. Branch and tag names are not accepted here; list history from a ref instead
400 BAD_REQUESTon create tag
- Cause
- The tag name is not a valid git ref, or the target is malformed
- What to do
- Rename the tag: at most 255 characters, no spaces, no
.., none of~ ^ : ? *, no[or\, no leading.or-, and not ending in.lock. The target is a commit sha or a branch name
400 BAD_REQUESTon a contribution heatmap
- Cause
- The start is after the end,
yearwas combined with an explicit range, or the range is longer than 366 days - What to do
- Ask for one calendar year with
year, or an explicit range inside 366 days. Fetch several years as several requests, or send no range at all for the trailing 365 days
401 UNAUTHORIZEDon anything
- Cause
- No credential was sent, or the one sent is expired or malformed
- What to do
- Sign in again, or mint a fresh token. A public repository is readable without a credential, but every write needs one
403 FORBIDDENon create
- Cause
- You can read the group but are not a Maintainer on it
- What to do
- Ask an Owner or Maintainer of the group to raise your level, or create the repository in a group you own — your personal namespace always qualifies
403 FORBIDDENon a visibility change, when other edits work
- Cause
- Visibility needs Owner; the rest of the settings need Maintainer
- What to do
- Ask an Owner to make the change, or to raise your level on the repository
403 FORBIDDENon delete or transfer
- Cause
- Both need Owner, and a transfer additionally needs Maintainer on the destination group
- What to do
- Ask an Owner. For a transfer, check you also have Maintainer where it is going
403 FORBIDDENwhen adding or changing a member
- Cause
- Every membership change needs Maintainer, and any change that grants the Owner level or alters somebody who already holds it needs Owner
- What to do
- Ask an Owner to make that one change. Adding or adjusting anybody at Guest through Maintainer still works with your Maintainer level
403 FORBIDDENlisting another person's repository memberships
- Cause
- That listing is self-only, by design — it is not a way to audit a colleague
- What to do
- Read the repository's own member list instead, which shows everyone with direct access
403 INSUFFICIENT_SCOPE
- Cause
- Your token is valid but was minted without the scope this call needs
- What to do
- Mint a new token carrying the scope named in the error. Scopes are chosen when a token is created and cannot be added afterwards
404 NOT_FOUNDon a repository you are certain exists
- Cause
- It is private and you are not a member, or the path is wrong
- What to do
- A private repository answers
404to non-members so its existence stays private. Check your membership, then the spelling — a rename or a transfer moves the path while keeping the id
404 NOT_FOUNDon create, naming the group
- Cause
- The group id does not exist, or is private and you are not a member
- What to do
- Resolve the group by path first, and confirm you are a member of it
404 NOT_FOUNDon a commit
- Cause
- The sha is not in this repository, or an abbreviation matches more than one object
- What to do
- Use the full 40-character sha. An ambiguous abbreviation is reported as absent rather than guessed at
404 NOT_FOUNDon create tag, naming the target
- Cause
- The branch or sha you are tagging does not resolve in this repository
- What to do
- Check the ref exists. A branch that was deleted, or a sha from a different repository, both land here
404 NOT_FOUNDon unstar
- Cause
- You had not starred that repository — or you can no longer read it, and the two answer alike
- What to do
- Treat it as already unstarred. If you are certain you starred it, check you still have access: a repository you cannot read can be neither starred, unstarred nor checked
404 NOT_FOUNDon the star check
- Cause
- You can no longer read the repository, or it was deleted. Not having starred it answers
starred: false, never404 - What to do
- Check you still have access. A private repository you were removed from answers
404to every read
404 NOT_FOUNDon a member read, update or removal
- Cause
- That person has no direct membership — they may still reach the repository through a group
- What to do
- Change their group membership instead, or add a direct membership first and then adjust it
409 CONFLICTon create, rename or transfer
- Cause
- A group or repository already holds that path. Groups and repositories share one path namespace
- What to do
- Pick another name, or clear the path first. Nothing was changed — the existing entity is untouched
409 CONFLICTon transfer when the path is free
- Cause
- The repository is more visible than the destination group allows
- What to do
- Narrow the repository's visibility first, then transfer. The destination group's visibility is the ceiling
409 CONFLICTon adding a member
- Cause
- They are already a direct member of this repository
- What to do
- Change their access level instead of adding them again
409 CONFLICTon create tag
- Cause
- A tag with that name already exists
- What to do
- Pick another name, or delete the existing tag first. Tags are never moved in place
409 REPOSITORY_EMPTYon a tree listing, a file read or the last-commit column
- Cause
- Nothing has been pushed to the repository, or every branch and tag has been deleted — so there is no tree at any ref
- What to do
- Push a commit:
git remote add origin gitmoap://<group>/<repo>, thengit push -u origin main. The repository'semptyfield says the same thing, so a page can show those commands without asking for a tree first
429 TOO_MANY_REQUESTSon create, transfer, or adding a member
- Cause
- The group is at its repository limit, the repository is at its member limit, or a transfer would take the destination group past its repository limit or the destination namespace past its stored-bytes limit
- What to do
- Delete something you no longer need, or raise the limit — see Limits above. Nothing was created or moved, so retrying without changing anything gets the same answer
500on an avatar upload or removal
- Cause
- Avatar storage is unavailable on Gitmoap's side
- What to do
- Everything else on the repository works; only avatars are affected. Contact support@gitmoap.com
501on an avatar upload
- Cause
- This Gitmoap deployment does not host avatars
- What to do
- Set the repository's
avatarfield to the address of a picture you host elsewhere. Everything else on the repository works
A tree or file request comes back as a redirect
- Cause
- You asked by branch name, and the answer points at the same request pinned to the commit that branch resolves to — which caches far better
- What to do
- Follow it, as most clients do automatically. A client that cannot follow a redirect should pass
redirect=falseand read the pinned location out of the response body
A tree or file request comes back empty with 304
- Cause
- You sent
If-None-Matchand the ref has not moved since - What to do
- Use the copy you already have. This is the fast path, not a failure
Some rows of the last-commit column have no commit, and the response sets truncated
- Cause
- The search through history stopped before it reached them — either it ran out of time, or it read as many commits as one request may
- What to do
- Ask again. An answer cut short by time is never kept, so a second request searches again and usually fills in more. If the same rows stay empty, their last change is further back than one request reads:
git log -1 -- <path>in a clone finds it
The language breakdown is empty and names no commit
- Cause
- Nothing has been indexed for this repository yet — either it has never been pushed to, or its first push has not been folded in
- What to do
- Push something and re-read a moment later. An empty breakdown is not a missing repository — the reply distinguishes them by answering
200with no commit rather than404
The language bar reports a language you did not expect to dominate
- Cause
- Vendored, generated, documentation and unclassifiable bytes are all left out, and everything else counts towards the shares
- What to do
- Read the excluded totals in the same response — they are broken out by reason, so you can see what was dropped and why
Somebody who has committed is missing from the contributors list
- Cause
- Their work is not on the default branch, or they committed with a different email
- What to do
- Merge the branch: a merge credits the original authors. If the address differs, they appear as a separate entry keyed by that address
The same person appears twice in the contributors list
- Cause
- They have committed under two different email addresses
- What to do
- Contributors are keyed by the address in the commit. Set
user.emailconsistently in git, or accept both entries — historic commits cannot be reattributed
A repository you can read is missing from your own repository list
- Cause
- You have no membership on it, direct or through a group. Being able to read a public repository does not make it yours
- What to do
- Star it to keep it to hand, or ask for a membership if you work on it
Your repository list and your starred list are both empty from a script
- Cause
- The call was made with a service-account token. Memberships and stars belong to people, so a service account has none and gets an empty page rather than a refusal
- What to do
- List the group's repositories instead, which is what a service account is scoped to, or run the call with a personal token
The contributor list still credits commits a force-push removed
- Cause
- Contributor counts are accumulated as pushes land, and a rewind leaves credit for commits nothing reaches any more. The language breakdown does not have this problem — a rewinding push rebuilds it from the new tip
- What to do
- Contact support@gitmoap.com to re-run the contributor attribution for the repository, which re-walks the current default branch and replaces the counts. The counts are a derived view, so nothing else is affected in the meantime
A colleague with access is missing from the member list
- Cause
- They reach the repository through a group, and the list shows direct members only
- What to do
- Look at the group's members. Removing a direct membership never removes group-granted access
A git push is rejected mentioning the storage limit
- Cause
- The push would take the namespace over its stored-bytes limit
- What to do
- The refs were rolled back, so the repository is unchanged. Delete a repository you no longer need, or raise the limit
A git push is rejected with LEGAL_ACCEPTANCE_REQUIRED
- Cause
- A version of the Terms you have not accepted is in force, and a push is a change
- What to do
- Accept it in the web app, then push again — see Terms and legal documents. Clone and fetch keep working meanwhile
A git push is refused before it uploads, saying it is larger than the per-push limit
- Cause
- One push may carry at most your plan's per-push size, and the push states its size before sending anything, so it is refused before you wait for an upload
- What to do
- Push the history in smaller steps:
git push origin <older-commit>:refs/heads/<branch>first, then the rest. Each push only carries what the one before it did not
A git push stops with "the server went silent"
- Cause
- Nothing came back for two minutes while the push was being processed, so the processing most likely failed part-way
- What to do
- Run
git fetch: the branch may already be where you pushed it. If it is not, push again — the upload starts over, and nothing from the first attempt is kept
A git push or clone stops with "the gitmoap gateway answered" and a request id
- Cause
- Something failed between the gateway and the service, and the gateway does not say what
- What to do
- Run
git fetchto see whether a push landed, then retry. If it keeps happening, send the request id to support@gitmoap.com — it identifies the failure exactly
The repository size looks stale right after a push
- Cause
- Size is remeasured when a push completes, and the reply you are reading was built before that
- What to do
- Re-read the repository. The size shown is the last measured value, not an estimate
FAQ
Can I change the default branch?
No. Every repository is created with main and there is no setting that moves it. It is what a clone checks out, what a tree or file read answers when you name no ref, and what contributor counts and the language breakdown are computed from.
Does deleting a repository free up my storage?
Yes. Its measured bytes are returned to the namespace's budget as part of the deletion.
If I rename a repository, do old links keep working?
No. The full path changes immediately and the old one stops resolving. Anything holding the repository's id keeps working, which is why automation should hold ids.
Why does a private repository answer 404 instead of 403?
Because 403 would confirm it exists. A stranger probing paths learns nothing from a 404, which is the point.
Do stars affect who can see a repository?
No — a star is a bookmark and a counter, and starring something grants nobody anything. It works the other way round: you have to be able to read a repository to star it, and a private repository you later lose access to disappears from your starred list along with everything else you can no longer see.
Are commits and tags stored twice — once in git and once here?
No. They are read out of git when you ask, so what you see matches a clone exactly, including after history is rewritten.
Related
- Groupsthe namespaces repositories live in, their nesting, membership and the usage and limits every repository counts against
- Branchescreating, listing and deleting branches without a clone
- Merge requestsproposing and landing a change to a repository
- Repository rulesprotecting branches and constraining what may be pushed
- Releasescutting a release from a tag, with its assets and archives
- Diffs and comparisonswhat a commit changed, and how two refs differ
- Service catalogwhich service owns which paths, who owns that service, and where it is deployed
- Access tokensminting a token with the scopes the calls above need
- Service accountsthe automation identities that appear as contributors and carry heatmaps of their own
- Searchfinding a repository, work item or merge request across every group you can reach
- Billing and plansmoving to a higher plan, or asking for one limit to be raised