runlot
ReferenceAPI

repos

Covers the 16 API operations in the repos category.

MethodPathDescription
GET/v1/orgs/{orgSlug}/projects/{projectName}/reposRepositories in the project (runlot repo list)
POST/v1/orgs/{orgSlug}/projects/{projectName}/reposCreate 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-updatesref history (runlot repo history)
GET/v1/me/ssh-keysMy SSH public keys (runlot key list)
POST/v1/me/ssh-keysRegister an SSH public key (runlot key add)
DELETE/v1/me/ssh-keys/{keyId}Remove an SSH public key (runlot key rm)
GET/v1/me/tokensMy access tokens (runlot token list)
POST/v1/me/tokensIssue 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}/refsBranches and tags (dashboard browse)
GET/v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/treeOne directory (dashboard browse)
GET/v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/blobOne file (dashboard browse)
GET/v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}/commitsCommit 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

StatusDescriptionResponse body
200Repository listobject
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

StatusDescriptionResponse body
201The created repositoryRepo
400
403
404
409A repository with the same name exists in this project (repo_exists)Error

GET /v1/orgs/{orgSlug}/projects/{projectName}/repos/{repoName}

One repository

operationId getRepo

StatusDescriptionResponse body
200RepositoryRepo
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

StatusDescriptionResponse body
204Updated
400
403
404
409A 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

StatusDescriptionResponse body
204Deleted
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

StatusDescriptionResponse body
200ref historyobject
403
404

GET /v1/me/ssh-keys

My SSH public keys (runlot key list)

operationId listSSHKeys

StatusDescriptionResponse body
200Key listobject

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

StatusDescriptionResponse body
201The registered keySSHKey
400
409The 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

StatusDescriptionResponse body
204Deleted
404

GET /v1/me/tokens

The plaintext is nowhere. What is stored is only the hash (§5.2).

operationId listAccessTokens

StatusDescriptionResponse body
200Token listobject

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

StatusDescriptionResponse body
201The 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

StatusDescriptionResponse body
204Revoked
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

StatusDescriptionResponse body
200ref listRepoRefs
403
404
503There 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

StatusDescriptionResponse body
200DirectoryRepoTree
403
404The 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

StatusDescriptionResponse body
200FileRepoBlob
403
404The 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

ParameterInRequiredTypeDescription
limitqueryNointeger
StatusDescriptionResponse body
200Commit listobject
403
404

On this page