Skip to Content
API ReferenceOrganizations

Organizations API

Organizations are company-level entities that own and group workspaces, and they are where all access control lives: people, groups, roles and the grants that connect them.

Every workspace belongs to exactly one organization. A group is granted one or more roles on each workspace it should reach, and that grant is the only thing that grants access — there is no workspace membership record and no workspace invitation.

Endpoints

MethodPathDescription
GET/api/v1/organizationsList organizations
POST/api/v1/organizationsCreate an organization
GET/api/v1/organizations/{orgId}Get an organization
PUT/api/v1/organizations/{orgId}Update an organization
DELETE/api/v1/organizations/{orgId}Delete an organization
GET/api/v1/organizations/{orgId}/workspacesList org workspaces
POST/api/v1/organizations/{orgId}/workspacesCreate workspace in org
GET/api/v1/organizations/{orgId}/groupsList groups
POST/api/v1/organizations/{orgId}/groupsCreate a group
GET/api/v1/organizations/{orgId}/groups/{groupId}Get a group
PUT/api/v1/organizations/{orgId}/groups/{groupId}Update a group
DELETE/api/v1/organizations/{orgId}/groups/{groupId}Delete a group
GET/api/v1/organizations/{orgId}/groups/{groupId}/membersList a group’s members
POST/api/v1/organizations/{orgId}/groups/{groupId}/membersAdd an account to a group
DELETE/api/v1/organizations/{orgId}/groups/{groupId}/members/{accountId}Remove one
PUT/api/v1/organizations/{orgId}/groups/{groupId}/workspaces/{workspaceId}The grant — set the roles this group holds on this workspace
DELETE/api/v1/organizations/{orgId}/groups/{groupId}/workspaces/{workspaceId}Revoke it
GET/api/v1/organizations/{orgId}/rolesList built-in + custom roles
POST/api/v1/organizations/{orgId}/rolesCreate a custom role
PUT/api/v1/organizations/{orgId}/roles/{roleId}Update one
DELETE/api/v1/organizations/{orgId}/roles/{roleId}Delete one
GET/api/v1/organizations/{orgId}/membersList org members
POST/api/v1/organizations/{orgId}/members/inviteInvite a new member by email
DELETE/api/v1/organizations/{orgId}/members/{accountId}Remove a member
GET/api/v1/organizations/{orgId}/invitesList pending invitations
DELETE/api/v1/organizations/{orgId}/invites/{inviteId}Cancel an invitation
POST/api/v1/organizations/{orgId}/invites/{inviteId}/acceptAccept an invitation
POST/api/v1/organizations/{orgId}/invites/{inviteId}/resendResend an invitation email

List Organizations

GET /api/v1/organizations

Returns all organizations the authenticated user is a member of — or, for a platform administrator, every organization on the platform. Does not require the X-Workspace-ID header.

Each entry carries caller_is_admin: whether you are in that organization’s Organization admins group, and so may create workspaces in it. It is true on every entry for a platform administrator. Only this list fills it in; the other organization reads return it as false.

Response

[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Acme Corp", "slug": "acme-corp", "avatar_url": "", "settings": {}, "created_by": "660e8400-e29b-41d4-a716-446655440000", "workspace_count": 3, "member_count": 15, "caller_is_admin": true, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-15T09:30:00Z" } ]

Example

curl -X GET https://agentic.zeotap.com/api/v1/organizations \ -H "Authorization: Bearer <token>"

Create Organization

POST /api/v1/organizations

Creates a new organization. The organization is seeded with its Organization admins and Organization viewers groups, and the authenticated user is put in Organization admins. Any approved account may create one; an account whose access has not been approved yet is refused 403 access_required.

Request Body

FieldTypeRequiredDescription
namestringYesOrganization display name. The organization’s slug is derived from it
{ "name": "Acme Corp" }

Response

Returns the created organization with status 201 Created.

Errors

StatusCodeMeaning
400organization_slug_requiredThe name contains no letter or digit to derive an identifier from
403access_requiredYour account’s access request has not been approved
409organization_slug_takenAnother organization already has the identifier this name produces. Choose a different name

Example

curl -X POST https://agentic.zeotap.com/api/v1/organizations \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corp" }'

Get Organization

GET /api/v1/organizations/{orgId}

Returns a single organization by ID.


Update Organization

PUT /api/v1/organizations/{orgId}

Updates organization details. Requires membership of the organization’s Organization admins group.

Request Body

FieldTypeRequiredDescription
namestringNoUpdated display name. Renaming also re-derives the slug, and is refused 409 organization_slug_taken if another organization already holds the new one
avatar_urlstringNoUpdated avatar URL
settingsobjectNoUpdated org settings

Delete Organization

DELETE /api/v1/organizations/{orgId}

Deletes an organization. Requires membership of the Organization admins group.

