Skip to content
GitmoapdocsBeta
Guide· How do I…?

Repositories

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

Quick start3 steps

Create a repository

You’ll need Maintainer on the group it goes in — your personal namespace always qualifies.
  1. Choose Create in the top bar, then New repository — or New repository on a group's own page.

  2. Pick the group and a name: lowercase letters, digits and hyphens. The full path updates as you type.

  3. Choose Create repository. You land on the empty repository, which shows the exact commands for pushing to it.

Quick start2 steps

Push an existing repository

You’ll need git and the Gitmoap git helper — install them.
  1. In your existing clone, add this repository as a remote:

    git remote add origin gitmoap://git.example.com/acme/platform/checkout
  2. Push your default branch and track it. The files and the history appear as soon as the push lands.

    git push -u origin main
Quick start2 steps

Start from an empty folder

You’ll need git and the Gitmoap git helper — install them.
  1. 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"
  2. 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
Quick start3 steps

Give someone access to a repository

You’ll need Maintainer on the repository, and an account for the person you are adding.

Start at Members in the repository's side navigation.

  1. Choose Add member.

  2. Find the person by name, username or email, and pick the lowest level that does the job — Reporter reads, Developer pushes, Maintainer manages.

  3. Choose Add member. The access applies straight away.

Quick start2 steps

Tag a release point

You’ll need Developer on the repository, and a clone of it.
  1. Tag the commit you want to mark, with a message saying what it is:

    git tag -a v1.0.0 -m "First release"
  2. Push the tag. It appears under Tags, ready to cut a release from.

    git push origin v1.0.0
Quick start2 steps

Add a README

You’ll need Developer on the repository, and a clone of it.
  1. Write a README.md at the root of your clone that says what the repository is for and how to use it.

  2. 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:read to read, repo:create, repo:update, repo:transfer, repo:delete and repo:star for the matching actions, repo_member:read / :add / :update / :remove for membership, commit:read, tag:read / tag:create / tag:delete, tree:read for files, user:read for your own or another person's contribution heatmap, service_account:read for a service account's, merge_request:read for a merge request's participants, and catalog:read for 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> then git push fills 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/billing

The 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 toYou need
See that a private repository existsGuest
Read files, history, tags, contributorsReporter, or anyone signed in through visibility on a public or internal repository
See who the members areReporter — visibility does not open the member list, even on a public repository
Create or delete a tagDeveloper
Rename, describe, set the avatar, manage membersMaintainer
Change visibility, transfer, delete the repositoryOwner

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.

Terminalbash
gitmoap repos create \
--group-id <group id> \
--repository-name <repository name>
curlbash
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/checkout
git push -u origin main

Find 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.

Terminalbash
gitmoap repos list
curlbash
curl -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.

Terminalbash
gitmoap repos get --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos get-by-path --full-path <id>
curlbash
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.

Terminalbash
gitmoap resolve resolve-path --full-path <id>
curlbash
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.

Terminalbash
gitmoap repos list-my
curlbash
curl -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.

Terminalbash
gitmoap repos list-my-starred
curlbash
curl -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.

Terminalbash
gitmoap repos update --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos update --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos upload-avatar \
--repo-id <id> \
--content-length <n> \
--content-type <content type>
curlbash
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.

Terminalbash
gitmoap repos delete-avatar --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repo-members add \
--repo-id <id> \
--access-level guest|reporter|developer|maintainer|owner \
--user-id <user id>
curlbash
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.

Terminalbash
gitmoap repo-members list --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repo-members get --repo-id <id> --user-id <id>
curlbash
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.

Terminalbash
gitmoap repo-members update \
--repo-id <id> \
--user-id <id> \
--access-level guest|reporter|developer|maintainer|owner
curlbash
curl -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.

Terminalbash
gitmoap repo-members remove --repo-id <id> --user-id <id>
curlbash
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.

Terminalbash
gitmoap repo-members list-repos-for-user --user-id <id>
curlbash
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.

Terminalbash
gitmoap tree list-tree --repo-id <id>
curlbash
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.

Terminalbash
gitmoap tree get-blob --repo-id <id> --path <path>
curlbash
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.

Terminalbash
gitmoap repos get-profile --repo-id <id>
curlbash
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.

