repos
Covers the 16 API operations in the repos category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos | Repositories in the project (runlot repo list) |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/repos | Create a repository (runlot repo create) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName} | One repository |
| PATCH | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName} | Change the name, visibility or default branch (runlot repo rename|visibility) |
| DELETE | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName} | Delete 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 | Register an SSH public key (runlot key add) |
| DELETE | /v1/me/ssh-keys/{keyId} | Remove 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 browse) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/tree | One directory (dashboard browse) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/blob | One 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 above.
A repository belongs to a project (docs/git-hosting.md §19). Authorization comes from the org role — a project has no roles — but the uniqueness of the name and the unit of the list are the project. One project holds several repositories (app, API, infrastructure).
operationId listRepos
| Status | Description | Response body |
|---|---|---|
| 200 | Repository list | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/projects/{projectName}/repos
Requires member or above. Audit event repo.create.
It does not create anything on disk (docs/git-hosting.md §10). It inserts one row and that is all; node-git creates the repository directory on first access — that is what lets this call succeed even while the git node is down.
operationId createRepo
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 201 | The created repository | Repo |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | A repository with the same name exists in this project (repo_exists) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}
One repository
operationId getRepo
| Status | Description | Response body |
|---|---|---|
| 200 | Repository | Repo |
| 403 | — | — |
| 404 | — | — |
PATCH /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}
Requires admin. Audit event repo.update.
A rename does not touch the disk (§7.1 — the path does not contain the name). Even if the name changes in the middle of a push in progress, that push is not cut off.
operationId updateRepo
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 204 | Updated | — |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | A repository with that name already exists (repo_exists) | Error |
DELETE /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}
Requires admin. Audit event repo.delete.
This is a soft delete (§7.4). It disappears from the URL immediately and the disk is erased after the retention period — irreversible destruction always comes last and has to have a window in which it can be cancelled.
operationId deleteRepo
| Status | Description | Response body |
|---|---|---|
| 204 | Deleted | — |
| 403 | — | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/ref-updates
Requires viewer or above. The most recent 200 entries.
This is not a copy of the audit log but the repository's own history (§6.1). old is the only clue for getting back a commit lost to a force-push.
operationId listRefUpdates
| Status | Description | Response body |
|---|---|---|
| 200 | ref history | object |
| 403 | — | — |
| 404 | — | — |
GET /v1/me/ssh-keys
My SSH public keys (runlot key list)
operationId listSSHKeys
| Status | Description | Response body |
|---|---|---|
| 200 | Key list | object |
POST /v1/me/ssh-keys
The fingerprint is globally UNIQUE (§5.3). The key is the identity, so a duplicate makes the identity ambiguous — an authentication whose identity depends on the order of registration is not authentication. An already registered key gets 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 | Description | Response body |
|---|---|---|
| 201 | The registered key | SSHKey |
| 400 | — | — |
| 409 | The public key is already registered (ssh_key_exists) | Error |
DELETE /v1/me/ssh-keys/{keyId}
It bypasses the authorization cache (§5.4). "I want to block this right now" is the very reason someone deletes a key, so making them wait out the TTL would turn the feature into a lie.
operationId deleteSSHKey
| Status | Description | Response body |
|---|---|---|
| 204 | Deleted | — |
| 404 | — | — |
GET /v1/me/tokens
The plaintext is nowhere. What is stored is only the hash (§5.2).
operationId listAccessTokens
| Status | Description | Response body |
|---|---|---|
| 200 | Token list | object |
POST /v1/me/tokens
The plaintext appears in this response only once. You cannot see it again.
git sends Basic auth over HTTPS, and we ignore the user name and look only at the token in the password field (§5.2). The prefix runlot_pat_ is fixed — leak scanning only works if it can be detected mechanically in repositories, logs and pastes.
operationId createAccessToken
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 201 | The issued token (plaintext included) | AccessTokenCreated |
| 400 | — | — |
DELETE /v1/me/tokens/{tokenId}
It stamps revokedAt instead of deleting the row — if you delete it, you cannot answer "how long was this token alive". It bypasses the authorization cache (§5.4).
operationId revokeAccessToken
| Status | Description | Response body |
|---|---|---|
| 204 | Revoked | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/refs
Requires viewer or above.
Browse authenticates with the dashboard session, while the git transport authenticates with PAT and SSH (docs/git-hosting.md §12.2). If both authentications live on one surface, that process ends up handling session cookies and the CSRF surface grows to include repository writes. So the screen comes through this path, and cp-public relays to node-git's internal read surface — authorization ends here.
operationId getRepoRefs
| Status | Description | Response body |
|---|---|---|
| 200 | ref list | RepoRefs |
| 403 | — | — |
| 404 | — | — |
| 503 | There is no git node, or it cannot be reached (git_unavailable·git_unreachable) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/tree
Requires viewer or above. An empty path means the root.
It cuts off at 1000 entries — an unbounded browse is a way to kill the dashboard with a single repository. If it was cut off, truncated is true.
A path that leaves the tree (a/../..) is a 404. Silently dropping .. would answer with a different path than the one that was asked about.
operationId getRepoTree
| Status | 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 above.
Above 1 MiB the contents are not included (tooLarge). It does not read and then cut; it does not read at all — by the time it has read, the memory is already gone. Binary files have no contents either (binary).
operationId getRepoBlob
| Status | 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 above. Default 200, maximum 200.
operationId getRepoCommits
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
limit | query | No | integer | — |
| Status | Description | Response body |
|---|---|---|
| 200 | Commit list | object |
| 403 | — | — |
| 404 | — | — |