Refused 409 organization_has_workspaces while the organization still holds any workspace, and 409 organization_has_hierarchies while any of its workspaces is in a hierarchy. Move the workspaces to another organization first — there is no cascade, because deleting an organization is not a request to destroy what is inside it.

Response

{ "status": "deleted" }

Workspace Management

List Org Workspaces

GET /api/v1/organizations/{orgId}/workspaces

Returns all workspaces belonging to the organization.

Create Workspace in Org

POST /api/v1/organizations/{orgId}/workspaces

Creates a new workspace under the organization. This is the only way to create a workspace, and it requires organization administration — membership of the Organization admins group, or platform administration. Returns the workspace with status 201 Created.

Request Body

FieldTypeRequiredDescription
namestringYesWorkspace display name
slugstringNoURL-safe identifier, unique across the platform. Stored as sent; refused 409 workspace_slug_taken if another workspace already uses it. Omit it to have one derived from the name and made unique
{ "name": "Production", "slug": "production" }

Errors

StatusCodeMeaning
403access_requiredYour account’s access request has not been approved
403—You are not an administrator of this organization
409workspace_slug_takenThe slug you sent is already used by another workspace

Retired: POST /api/v1/workspaces

POST /api/v1/workspaces no longer creates workspaces. It answers 410 Gone with the code workspace_creation_moved, naming the organization route above. An account that belongs to no organization creates one first with POST /api/v1/organizations.


Groups and Grants

A group belongs to an organization, holds accounts, and is granted one or more roles on each workspace it should reach.

Every organization is seeded with two groups carrying a system_key — org_admins and org_viewers — which cannot be renamed (409 system_group_immutable) or deleted. Each holds one wildcard grant: a row with no workspace_id, meaning every workspace in the organization, now and in future. Membership of org_admins is what organization administration is; there is no role on the roster.

List Groups

GET /api/v1/organizations/{orgId}/groups

