Skip to content
Contents
Workspace data

Workspaces

A workspace is the tenant: every channel, contact, conversation, key and invoice belongs to exactly one, and nothing crosses between them. It also owns the clock the rest of the platform is measured by.

The workspace

A person may belong to several, so a user access token alone is not enough to identify one — X-Workspace-Id is. An API key already names its workspace and ignores the header.

FieldTypeDescription
idstringThe workspace's public id, and the value of X-Workspace-Id.
name, slugstringThe display name and its slug.
statusstringActive or suspended.
is_personalbooleanWhether it is the personal workspace created with the account.
localestringThe default language for platform-generated copy.
timezonestringThe one clock. See below.
rolestringThe CALLER's role in it. Present on the routes that resolve a membership.
permissionsstring[]The caller's resolved permissions. A UI signal only — the backend re-derives them on every request.

The timezone is not a display preference

It is the clock business hours are evaluated in, the clock the away reply decides against, and the clock every analytics bucket is cut with. Changing it moves all three. It is never the caller's browser timezone, and deriving one locally is the bug those features exist to prevent.

Endpoints

GET/v1/workspacesPOST/v1/workspacesGET/v1/workspaces/{workspaceId}PATCH/v1/workspaces/{workspaceId}DELETE/v1/workspaces/{workspaceId}
curl
curl -X POST https://whats.azzamkh.sa/api/v1/workspaces \
  -H "Authorization: Bearer <access token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme", "locale": "ar", "timezone": "Asia/Riyadh" }'

# 201 Created — role and permissions are resolved for THIS user.
# {
#   "id": "ws_...",
#   "name": "Acme",
#   "slug": "acme",
#   "status": "active",
#   "is_personal": false,
#   "locale": "ar",
#   "timezone": "Asia/Riyadh",
#   "created_at": "2026-08-14T09:00:00.000Z",
#   "role": "owner",
#   "permissions": ["workspace.read", "workspace.manage", ...]
# }
  • Every route in this section is declared for sessionToken alone — an API key belongs to a workspace and cannot create, list or archive one. See authentication.
  • DELETE ARCHIVES. It answers the id and the new status, and it does not destroy the tenant's data.
  • Creating a workspace is limited to 20 an hour per person.
  • GET /v1/auth/me returns the USER only. Memberships come from GET /v1/workspaces, which is also where role and the resolved permissions live.

Naming the workspace on a request

Tenant context is explicit everywhere. There is no ambient default, deliberately: a key minted into the wrong workspace is not recoverable. See authentication.

CredentialHeaderResult
API keyThe key names its workspace. The header is ignored.
Access tokenX-Workspace-IdThe header decides, and it is required.
Access token400 WORKSPACE_CONTEXT_REQUIRED.

People and money

Members, roles, invitations and teams are all workspace sub-resources and have their own page: teams and invitations. The subscription and its statements are on billing.

Errors that matter here

CodeHTTPWhen it happens
WORKSPACE_NOT_FOUND404No such workspace, or the caller is not a member of it. The two are one answer on purpose.
WORKSPACE_CONTEXT_REQUIRED400A user access token was used without X-Workspace-Id on a route that needs one.
WORKSPACE_SUSPENDED403The workspace is suspended. Reads may still work; work that would send does not.
OWNER_REQUIRED403Only the workspace owner may do that.

Every code and its status is on the errors page.