Prerequisites
- An API key with
adminorganization access — see Create an API key - A website, for the website-scoped flow — see the Quickstart
Choose the flow
The
owner role is not invitable; ownership moves only through a member-role update performed by the current owner. See Access control for the capability each role carries.
1
Invite a teammate to the organization
POST /v1/organizations/{organizationId}/invitations emails an invitation. role defaults to full_editor when omitted; invitations expire after 48 hours unless you pass expiresAt.Invite an admin
Response
409; pass "resend": true to cancel it and issue a fresh one. The response always includes the acceptance url. To deliver the link yourself — for example from an agent — pass "sendEmail": false, which creates the invitation without emailing the recipient.The recipient accepts from the email and becomes a member with the invited role. Adjust roles later with PATCH /v1/organizations/{organizationId}/members/{memberPrincipalId} (body { "role": "..." }); assigning owner requires the current owner.2
Invite a client to one website
This is the agency pattern: the client gets access to their website and nothing else. The response depends on whether the email already has a Cactal account:
POST /v1/websites/{websiteId}/editors takes an email and a website role — full or content.Invite a client as a content editor
Response when the email is new
"status": "pending"— an invitation email was sent; it expires after 48 hours by default."status": "granted"— the account exists, so the editor grant was applied immediately; the response includes thegrant."status": "already_inherited"— that person is already an organization member and inherits access from their organization role.
content editor manages CMS items and assets on that website. A full editor also edits source code and publishes. Neither can see other websites, organization settings, members, or API keys.After accepting, the client lands directly on the shared website. It appears in their dashboard’s unified Sites list alongside any sites they already have, under a “Shared with you” sidebar group; clients without an organization of their own get the same Sites page with a “Recently opened” group. In the API, GET /v1/websites?scope=shared returns exactly this shared set.3
Grant or change access directly
When you already know a person’s principal id — for example from the editors list — set their role without an email round-trip:The call is an upsert — repeat it with a different
Upsert a direct editor grant
Response
role to change access. Granting to an existing organization member returns 400, because members already inherit website access from their organization role.4
Remove access and cancel invitations
Remove a website editor:Access ends immediately; a missing grant returns
Remove an editor
404. The same endpoint cancels a pending website invitation: pass { "invitationId": "..." } instead of principalId. Get that id from GET /v1/websites/{websiteId}/editor-invitations?status=pending. Organization invitations cancel with POST /v1/organizations/invitations/{invitationId}/cancel; list their ids with GET /v1/organizations/{organizationId}/invitations?status=pending. Remove an organization member with DELETE /v1/organizations/{organizationId}/members/{memberPrincipalId}.5
Verify who has access
List website editors
List pending website invitations
List pending organization invitations
The editors endpoint and each invitation endpoint return
{ "items": [...], "nextCursor": "..." | null }. An editor entry looks like { "principalId": "...", "role": "full" | "content", "name": "...", "email": "...", "createdAt": "..." }. Pass nextCursor back as cursor until it is null; use status=all to retrieve accepted and canceled history as well as pending invitations. Organization members remain separate at GET /v1/organizations/{organizationId}/members.Troubleshooting
Next steps
- Manage API keys — the same scoping model, applied to keys and agents
- Access control concepts for the full role and capability matrix