{ "groups": [ { "id": "7c1f4e2a-9b3d-4f18-a0c6-2d5e8b1f9034", "organization_id": "550e8400-e29b-41d4-a716-446655440000", "name": "Organization admins", "description": "Administers this organization and every workspace in it", "system_key": "org_admins", "is_system": true, "member_count": 2, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ] }

Create, Update, Delete a Group

POST /api/v1/organizations/{orgId}/groups takes {"name", "description"?}. Names are unique within the organization, compared without regard to case.

PUT …/groups/{groupId} takes the same fields. DELETE …/groups/{groupId} removes the group, its members, its grants and its access-policy assignments. Both refuse 409 system_group_immutable on a seeded group.

All three require organization administration.

Group Membership

POST /api/v1/organizations/{orgId}/groups/{groupId}/members takes {"account_id"}; the account must already be in the organization. DELETE …/members/{accountId} removes them.

Removing the last member of org_admins is refused 409 last_org_admin. The rule is a transition, not a state — with two administrators either removal is allowed — so an empty admins group is a legitimate state (a platform administrator can create an organization for a customer who has not joined yet).

Set a Grant

PUT /api/v1/organizations/{orgId}/groups/{groupId}/workspaces/{workspaceId}

States the complete set of roles the group holds on this workspace. A role the group holds and the body omits is dropped, so this both grants and revokes.

FieldTypeRequiredDescription
role_idsarray of string (UUID)YesEvery role this group should hold on this workspace. An empty array attaches the group and grants no permissions — its members count as members of the workspace, which keeps its access policies meaningful, and can do nothing
role_idstring (UUID) or nullNoThe single-role spelling this endpoint shipped with, still accepted. role_ids wins when both are present

A group may hold several roles on one workspace, and what it grants there is the UNION of their permissions. Nothing subtracts, so adding a role can only widen what the group’s members can do — worth knowing, because “Viewer and Editor” reads as though it might mean the narrower of the two. This is how “Viewer everywhere, plus Editor on analytics” is expressed without inventing a role per combination.

The response is {"assignments": [...]}, one entry per role.

The workspace must be in the same organization as the group; the database enforces it.

Revoke a Grant

DELETE /api/v1/organizations/{orgId}/groups/{groupId}/workspaces/{workspaceId}

Removes every role the group holds on that workspace, and its access-policy assignments there. Distinct from PUT with an empty role_ids, which leaves the group attached granting nothing.

Read the Grants Reaching One Workspace

GET /api/v1/workspaces/{id}/groups — the workspace-side read, backing the workspace Groups tab. Each assignment carries group_id, group_name, group_system_key, workspace_id (absent on a wildcard) and role_id. There is one entry per role, so a group holding two roles on the workspace appears twice; group them by group_id to show a row per group.

Roles

Custom roles belong to the organization, so one role is written once and can be granted in every workspace in it. The three built-in roles belong to no organization, which is what makes the same three ids usable everywhere:

RoleID
Owner00000000-0000-0000-0000-000000000001
Admin00000000-0000-0000-0000-000000000002
Member00000000-0000-0000-0000-000000000003

GET /api/v1/organizations/{orgId}/roles lists both sets. POST, PUT and DELETE manage the custom ones and require roles.write.

A role still held by any grant cannot be deleted: the delete first moves every such grant to the built-in Member role, and refuses if that cannot be done. Without it, the ON DELETE CASCADE on the grant’s role_id would delete the grants instead, revoking a group’s access with no error anywhere.

Member Management

The organization roster answers one question — does this account belong to this organization — and carries no role. What somebody can do comes from the groups they are in.

List Members

GET /api/v1/organizations/{orgId}/members

[ { "organization_id": "550e8400-e29b-41d4-a716-446655440000", "account_id": "660e8400-e29b-41d4-a716-446655440000", "email": "alice@example.com", "name": "Alice Smith", "avatar_url": "https://...", "groups": [ { "group_id": "7c1f…", "group_name": "Organization admins", "system_key": "org_admins" } ], "created_at": "2024-01-01T00:00:00Z" } ]

Invite Member

POST /api/v1/organizations/{orgId}/members/invite

Sends an email invitation to add a new member to the organization. The new account is created on acceptance.

Request Body

FieldTypeRequiredDescription
emailstringYesEmail address of the user to invite
group_idsarray of string (UUID)NoThe groups the invitee lands in on acceptance. An empty list is legal and means they join the roster and reach nothing until a group takes them in

Remove Member

DELETE /api/v1/organizations/{orgId}/members/{accountId}

Removes a member from the organization, and with them their membership of every group in it — and therefore their access to every workspace those groups reached. Nothing survives it, because there is no workspace membership record to survive.


Invitations

Invitations are managed via the members/invite endpoint (see Invite Member above) and the invites collection below.

List Invitations

GET /api/v1/organizations/{orgId}/invites

Returns all pending invitations.

Response

[ { "id": "770e8400-e29b-41d4-a716-446655440000", "organization_id": "550e8400-e29b-41d4-a716-446655440000", "email": "newuser@example.com", "groups": [ { "group_id": "7c1f4e2a-9b3d-4f18-a0c6-2d5e8b1f9034", "group_name": "EMEA Marketing" } ], "invited_by": "660e8400-e29b-41d4-a716-446655440000", "inviter_name": "Alice Smith", "status": "pending", "expires_at": "2024-02-15T00:00:00Z", "created_at": "2024-01-15T09:30:00Z", "updated_at": "2024-01-15T09:30:00Z" } ]

Cancel Invitation

DELETE /api/v1/organizations/{orgId}/invites/{inviteId}

Cancels a pending invitation.

Accept Invitation

POST /api/v1/organizations/{orgId}/invites/{inviteId}/accept

Accepts an invitation. The authenticated user is added to the organization and to the groups the invitation named.

Idempotent: the post-signup verification flow can land two tabs on the accept page at once, and the loser of that race sees success rather than a refusal, for an invitation that did go through.

Resend Invitation

POST /api/v1/organizations/{orgId}/invites/{inviteId}/resend

Resends the invitation email for a pending invite.


Organization Object

FieldTypeDescription
idstring (UUID)Unique identifier
namestringDisplay name
slugstringURL-safe identifier
avatar_urlstringAvatar image URL
settingsobjectOrganization settings
created_bystring (UUID)Account that created the org
workspace_countintegerNumber of workspaces
member_countintegerNumber of members
caller_is_adminbooleanWhether you administer this organization. Filled in only by List Organizations; false elsewhere
created_atstring (ISO 8601)Creation timestamp
updated_atstring (ISO 8601)Last update timestamp

Organization Member Object

FieldTypeDescription
organization_idstring (UUID)Organization ID
account_idstring (UUID)Account ID
groupsarrayThe groups this account is in — {group_id, group_name, system_key} each. There is no role: membership of the org_admins group is what organization administration is
emailstringMember email
namestringMember display name
avatar_urlstringMember avatar URL
created_atstring (ISO 8601)When the member was added

Organization Invite Object

FieldTypeDescription
idstring (UUID)Unique identifier
organization_idstring (UUID)Organization ID
emailstringInvited email address
groupsarrayThe groups the invitee lands in on acceptance — {group_id, group_name} each. May be empty
invited_bystring (UUID)Account that sent the invite
inviter_namestringName of the inviter
statusstringpending, accepted, or cancelled
expires_atstring (ISO 8601)Seven days after creation. Expiry is a timestamp, not a status — a lapsed invite stays pending and is refused on accept
created_atstring (ISO 8601)Creation timestamp
updated_atstring (ISO 8601)Last update timestamp
Last updated on