Skip to main content

Workspaces & accounts

A workspace is the tenant boundary in Orunbase: members, roles, projects, config, audit, usage, and billing all hang off one. The API canonically calls this resource an organization/v1/organizations/… — and /v1/workspaces/* is an accepted alias that rewrites to the same handlers, so the two spellings return identical results (the alias additionally mirrors every orgId as workspaceId in responses).

Your first workspace is created automatically on first login; you never start in an empty account.

Three ways to reference a workspace

Every workspace carries three references, and all three are accepted interchangeably in URL paths — the edge resolves the segment to the canonical id before routing:

ReferenceExampleProperties
Canonical idorg_1a2b3c4d5e6f70819203a4b5c6d7e8f9Opaque primary key; what the API returns as id
Workspace IDws_a1b2c3d4Immutable public ref (workspaceRef) — safe to commit, quote, automate
SlugacmeMutable vanity label — human-friendly, can be renamed
# These are the same request:
curl https://api.orunbase.com/v1/organizations/org_1a2b3c4d5e6f70819203a4b5c6d7e8f9 -H "Authorization: Bearer $TOKEN"
curl https://api.orunbase.com/v1/organizations/ws_a1b2c3d4 -H "Authorization: Bearer $TOKEN"
curl https://api.orunbase.com/v1/organizations/acme -H "Authorization: Bearer $TOKEN"

An unresolvable ws_… or slug segment returns 404 not_found at the edge. In scripts and CI, prefer the ws_… Workspace ID — a slug rename will never break it.

tip

Use workspaceRef (ws_…) anywhere a reference outlives the current session: intent files, pipeline variables, runbooks.

Create a workspace

POST /v1/organizations with a name (1–100 chars) and optional slug (2–63 chars, lowercase alphanumeric + hyphens). Omitting slug derives one from the name. The creator becomes the workspace's owner, and the free plan is assigned at bootstrap.

curl -X POST https://api.orunbase.com/v1/organizations \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f2a9d1e-create-acme" \
-d '{"name": "Acme", "slug": "acme"}'
{
"data": {
"organization": { "id": "org_1a2b3c4d5e6f70819203a4b5c6d7e8f9", "name": "Acme", "slug": "acme", "createdAt": "2026-07-02T12:00:00.000Z" },
"membership": { "role": "owner", "joinedAt": "2026-07-02T12:00:00.000Z" }
},
"meta": { "requestId": "req_8c9d0e1f2a3b", "cursor": null }
}
const { organization } = await client.workspaces.create({ name: "Acme", slug: "acme" });
// client.organizations.create(...) is the same call under the legacy name

A duplicate slug returns 409 conflict. Creating an additional workspace (beyond your first) is gated by the account's plan: it requires feature.multi_org and headroom under limit.organizations on the billing parent, otherwise 412 precondition_failed with a reason in details. An allowed additional workspace is created as a child of your account and inherits the parent's plan entitlements (fan-out).

Which account does it land under?

Your own, and only ever your own: the earliest-created Account root you hold the owner role on. That is the account whose plan the gate checks, whose entitlements fan out to the new workspace, and whose subscription the billing rolls up to.

Workspaces that were shared with you are excluded from that choice — being invited into somebody else's workspace, at any role, never makes their account yours. So:

  • If you already own an account, every extra workspace you create joins it, no matter how many other accounts have invited you.
  • If every workspace you can see was shared with you, you own no account yet: your next workspace starts a new Account root of your own, standalone and on the free plan, and it is not gated by anyone else's plan.

The workspace count checked against limit.organizations is likewise the count of workspaces in your account — workspaces shared with you from elsewhere don't consume your plan's headroom.

List and get workspaces

curl "https://api.orunbase.com/v1/organizations?limit=20" -H "Authorization: Bearer $TOKEN"
{
"data": {
"organizations": [
{
"id": "org_1a2b3c4d5e6f70819203a4b5c6d7e8f9",
"name": "Acme",
"slug": "acme",
"workspaceRef": "ws_a1b2c3d4",
"accountId": "ws_a1b2c3d4",
"kind": "account",
"isAccountRoot": true,
"role": "owner",
"status": "active",
"createdAt": "2026-07-02T12:00:00.000Z"
}
]
},
"meta": { "requestId": "req_9d0e1f2a3b4c", "cursor": null }
}

The list is cursor-paginated; iterate until meta.cursor is null. GET /v1/organizations/{ref} returns a single workspace (any of the three reference spellings).

Each entry carries roleyour strongest role in that workspace. Together with isAccountRoot it tells an account you own (isAccountRoot: true, role: "owner") apart from one that was merely shared with you, which is exactly the distinction the create-workspace gate makes.

const { organizations } = await client.workspaces.list();
const { organization } = await client.workspaces.get("ws_a1b2c3d4");

Rename a workspace

PATCH /v1/organizations/{ref} changes what a workspace presents: the display name, the vanity slug, and its logoUrl (below). Send any of them; a body with none is 422 validation_failed. It requires organization.settings.update (workspace owner/admin, or an account role that cascades); anyone else gets 404 not_found, since the workspace surface is deny-by-default and doesn't disclose existence.

curl -X PATCH https://api.orunbase.com/v1/organizations/ws_a1b2c3d4 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Industries", "slug": "acme-industries"}'
{
"data": {
"organization": {
"id": "org_1a2b3c4d5e6f70819203a4b5c6d7e8f9",
"name": "Acme Industries",
"slug": "acme-industries",
"workspaceRef": "ws_a1b2c3d4",
"accountId": "ws_a1b2c3d4",
"kind": "account",
"isAccountRoot": true,
"createdAt": "2026-07-02T12:00:00.000Z"
}
},
"meta": { "requestId": "req_1f2a3b4c5d6e", "cursor": null }
}
const { organization } = await client.workspaces.update("ws_a1b2c3d4", { name: "Acme Industries" });
// client.organizations.update(...) is the same call under the legacy name

What a rename does not touch: id and workspaceRef are immutable, so API keys, tokens, committed intent.yaml refs, and CI claims keep resolving. What it does move is anything addressed by slug — console URLs (/orgs/{slug}) and slug-spelled API paths — so a bookmarked slug URL stops resolving once someone else takes the old slug. Prefer ws_… wherever a reference is stored.

A slug already in use anywhere on the platform returns 409 conflict. Slug validation matches creation: 2–63 characters, lowercase letters, numbers and hyphens, starting and ending alphanumeric. The rename emits organization.updated, whose payload carries both the previous and new value of each changed label, and writes a membership-category audit entry.

Every workspace has an avatar in the console whether or not anyone uploads anything: with no logo it wears deterministic initials on a colour derived from its workspaceRef — so a workspace keeps the same mark across a rename, and two workspaces sharing a name never share a colour.

logoUrl overrides that mark. It is a base64 data: URL of a PNG, JPEG, or WebP, at most 128 KB encoded; anything else is 422 validation_failed. SVG is not accepted — it is a document rather than a bitmap, and it can carry script and external references. Upload one in Settings → General → Logo, which crops the picked image square, scales it to 128px and encodes it for you, so what you can pick is not bounded by what the API stores.

# Set it. (The console does this for you; the field is here for automation.)
curl -X PATCH https://api.orunbase.com/v1/organizations/ws_a1b2c3d4 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"logoUrl": "data:image/webp;base64,UklGRi..."}'

# Take it back off — the workspace returns to its initials mark.
curl -X PATCH https://api.orunbase.com/v1/organizations/ws_a1b2c3d4 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"logoUrl": null}'

Unlike name and slug, logoUrl is nullable: omitting the field leaves the logo alone, and an explicit null clears it. A workspace with no logo omits the field from its responses. The change emits organization.updated carrying logo: "set" | "cleared" — the transition, never the image bytes.

Accounts: the parent of your workspaces

An account is a parent organization — the AWS-account analog that owns a family of workspaces. Every workspace's accountId names its account's Workspace ID; for an account root, accountId equals its own workspaceRef (and kind is account).

What the account level gives you:

  • Child workspacesGET /v1/organizations/{accountId}/workspaces lists the workspaces under an account (requires organization.member.list on the account):

    { "data": { "workspaces": [{ "orgId": "org_…", "workspaceRef": "ws_e5f6a7b8", "name": "Acme Staging" }] }, "meta": { "requestId": "req_0e1f2a3b4c5d", "cursor": null } }
    const { workspaces } = await client.organizations.listWorkspaces(accountId);
  • Account roles that cascadeaccount_owner, account_admin, and account_billing_admin are granted once, on the account (POST /v1/organizations/{accountId}/account-roles with subjectId and role), and cascade to authority on every workspace under it — no per-workspace role rows. Granting requires member-management authority on the account itself; a workspace-only admin holds no role on the account and is denied.

  • Billing fan-out — the account root is the billing entity. Its plan's entitlements fan out to child workspaces on creation and re-fan-out when the parent's plan changes (an upgrade lifts every child; a downgrade can freeze children over the new limits — surfaced as workspace status).

Seeing which account a workspace belongs to

In the console, three surfaces answer it:

WhereWhat it shows
Workspace switcher (sidebar)An Account/Workspace badge per entry, and under a child workspace the name of the account it belongs to
Settings → AccountThe account's ws_… id as a pill, plus its workspaces, members, roles, and teams. The page header says whether this workspace is the account root or a workspace under one
Settings → Account → WorkspacesEvery workspace under the account — the authoritative list of what rolls up to it

Over the API, accountId is the same fact: it equals the workspace's own workspaceRef exactly when the workspace is an account root, and names the parent otherwise.

note

Accounts are a structural layer over the same organization resource — there is no separate account API object. A standalone workspace is simply its own account root.