Workspaces
The tenant boundary, and the only routes that are not workspace-scoped.
View as MarkdownA workspace is the tenant. Members, permissions, quotas and billing belong to it, and nothing crosses between workspaces.
The collection routes are different
POST and GET /v1/workspaces are the only routes with no permission check. They are
user-scoped rather than workspace-scoped, because a brand-new user has no workspace and
therefore no workspace-scoped claim to check.
curl -fsS https://api.saved.sh/v1/workspaces -H "Authorization: Bearer $TOKEN"{
"data": [
{ "id": "4b7e1d90-...", "name": "acme", "workos_org_id": "org_01H...", "role": "admin" }
],
"next_cursor": null
}This returns your memberships, so it is the endpoint that answers "which workspaces can I act on".
Create
curl -fsS -X POST https://api.saved.sh/v1/workspaces \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "analytics"}'Creating a workspace requires a human principal. A machine credential is refused with
403, whatever permissions it holds. This is one of only two gates expressed on the
principal's kind rather than on a permission, because there is no workspace-scoped permission
that could express it. Each call provisions an organization, a customer record and a
namespace, so leaving it open to any valid credential was not acceptable.
The creator becomes admin. Limit: 5 workspaces per user.
Read, update, delete
curl -fsS "https://api.saved.sh/v1/workspaces/$WID" -H "Authorization: Bearer $TOKEN"{
"id": "4b7e1d90-...",
"name": "acme",
"workos_org_id": "org_01H...",
"created_at": "2026-01-04T10:00:00Z",
"updated_at": "2026-08-08T09:14:02Z"
}| Method | Path | Permission |
|---|---|---|
GET | /v1/workspaces/{wid} | workspaces:read |
PATCH | /v1/workspaces/{wid} | workspaces:write |
DELETE | /v1/workspaces/{wid} | workspaces:write |
curl -fsS -X PATCH "https://api.saved.sh/v1/workspaces/$WID" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "acme-production"}'The workspace in a path must match your credential
Most routes carry {workspaceID}, and it is not a selector: it must be the workspace your
credential is scoped to.
| Situation | Response |
|---|---|
| Matches | Normal |
| Another workspace you are a member of | 403 wrong_workspace |
| A workspace that is not yours | 404, indistinguishable from one that does not exist |
That last row is deliberate. Returning 403 for "exists but not yours" and 404 for "does
not exist" would let anyone probe whether an ID exists in someone else's workspace.
To act on a different workspace, use a credential scoped to it. Session tokens are re-minted by switching; API keys are bound at creation.
Billing state
A workspace has a billing state that gates writes but never reads.
| State | New runs | Config changes | Reads and downloads |
|---|---|---|---|
trial, active, warned | Yes | Yes | Yes |
stopped | No | Yes | Yes |
blocked | No | No | Yes |
Reads and downloads are never gated on billing state. A workspace that is behind on payment can still list and download every artifact it has. Your backups are not leverage.
A write refused for this reason returns a 409 naming the state rather than a permission
error, since the credential is fine and the workspace is not.
Deleting a workspace
curl -fsS -X DELETE "https://api.saved.sh/v1/workspaces/$WID" -H "Authorization: Bearer $TOKEN"This removes the workspace and its records. Artifacts in your own destination buckets are left where they are, as everywhere else.
Deleting a workspace is not reversible and takes the audit trail with it. Export anything you
need to keep first, including GET /v1/workspaces/{wid}/audit.
Quotas
curl -fsS "https://api.saved.sh/v1/workspaces/$WID/quotas" -H "Authorization: Bearer $TOKEN"See Quotas. Permission: workspaces:read.