Terminalbash
gitmoap tree grep-repo --repo-id <id> --q <q>
curlbash
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.

Terminalbash
gitmoap tree list-tree-last-commits --repo-id <id>
curlbash
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.

Terminalbash
gitmoap commits list --repo-id <id>
curlbash
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.

Terminalbash
gitmoap commits list --repo-id <id>
curlbash
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.

Terminalbash
gitmoap commits get --repo-id <id> --commit-ref <id>
curlbash
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.

Terminalbash
gitmoap tags create --repo-id <id> --tag-name <tag name> --target <target>
curlbash
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.

Terminalbash
gitmoap tags list --repo-id <id>
curlbash
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.

Terminalbash
gitmoap tags get --repo-id <id> --tag-name <id>
curlbash
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.

Terminalbash
gitmoap tags delete --repo-id <id> --tag-name <id>
curlbash
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.

Terminalbash
gitmoap languages get-repo-languages --repo-id <id>
curlbash
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.

Terminalbash
gitmoap languages get-service-languages --service-id <id>
curlbash
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.

Terminalbash
gitmoap contributors list-repo-contributors --repo-id <id>
curlbash
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.

Terminalbash
gitmoap contributors list-mr-participants --repo-id <id> --mr-id <id>
curlbash
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.

Terminalbash
gitmoap contributions get-my-contributions
curlbash
curl -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.

Terminalbash
gitmoap contributions get-user-contributions --user-id <id>
curlbash
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.

Terminalbash
gitmoap contributions get-sa-contributions --sa-id <id>
curlbash
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.

Terminalbash
gitmoap repos star --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos check-star --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos unstar --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos get-rendered-codeowners --repo-id <id>
curlbash
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.

Terminalbash
gitmoap repos transfer --repo-id <id> --new-group-id <new group id>
curlbash
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.

Terminalbash
gitmoap repos preview-transfer --repo-id <id> --new-group-id <id>
curlbash
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.

Terminalbash
gitmoap repos delete --repo-id <id>
curlbash
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

create_repoPOST /repositoriesOpen in the API reference

Token scope

repo:create

Counts against

repos_per_group

Send

Body
  • avatarobjectoptional
  • descriptionobjectoptional
  • display_nameobjectoptional
  • group_iduuid
  • repository_namestring
  • visibilityobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404The group named by `group_id` does not exist
  • 409A group or repository already holds that path
  • 429Repository 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

list_reposGET /repositoriesOpen in the API reference

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 request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404The 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

get_repoGET /repositories/{repo_id}Open in the API reference

Token scope

repo:read

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

No body.

GET/repositories/by-path/{full_path}repo:read

Get a repository by its full path

get_repo_by_pathGET /repositories/by-path/{full_path}Open in the API reference

Token scope

repo:read

Send

Path
  • full_pathstringFull hierarchical path (e.g. acme/platform/my-repo)

Refuses

  • 400Invalid path
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

No body.

GET/resolve/{full_path}repo:read

Resolve a full path to its entity type and ID

resolve_pathGET /resolve/{full_path}Open in the API reference

Token scope

repo:read

Send

Path
  • full_pathstringFull hierarchical path (e.g. acme/platform/my-repo)

Refuses

  • 400Invalid path
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Path 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

update_repoPUT /repositories/{repo_id}Open in the API reference

Token scope

repo:update

Send

Path
  • repo_iduuidRepository ID
Body
  • avatarobjectoptional
  • descriptionobjectoptional
  • display_nameobjectoptional
  • repository_nameobjectoptional
  • visibilityobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409The 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

transfer_repoPOST /repositories/{repo_id}/transferOpen in the API reference

Token scope

repo:transfer

Counts against

namespace_storage

Send

Path
  • repo_iduuidRepository ID
Body
  • new_group_iduuid

Refuses

  • 400Invalid request or same group
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository or destination group not found
  • 409Path conflict or visibility conflict
  • 429The 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

preview_repo_transferGET /repositories/{repo_id}/transfer-previewOpen in the API reference

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 group
  • 401Unauthenticated — no credential, or an invalid one
  • 403The caller could not make this transfer
  • 404Repository or destination group not found
  • 409The 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 changes stops short of everyone affected. The summary still counts them all.
DELETE/repositories/{repo_id}repo:delete

Delete a repository

delete_repoDELETE /repositories/{repo_id}Open in the API reference

Token scope

repo:delete

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409The 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

upload_repo_avatarPUT /repositories/{repo_id}/avatarOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden (Maintainer required)
  • 404Repository not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 501Avatar uploads are not available on this deployment

Receive

  • avatar_urlstring
  • upload_urlstring
DELETE/repositories/{repo_id}/avatarrepo:update

Delete a repository avatar

delete_repo_avatarDELETE /repositories/{repo_id}/avatarOpen in the API reference

Token scope

repo:update

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden (Maintainer required)
  • 404Repository not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 500Avatar 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

get_rendered_codeownersGET /repositories/{repo_id}/codeowners/renderedOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No 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

list_my_repositoriesGET /user/repositoriesOpen in the API reference

Token scope

repo:read

Send

Query
  • limitint32optionalMaximum number of items to return
  • next_tokenstringoptionalPagination token

Refuses

  • 401Unauthenticated
  • 403Forbidden — 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

list_my_starred_repositoriesGET /user/starredOpen in the API reference

Token scope

repo:read

Send

Query
  • limitint32optionalMaximum number of items to return
  • next_tokenstringoptionalPagination token

Refuses

  • 401Unauthenticated
  • 403Forbidden — 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

star_repoPOST /repositories/{repo_id}/starOpen in the API reference

Token scope

repo:star

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthorized
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409The 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

check_starGET /repositories/{repo_id}/starOpen in the API reference

Token scope

repo:read

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthorized
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

  • starredboolean
  • starred_atobjectoptionalWhen you starred it. Absent when starred is false.
DELETE/repositories/{repo_id}/starrepo:star

Unstar a repository

unstar_repoDELETE /repositories/{repo_id}/starOpen in the API reference

Token scope

repo:star

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthorized
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found or not starred
  • 409The 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

add_repo_memberPOST /repositories/{repo_id}/membersOpen in the API reference

Token scope

repo_member:add

Counts against

members_per_scope

Send

Path
  • repo_iduuidRepository ID
Body
  • access_leveldeveloperguestmaintainerownerreporter
  • user_iduuid

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository or user not found
  • 409User is already a member
  • 429Member 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

list_repo_membersGET /repositories/{repo_id}/membersOpen in the API reference

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 type
  • 401Authentication required — a roster is not public
  • 403Caller is not a member with at least Reporter
  • 404Repository 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

get_repo_memberGET /repositories/{repo_id}/members/{user_id}Open in the API reference

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 type
  • 401Authentication required — a roster is not public
  • 403Caller is not a member with at least Reporter
  • 404Repository 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

update_repo_memberPUT /repositories/{repo_id}/members/{user_id}Open in the API reference

Token scope

repo_member:update

Send

Path
  • repo_iduuidRepository ID
  • user_iduuidUser ID
Body
  • access_leveldeveloperguestmaintainerownerreporter

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository or member not found
  • 409The 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

remove_repo_memberDELETE /repositories/{repo_id}/members/{user_id}Open in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository or member not found
  • 409The 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

list_repos_for_userGET /users/{user_id}/repositoriesOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — not the target user
  • 404Not 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

list_treeGET /repositories/{repo_id}/treeOpen in the API reference

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
  • redirectbooleanoptionalfalse answers 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 to
  • 304The ref has not moved since the caller's `If-None-Match`
  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository or tree not found
  • 409The 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

list_tree_last_commitsGET /repositories/{repo_id}/tree/last-commitsOpen in the API reference

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
  • redirectbooleanoptionalfalse answers 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 to
  • 304The ref has not moved since the caller's `If-None-Match`
  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository or directory not found
  • 409The 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 with Cache-Control: no-store and no ETag, 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

get_blobGET /repositories/{repo_id}/blobOpen in the API reference

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)
  • redirectbooleanoptionalfalse answers 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 to
  • 304The ref has not moved since the caller's `If-None-Match`
  • 400Invalid request or file too large
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository or file not found
  • 409The repository is empty: nothing has been pushed to it, so there is no file to read

