runlot
ReferenceAPI

Deploy access control

Covers the 5 API operations in the access category.

MethodPathDescription
GET/v1/orgs/{orgSlug}/projects/{projectName}/accessDeploy access policy (runlot access)
PUT/v1/orgs/{orgSlug}/projects/{projectName}/accessUpdate the policy (runlot access set)
POST/v1/orgs/{orgSlug}/projects/{projectName}/access/bypassIssue a bypass key for automation (runlot access bypass --new)
DELETE/v1/orgs/{orgSlug}/projects/{projectName}/access/bypassRevoke the bypass key (runlot access bypass --revoke)
POST/v1/access/authorizeOne-time code for reaching a protected deploy (docs/access.md §3.4-3)

GET /v1/orgs/{orgSlug}/projects/{projectName}/access

Who can reach this deploy (docs/access.md §3.1). A project that has never set a policy is public, not a 404 — most projects are in that state.

No values are returned. The password never leaves on any surface, and the bypass secret is shown only once, when it is created.

Requires viewer or above — "is this deploy open" is a fact anyone in the organization needs to know.

operationId getAccess

StatusDescriptionResponse body
200PolicyAccessPolicy
403
404
503no_core — no cp-core connectionError

PUT /v1/orgs/{orgSlug}/projects/{projectName}/access

public keeps today's behaviour, org requires member or above in the org that owns the project, and password is one shared password per project — the moment it becomes per-user it is a subscriber-table problem and outside the scope of this API (docs/access.md §3.1).

If you send no password, the existing one stays. If a save that only changes the mode wiped the password, you would have to set it again on the way back, and that inconvenience buys no safety. To clear it, send a new value.

Requires admin or above. A boundary a member can change is not a boundary. Audit event access.set (the password is not recorded in the audit).

operationId setAccess

Request body: application/json · object

StatusDescriptionResponse body
200The updated policyAccessPolicy
400
403
404
503no_coreError

POST /v1/orgs/{orgSlug}/projects/{projectName}/access/bypass

The key CI uses to reach a protected deploy (docs/access.md §3.6). Send it in the request header Runlot-Access-Bypass: <secret>.

The value exists only in this response. You cannot see it again, and calling again kills the old value immediately — that is the only way to invalidate a leaked secret.

Without this key, CI cannot reach a protected deploy and you may end up turning protection off. Requires admin or above. The audit event access.bypass.new does not record the key value.

operationId newAccessBypass

StatusDescriptionResponse body
200Bypass key (shown once)AccessBypass
403
404
503no_coreError

DELETE /v1/orgs/{orgSlug}/projects/{projectName}/access/bypass

Returns 204 even when there is no bypass key. The access policy itself does not change; the bypass key is managed separately from the policy. Requires admin or above. Audit event access.bypass.revoke.

operationId revokeAccessBypass

StatusDescriptionResponse body
204Revoked
403
404
503no_coreError

POST /v1/access/authorize

This is the middle of the login round trip. When a request to a protected deploy comes back to the dashboard's /access/authorize screen, that screen makes this call with the session and moves to the redirect it receives. It is the front that sets the cookie on that callback — the fact that it cannot be set from another origin shaped this entire flow.

to must be a hostname we know. If we do not know it, the answer is 404: without this check the endpoint would be an open redirect that sends a logged-in user to any domain, and that domain would receive the code. next also accepts a path only (//evil.example is read as an absolute address).

Someone outside the org gets 403 — re-logging in does not change the result (§3.7). The point is that it requires viewer or above, not member or above: viewing a deploy is exactly a viewer's job.

§3.4-2 of the design document describes this flow as a browser GET /access/authorize. The dashboard stores the session token in localStorage, so the server cannot see the session on that GET. Only the party that performs the move changed from the server to the browser; the decision itself is unchanged.

operationId authorizeAccess

Request body: application/json · object

StatusDescriptionResponse body
200Callback URLAccessAuthorize
400
403
404
503no_coreError

On this page