Workspaces API
Full REST reference for the workspaces domain. All routes are under
/api/workspaces, require a Cognito JWT (Authorization: Bearer <token>), and are
backed by services/backend/lambda/workspaces_handler.py /
workspaces_service.py. See specs/api/workspaces.md in the repo for the
underlying spec this page is based on, and the
Workspaces feature guide for the product-level behavior.
Endpoints at a glance
| Method | Path | Description |
|---|---|---|
POST | /api/workspaces | Create a workspace |
GET | /api/workspaces | List my workspaces |
GET | /api/workspaces/{id} | Get a workspace |
PUT | /api/workspaces/{id} | Update a workspace |
DELETE | /api/workspaces/{id} | Delete a workspace |
POST | /api/workspaces/{id}/members | Add / invite a member |
GET | /api/workspaces/{id}/members | List members |
DELETE | /api/workspaces/{id}/members/{userId} | Remove a member |
Every error response has the same shape:
{ "error": "human-readable message" }Response bodies are the raw DynamoDB item as stored, so workspace and member
objects also carry the internal pk, sk, gsi1pk, gsi1sk, and entity_type
attributes — omitted from the examples below for readability. Treat them as
opaque storage plumbing, not part of the contract.
Create
POST /api/workspaces// Request
{ "name": "Acme Marketing", "description": "Q3 content calendar" }Creates the workspace and your owner membership in one call.
// 201 Response
{
"workspace_id": "ws_123",
"name": "Acme Marketing",
"description": "Q3 content calendar",
"owner_id": "user_1",
"status": "active",
"default_prompt_id": null,
"created_at": "2026-07-17T10:00:00+00:00",
"updated_at": "2026-07-17T10:00:00+00:00"
}| Status | Reason |
|---|---|
| 400 | name missing/empty after trimming, or over 100 characters |
List
GET /api/workspacesReturns every workspace you're currently a member of, each annotated with your
member_role. Soft-deleted workspaces are excluded.
// 200 Response
{
"workspaces": [
{
"workspace_id": "ws_123",
"name": "Acme Marketing",
"description": "Q3 content calendar",
"owner_id": "user_1",
"status": "active",
"default_prompt_id": null,
"created_at": "2026-07-17T10:00:00+00:00",
"updated_at": "2026-07-17T10:00:00+00:00",
"member_role": "owner"
}
]
}Get
GET /api/workspaces/{id}Same shape as Create's response, plus member_count and your own
member_role.
// 200 Response
{
"workspace_id": "ws_123",
"name": "Acme Marketing",
"description": "Q3 content calendar",
"owner_id": "user_1",
"status": "active",
"default_prompt_id": null,
"created_at": "2026-07-17T10:00:00+00:00",
"updated_at": "2026-07-17T10:00:00+00:00",
"member_count": 4,
"member_role": "owner"
}| Status | Reason |
|---|---|
| 403 | Caller is not a member |
| 404 | Workspace does not exist or is soft-deleted |
Update
PUT /api/workspaces/{id}Partial update — send only the fields you're changing. Requires your role to be
owner or editor.
// Request (any subset)
{
"name": "Acme Marketing (renamed)",
"description": "Updated description",
"default_prompt_id": "prompt_789",
"publishing_slots": ["FRI 07:30"]
}Transferring ownership: include owner_id in the same request to hand off
ownership. Only the current owner may do this, and the target must already be a
member.
// Request — ownership transfer
{ "owner_id": "user_2" }A transfer downgrades the previous owner to editor and promotes the target to
owner — there's no co-owner state. See
Transferring ownership.
// 200 Response
{ "workspace_id": "ws_123", "name": "...", "owner_id": "user_2", "...": "..." }| Status | Reason |
|---|---|
| 400 | owner_id given but the target user isn't a member |
| 403 | Caller's role is not owner/editor |
| 403 | Caller isn't owner but tried to transfer ownership |
| 404 | Workspace does not exist or is soft-deleted (checked after the role check — a non-privileged caller against a bogus ID still gets 403, never 404) |
Delete
DELETE /api/workspaces/{id}Soft-deletes the workspace (status → "deleted"). Owner-only.
A non-member and a non-owner member get the same 403 message here — this
endpoint doesn't distinguish "you're not in this workspace" from "you're in it but
not the owner."
Response: 204 No Content
| Status | Reason |
|---|---|
| 403 | Caller's role is not owner (or caller is not a member at all) |
| 404 | Workspace does not exist or is already soft-deleted |
Add Member
POST /api/workspaces/{id}/membersRequires your role to be owner or editor.
// Request
{ "email": "new.person@example.com", "role": "editor" }email is required (trimmed + lowercased). role defaults to "viewer"; if given,
must be owner, editor, or viewer.
// 201 Response — existing user, added immediately
{
"workspace_id": "ws_123",
"user_id": "user_2",
"role": "editor",
"joined_at": "2026-07-17T10:05:00+00:00"
}// 201 Response — unregistered email, pending invite created
{ "status": "invited", "email": "new.person@example.com", "role": "editor" }A pending invite is consumed automatically by Cognito's post-confirmation hook the moment someone signs up with that email — see Inviting someone. No email/Slack notification is sent by this endpoint itself.
| Status | Reason |
|---|---|
| 400 | email missing, or role isn't one of owner/editor/viewer |
| 403 | Caller's role is not owner/editor |
| 404 | Workspace does not exist or is soft-deleted |
| 409 | Target user is already a member |
List Members
GET /api/workspaces/{id}/membersAny member (any role) can call this.
// 200 Response
{
"members": [
{
"workspace_id": "ws_123",
"user_id": "user_1",
"role": "owner",
"joined_at": "2026-07-17T10:00:00+00:00",
"email": "owner@example.com",
"name": "Jane Doe"
}
]
}| Status | Reason |
|---|---|
| 403 | Caller is not a member |
Remove Member
DELETE /api/workspaces/{id}/members/{userId}Two allowed cases:
- Removing yourself (
{userId}= your own ID): allowed for any role exceptowner. - Removing someone else: only allowed if you're the
owner.
Response: 204 No Content
| Status | Reason |
|---|---|
| 400 | The owner tried to remove themselves — transfer ownership first |
| 403 | Caller is not a member |
| 403 | Caller isn't the owner but tried to remove someone else |
| 404 | {userId} is not a member of the workspace |
See also
- Workspaces feature guide — product behavior, roles, and the full authorization matrix.
- Workspaces MCP tools — the read-only
list_workspacestool. - Admin API —
GET /api/admin/workspacesand the recover/hard-delete operations, a separate admin-only surface.