Deployment access control
Covers 5 API operations in the access category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/access | Deployment access policy (runlot access) |
| PUT | /v1/orgs/{orgSlug}/projects/{projectName}/access | Changes the policy (runlot access set) |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/access/bypass | Issues a bypass key for automation (runlot access bypass --new) |
| DELETE | /v1/orgs/{orgSlug}/projects/{projectName}/access/bypass | Revoke a bypass key (runlot access bypass --revoke) |
| POST | /v1/access/authorize | One-time code to reach a protected deployment (docs/access.md §3.4-3) |
GET /v1/orgs/{orgSlug}/projects/{projectName}/access
Who can reach this deployment (docs/access.md §3.1). A project that has
never set a policy is public, not 404 — most projects are
in that state.
There is no value. The password never appears on any surface, and a bypass secret is shown only once, at creation time.
viewer and above — whether this deployment is open is a fact anyone in the org needs to know.
operationId getAccess
| Status code | Description | Response body |
|---|---|---|
| 200 | Policy | AccessPolicy |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core — no connection to cp-core | Error |
PUT /v1/orgs/{orgSlug}/projects/{projectName}/access
public keeps the current behavior, org means member or higher in the org the project
belongs to, and password means one shared password per project — the moment it
becomes per-user, that is a subscriber table's problem and out of scope for this API
(docs/access.md §3.1).
If you don't send a password, the existing one stays. If a save that only changes the mode also clears the password, you would have to set it again when switching back, and that inconvenience buys no safety. Send a new value if you want to clear it.
Requires admin or higher. If a member could change the boundary, it would not be a
boundary. Audited as access.set (the password itself is not included in the audit).
operationId setAccess
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | The policy that changed | AccessPolicy |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |
POST /v1/orgs/{orgSlug}/projects/{projectName}/access/bypass
This is the key CI uses to access a protected deployment (docs/access.md
§3.6). Send it in the Runlot-Access-Bypass: <secret> request header.
The value appears only in this response. You cannot view it again, and calling this again immediately invalidates the old value — this is the only way to revoke a leaked secret.
Without this key, CI cannot access protected deployments, which may force
you to turn off protection.
Requires admin or higher. The audit event access.bypass.new does not
record the key value.
operationId newAccessBypass
| Status code | Description | Response body |
|---|---|---|
| 200 | Bypass key (shown only once) | AccessBypass |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |
DELETE /v1/orgs/{orgSlug}/projects/{projectName}/access/bypass
Returns 204 even if there is no bypass key. The access policy itself does not change.
The bypass key is managed separately from the policy. Admin or above can use this.
Audited as access.bypass.revoke.
operationId revokeAccessBypass
| Status code | Description | Response body |
|---|---|---|
| 204 | detached | — |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |
POST /v1/access/authorize
This sits in the middle of the login round trip. When a request to a protected
deployment lands back on the dashboard's /access/authorize screen, that screen makes
this call with the session and moves to the received redirect. The front end that
receives that callback is what sets the cookie — the fact that it cannot be set from a
different origin shaped this whole flow.
to must be a hostname we know. If it isn't, this returns 404: without this check,
this endpoint would be an open redirect that sends a session-holding user to any domain,
and that domain would receive the code. next also only accepts a path
(//evil.example is read as an absolute address).
Someone outside the org gets 403 — even re-logging in gives the same result (§3.7). The point is viewer or above, not member or above: viewing a deployment is exactly a viewer's job.
Section §3.4-2 of the docs describes this flow as the browser's GET /access/authorize.
Because the dashboard stores the session token in localStorage, the server cannot see
the session on that GET. So only who does the moving switched from server to browser,
and the verdict stays the same.
operationId authorizeAccess
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | Callback URL | AccessAuthorize |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |