repos
Covers 16 API operations in the repos category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos | List of repositories for the project (runlot repo list) |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/repos | Creates a repository (runlot repo create) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName} | A single repository |
| PATCH | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName} | Changes the name, visibility, or default branch (runlot repo rename|visibility) |
| DELETE | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName} | Deletes a repository (runlot repo delete) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/ref-updates | Ref history (runlot repo history) |
| GET | /v1/me/ssh-keys | My SSH public keys (runlot key list) |
| POST | /v1/me/ssh-keys | Registers an SSH public key (runlot key add) |
| DELETE | /v1/me/ssh-keys/{keyId} | Removes an SSH public key (runlot key rm) |
| GET | /v1/me/tokens | My access tokens (runlot token list) |
| POST | /v1/me/tokens | Issue an access token (runlot token create) |
| DELETE | /v1/me/tokens/{tokenId} | Revoke an access token (runlot token revoke) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/refs | Branches and tags (dashboard browsing) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/tree | A single directory (dashboard browsing) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/blob | A single file (dashboard browse) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/commits | Commit log (dashboard browse) |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos
Requires viewer or higher.
A repository belongs to a project (docs/git-hosting.md §19). Authorization comes from the org role — a project has no role of its own — but name uniqueness and listing are scoped to the project. One project can have several repositories (app, API, infra).
operationId listRepos
| Status code | Description | Response body |
|---|---|---|
| 200 | List of repositories | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/projects/{projectName}/repos
Requires member or higher. Audited as repo.create.
Does not create anything on disk (docs/git-hosting.md §10). It only inserts a row, and node-git creates the repository directory on first access — that way this call succeeds even while the git node is down.
operationId createRepo
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 201 | The repository that was created | Repo |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | This project has a repository with the same name (repo_exists) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}
A single repository
operationId getRepo
| Status code | Description | Response body |
|---|---|---|
| 200 | Repository | Repo |
| 403 | — | — |
| 404 | — | — |
PATCH /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}
Requires admin. Audited as repo.update.
Renaming does not touch the disk (§7.1 — the path does not contain the name). If the name changes while a push is in progress, that push is not interrupted.
operationId updateRepo
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 204 | changed | — |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | A repository with that name already exists (repo_exists) | Error |
DELETE /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}
Requires admin. Audited as repo.delete.
This is a soft delete (§7.4). It disappears from the URL immediately, and the disk copy is deleted after a retention period — irreversible destruction always comes last, and there must be a cancellation window.
operationId deleteRepo
| Status code | Description | Response body |
|---|---|---|
| 204 | deleted | — |
| 403 | — | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/ref-updates
Requires viewer or higher. The most recent 200 entries.
This is the repository's history, not a copy of the audit log (§6.1). old is the
only clue for recovering a commit lost to a force-push.
operationId listRefUpdates
| Status code | Description | Response body |
|---|---|---|
| 200 | Ref history | object |
| 403 | — | — |
| 404 | — | — |
GET /v1/me/ssh-keys
My SSH public keys (runlot key list)
operationId listSSHKeys
| Status code | Description | Response body |
|---|---|---|
| 200 | Key list | object |
POST /v1/me/ssh-keys
The fingerprint is globally UNIQUE (§5.3). The key is the identity itself, so a
duplicate makes the identity ambiguous — authentication where identity depends on
registration order is not authentication. A key that is already registered returns 409
ssh_key_exists.
Allowed: ed25519, ecdsa-sha2-nistp256/384/521, rsa (2048 bits or more). DSA is rejected.
operationId addSSHKey
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 201 | Registered key | SSHKey |
| 400 | — | — |
| 409 | Public key already registered (ssh_key_exists) | Error |
DELETE /v1/me/ssh-keys/{keyId}
Bypasses the authorization cache (§5.4). Deleting a key exists precisely because you want to block it right now, so making it wait for the TTL would make the feature a lie.
operationId deleteSSHKey
| Status code | Description | Response body |
|---|---|---|
| 204 | deleted | — |
| 404 | — | — |
GET /v1/me/tokens
The plaintext exists nowhere. Only the hash is stored (§5.2).
operationId listAccessTokens
| Status code | Description | Response body |
|---|---|---|
| 200 | Token list | object |
POST /v1/me/tokens
The plaintext is carried in this response only once. You cannot see it again.
git sends Basic auth over HTTPS, and we ignore the username and only look at the token
in the password field (§5.2). The prefix runlot_pat_ is fixed — it needs to be
mechanically detectable in repositories, logs, and pastes for leak scanning to work.
operationId createAccessToken
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 201 | The issued token (including the plaintext) | AccessTokenCreated |
| 400 | — | — |
DELETE /v1/me/tokens/{tokenId}
Sets revokedAt instead of deleting the row — deleting it would leave
no way to answer "how long was this token alive". Bypasses the
authorization cache (§5.4).
operationId revokeAccessToken
| Status code | Description | Response body |
|---|---|---|
| 204 | discarded | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/refs
Requires viewer or higher.
Browsing authenticates with a dashboard session, while git transport authenticates with a PAT or SSH (docs/git-hosting.md §12.2). If both authentications lived on one surface, that process would have to handle session cookies too, extending the CSRF surface to repository writes. So the dashboard goes through this path, and cp-public relays to node-git's internal read surface — authorization ends here.
operationId getRepoRefs
| Status code | Description | Response body |
|---|---|---|
| 200 | List of refs | RepoRefs |
| 403 | — | — |
| 404 | — | — |
| 503 | The git node is missing or unreachable (git_unavailable, git_unreachable) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/tree
Requires viewer or higher. path is the root when empty.
Truncates at 1000 entries — unbounded browsing is a way for a single repository to
kill the dashboard. If truncated, truncated is true.
A path that escapes the tree (a/../..) returns 404. Silently dropping .. would
return a different path than the one requested.
operationId getRepoTree
| Status code | Description | Response body |
|---|---|---|
| 200 | Directory | RepoTree |
| 403 | — | — |
| 404 | The repository, ref, or path does not exist (no_ref·no_path) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/blob
Requires viewer or higher.
Content over 1 MiB is not included (tooLarge). It is not read and
then truncated; it is never read at all — once it is read, the memory is
already spent. Binary files also carry no content (binary).
operationId getRepoBlob
| Status code | Description | Response body |
|---|---|---|
| 200 | File | RepoBlob |
| 403 | — | — |
| 404 | The repository, ref, or path does not exist (no_ref·no_path) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/commits
Requires viewer or higher. Default 200, maximum 200.
operationId getRepoCommits
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
limit | query | No | integer | — |
| Status code | Description | Response body |
|---|---|---|
| 200 | Commit list | object |
| 403 | — | — |
| 404 | — | — |