Receive

  • blob_shastring
  • commit_shastring
  • contentstring
  • encodingbase64utf8
  • lossybooleantrue when the file is text but not valid UTF-8, and content shows 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

get_repo_profileGET /repositories/{repo_id}/profileOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository or ref not found
  • 409The 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

grep_repoGET /repositories/{repo_id}/grepOpen in the API reference

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 q as 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 path
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository or ref not found
  • 409The 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

list_commitsGET /repositories/{repo_id}/commitsOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not 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

get_commitGET /repositories/{repo_id}/commits/{commit_ref}Open in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Commit 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

create_tagPOST /repositories/{repo_id}/tagsOpen in the API reference

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 request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found — or found and private, which is masked as absent
  • 409Tag 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}/tagstag:read

List tags in a repository

list_tagsGET /repositories/{repo_id}/tagsOpen in the API reference

Token scope

tag:read

Send

Path
  • repo_iduuidRepository ID
Query
  • limitint32optionalMaximum number of tags to return (1-100, default 20)
  • next_tokenstringoptionalPagination cursor
  • searchstringoptionalFilter tags by name substring
  • sortstringoptionalSort order: name_asc (default) or name_desc

Refuses

  • 400An unknown sort, an unreadable pagination token, or a malformed repository ID
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found — or found and private, which is masked as absent

Receive

  • items[]object18 fields
    • 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
  • next_tokenobjectoptional
GET/repositories/{repo_id}/tags/{tag_name}tag:read

Get a tag by name

get_tagGET /repositories/{repo_id}/tags/{tag_name}Open in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Tag 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

delete_tagDELETE /repositories/{repo_id}/tags/{tag_name}Open in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Tag not found
  • 409The 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

get_repo_languagesGET /repositories/{repo_id}/languagesOpen in the API reference

Token scope

repo:read

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository 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. null when the repository has never been indexed.
  • items[]object6 fields
    • bytesint64Bytes attributed to this language. Authoritative; share is 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.
    • typestringprogramming or markup — the only two kinds that reach a breakdown.
  • 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

get_service_languagesGET /catalog/services/{service_id}/languagesOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidService ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Service 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; share is 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.
    • typestringprogramming or markup — the only two kinds that reach a breakdown.
  • 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

list_repo_contributorsGET /repositories/{repo_id}/contributorsOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository 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 reference

Token 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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden
  • 404Repository 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.

get_my_contributionsGET /user/contributionsOpen in the API reference

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 query
  • 401Unauthenticated
  • 403Forbidden — 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_typestringuser or service_account.
  • tostringResolved inclusive range end, YYYY-MM-DD.
  • totalint64Sum of count across all cells in range.
GET/users/{user_id}/contributionsuser:read

Any user's public contribution heatmap.

get_user_contributionsGET /users/{user_id}/contributionsOpen in the API reference

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 query
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404User 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_typestringuser or service_account.
  • tostringResolved inclusive range end, YYYY-MM-DD.
  • totalint64Sum of count across all cells in range.
GET/service-accounts/{sa_id}/contributionsservice_account:read

A service account's contribution heatmap.

get_sa_contributionsGET /service-accounts/{sa_id}/contributionsOpen in the API reference

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 query
  • 401Unauthenticated
  • 403Forbidden
  • 404Service 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_typestringuser or service_account.
  • tostringResolved inclusive range end, YYYY-MM-DD.
  • totalint64Sum of count across 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.

LimitApplies toFreeProTeamEnterprise
Repositories in one groupeach group, counted separately202002 00020 000
Members on one repositoryeach repository502001 0005 000
Stored bytesthe whole namespace5 GiB100 GiB1 TiB10 TiB
Avatar image sizeeach upload5 MiB5 MiB5 MiB5 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/checkout was pasted where an id belongs
What to do
Use the id, or the by-path read, or the path resolver. The CLI's --repo-id accepts 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_id nor group_path was 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 path parameter 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, year was 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 404 to 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, never 404
What to do
Check you still have access. A private repository you were removed from answers 404 to 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>, then git push -u origin main. The repository's empty field 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 avatar field 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=false and read the pinned location out of the response body
A tree or file request comes back empty with 304
Cause
You sent If-None-Match and 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 200 with no commit rather than 404
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.email consistently 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 fetch to 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.

  • 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