---
title: "Workspaces"
description: "The tenant boundary, and the only routes that are not workspace-scoped."
url: "https://saved.sh/docs/api/workspaces"
---

A workspace is the tenant. Members, permissions, quotas and billing belong to it, and nothing
crosses between workspaces.

## The collection routes are different [#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.

```bash
curl -fsS https://api.saved.sh/v1/workspaces -H "Authorization: Bearer $TOKEN"
```

```json
{
  "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 [#create]

```bash
curl -fsS -X POST https://api.saved.sh/v1/workspaces \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "analytics"}'
```

<Callout type="error">
  **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.
</Callout>

The creator becomes `admin`. Limit: 5 workspaces per user.

## Read, update, delete [#read-update-delete]

```bash
curl -fsS "https://api.saved.sh/v1/workspaces/$WID" -H "Authorization: Bearer $TOKEN"
```

```json
{
  "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` |

```bash
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 [#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 |

<Callout>
  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.
</Callout>

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 [#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                 |

<Callout>
  **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.
</Callout>

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 [#deleting-a-workspace]

```bash
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.

<Callout type="error">
  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`.
</Callout>

## Quotas [#quotas]

```bash
curl -fsS "https://api.saved.sh/v1/workspaces/$WID/quotas" -H "Authorization: Bearer $TOKEN"
```

See [Quotas](/docs/api/quotas). Permission: `workspaces:read`.

## Next [#next]

<Cards>
  <Card href="/docs/api/members" title="Members" description="Membership and invitations." />

  <Card href="/docs/api/roles" title="Roles" description="Bundling permissions." />

  <Card href="/docs/api/quotas" title="Quotas" description="Limits and usage." />
</Cards>
