---
title: Workspaces and environments
description: Create and manage workspaces and environments, and scope API requests to them.
url: https://hexelstudio.com/docs/identity/workspaces
---

A workspace groups resources inside an organization, for example one per team or product. An environment is a stage inside a workspace, such as Production or Development. For how they fit into the hierarchy, see [Organizations, workspaces and environments](https://hexelstudio.com/docs/identity/organizations).

Every organization starts with a **Default** workspace that has two environments: **Production** (the default environment) and **Development**.

The examples use `$ACCESS_TOKEN`, an access token for your organization. Workspace and environment requests always act in the token's organization. A workspace or environment of another organization answers `404`, exactly like one that doesn't exist.

## Required permissions

| Operation | Permission |
| --- | --- |
| Create a workspace | `workspace:workspace:create`, granted at organization level |
| List, get, or get the default workspace | `workspace:workspace:read` |
| Update a workspace | `workspace:workspace:update` |
| Delete a workspace | `workspace:workspace:delete` |
| List, add or remove workspace members | `workspace:member:manage` |
| Create an environment | `env:env:create` |
| List, get, or get the default environment | `env:env:read` |
| Update an environment | `env:env:update` |
| Delete an environment | `env:env:delete` |

The owner, admin and member roles hold all of these by default. Viewers can only read. See [Members](https://hexelstudio.com/docs/identity/members#member-roles).

## Workspaces

### Create a workspace

<CodeGroup>

```bash curl
curl -X POST https://api.hexelstudio.com/v1/workspaces \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Payments", "slug": "payments", "description": "Payments team"}'
```

```typescript TypeScript
const res = await fetch("https://api.hexelstudio.com/v1/workspaces", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Payments",
    slug: "payments",
    description: "Payments team",
  }),
});
const workspace = await res.json();
```

```python Python
import os
import requests

res = requests.post(
    "https://api.hexelstudio.com/v1/workspaces",
    headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
    json={"name": "Payments", "slug": "payments", "description": "Payments team"},
)
workspace = res.json()
```

</CodeGroup>

<ParamField body="name" type="string">
  Display name.
</ParamField>

<ParamField body="slug" type="string">
  Short identifier, for example `payments`.
</ParamField>

<ParamField body="description" type="string">
  Optional description.
</ParamField>

The response is `201 Created`:

```json
{
  "id": "<workspace-id>",
  "organization_id": "<org-id>",
  "name": "Payments",
  "slug": "payments",
  "description": "Payments team",
  "is_default": false,
  "is_active": true,
  "created_by_user_id": "<user-id>",
  "created_at": "2026-10-20T09:30:00Z",
  "updated_at": "2026-10-20T09:30:00Z"
}
```

A new workspace has no environments. [Create them](#create-an-environment) next.

### List and get workspaces

```bash
# All workspaces of your organization
curl https://api.hexelstudio.com/v1/workspaces \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# One workspace
curl https://api.hexelstudio.com/v1/workspaces/$WORKSPACE_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# The default workspace
curl https://api.hexelstudio.com/v1/workspaces/default \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The default workspace is the one with `is_default: true`. If there is none, `/workspaces/default` returns the oldest workspace.

### Update a workspace

Send only the fields to change:

```bash
curl -X PATCH https://api.hexelstudio.com/v1/workspaces/$WORKSPACE_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Payments platform"}'
```

`id`, `organization_id`, `created_by_user_id` and `is_default` can't be changed; they are ignored if you send them. The response is the workspace.

### Delete a workspace

```bash
curl -X DELETE https://api.hexelstudio.com/v1/workspaces/$WORKSPACE_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The response is `204 No Content`. Deleting a workspace permanently deletes its environments and its member list. It can't be undone. This applies to the Default workspace too: it is not protected from deletion.

### Workspace members

A workspace has its own member list. Only members of the workspace's organization can be added; anyone else is refused with `422`:

```bash
curl -X POST https://api.hexelstudio.com/v1/workspaces/$WORKSPACE_ID/members \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "<user-id>", "role": "member"}'
```

```json
{
  "error": "not_org_member",
  "message": "the user is not a member of the workspace's organization"
}
```

<ParamField body="user_id" type="string" required>
  The user ID of a member of the organization.
</ParamField>

<ParamField body="role" type="string" default="MEMBER">
  A label for the person's role in the workspace.
</ParamField>

List the members with `GET /v1/workspaces/{workspace_id}/members`. Remove one with `DELETE /v1/workspaces/{workspace_id}/members/{member_id}`, where `member_id` is the `id` of the workspace membership, not the user ID.

<Note>
The workspace member list records who works in a workspace, for your own bookkeeping. Adding someone to it grants them nothing, and removing them takes nothing away: Hexel Studio does not use it to allow or deny requests. Access to a workspace comes only from [policies](https://hexelstudio.com/docs/identity/policies). When someone is removed from the organization, their workspace memberships in it are removed too.
</Note>

## Environments

### Create an environment

```bash
curl -X POST https://api.hexelstudio.com/v1/environments \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id": "'"$WORKSPACE_ID"'", "name": "Staging", "slug": "staging", "type": "STAGING"}'
```

<ParamField body="workspace_id" type="string" required>
  The workspace to create the environment in. A missing or unknown workspace returns `404`.
</ParamField>

<ParamField body="name" type="string">
  Display name.
</ParamField>

<ParamField body="slug" type="string">
  Short identifier, for example `staging`.
</ParamField>

<ParamField body="type" type="string" default="DEVELOPMENT">
  The kind of stage, for example `DEVELOPMENT`, `STAGING`, `PRODUCTION` or `SANDBOX`.
</ParamField>

<ParamField body="description" type="string">
  Optional description.
</ParamField>

The response is `201 Created`:

```json
{
  "id": "<environment-id>",
  "workspace_id": "<workspace-id>",
  "name": "Staging",
  "slug": "staging",
  "type": "STAGING",
  "is_default": false,
  "is_protected": false,
  "is_active": true,
  "settings": "{}",
  "created_by_user_id": "<user-id>",
  "created_at": "2026-10-20T09:30:00Z",
  "updated_at": "2026-10-20T09:30:00Z"
}
```

`is_protected` is a label. The Production environment of the Default workspace has it set. Hexel Studio does not enforce it.

### List and get environments

```bash
# All environments of your organization
curl https://api.hexelstudio.com/v1/environments \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# The environments of one workspace
curl "https://api.hexelstudio.com/v1/environments?workspace_id=$WORKSPACE_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# One environment
curl https://api.hexelstudio.com/v1/environments/$ENVIRONMENT_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# The default environment of a workspace
curl "https://api.hexelstudio.com/v1/environments/default?workspace_id=$WORKSPACE_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

`/environments/default` needs `workspace_id`. It returns the workspace's environment with `is_default: true`, or the oldest one if none is marked.

### Update an environment

```bash
curl -X PATCH https://api.hexelstudio.com/v1/environments/$ENVIRONMENT_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Pre-production"}'
```

`id` and `workspace_id` can't be changed; they are ignored if you send them. An environment can't move to another workspace.

### Delete an environment

```bash
curl -X DELETE https://api.hexelstudio.com/v1/environments/$ENVIRONMENT_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The response is `204 No Content`. The environment is deleted permanently. Default and protected environments can be deleted too.

### Deleted entries in lists

Workspace and environment objects carry a `deleted_at` field. It is absent on live entries. If a list contains an entry with `deleted_at` set, treat it as deleted.

## Scope a request to a workspace or environment

Send `X-Workspace-Id` and, inside it, `X-Environment-Id` to make a request in that workspace or environment:

```bash
curl https://api.hexelstudio.com/v1/... \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "X-Environment-Id: $ENVIRONMENT_ID"
```

The headers choose where the permission check happens. They never change the organization, which always comes from the token.

- Permissions granted at organization level apply in every workspace and environment.
- Permissions granted for a workspace apply only when `X-Workspace-Id` names that workspace. Permissions granted for an environment need both headers.
- A request is denied with `403` when `X-Workspace-Id` names a workspace that isn't an active workspace of your organization, when `X-Environment-Id` names an environment outside that workspace, or when `X-Environment-Id` is sent without `X-Workspace-Id`.

Creating a workspace is an organization-level operation: it ignores both headers. See [Headers](https://hexelstudio.com/docs/identity/headers) for every header the API reads.

## Limits

Two plan limits apply. See [Limits](https://hexelstudio.com/docs/identity/limits) for your plan's values.

| Limit | What counts |
| --- | --- |
| `workspaces_per_org` | Workspaces in the organization, including the Default workspace. |
| `environments_per_workspace` | Environments in the workspace. |

Deleted workspaces and environments don't count. A create over the limit fails with `403`:

```json
{
  "error": "quota_exceeded",
  "quota_key": "environments_per_workspace",
  "limit": 2,
  "current": 2,
  "message": "plan limit reached: this workspace can have at most 2 environments; upgrade the plan or contact Hexel"
}
```

<Note>
On the default plan a workspace can have 2 environments, so the Default workspace is already at its limit with Production and Development. Create a new workspace, or delete an environment, to add another one.
</Note>
