Skip to content

Let members run their user organization

A user organization is a group of your users that holds access for its members, such as an employer that buys seats for its staff. Staff open one and seat its first admin in the console; see User organizations. From then on, its own owners and admins run it from your site, through /v1/org/*. What a seat gives access to is a grant the organization holds; see Subscriptions for selling one.

Every /v1/org/* route acts as the signed-in user and needs their session. The routes answer at edgekit_only and dual sites, and 404 not_found at an external_only site. The browser client’s org methods wrap them; see createClient and the Organizations operations.

Role Can
owner Everything an admin can. The last owner cannot leave.
admin Invite and remove members, change roles between admin and member, cancel invitations, list seat refusals, rename the organization.
member Read the organization and its members, and leave.

owner cannot be given through /v1/org/update-member-role, which takes admin or member only. A role changes what a person can manage, never what they can read: the organization’s grants reach every seat.

  1. An owner or admin sends the invitation, with the role it offers:

    Terminal window
    curl -X POST https://your-tenant-host/v1/org/invite-member \
    -H 'x-edgekit-site-id: site_example' \
    -H 'Authorization: Bearer <session-token>' \
    -H 'content-type: application/json' \
    -d '{"email": "grace@example.com", "role": "member"}'

    EdgeKit emails the invitee: You've been invited to join <organization>, telling them to sign in to accept. The email carries no link. It goes out under your transactional mail settings, like sign-in emails.

  2. The invitee signs in on your site with that address. Your page lists their invitations with GET /v1/org/list-user-invitations.

  3. The invitee accepts with POST /v1/org/accept-invitation and the invitation’s invitationId, or declines with POST /v1/org/reject-invitation.

const site = { 'x-edgekit-site-id': 'site_example' };
const { invitations } = await fetch('/edgekit/v1/org/list-user-invitations', {
credentials: 'include',
headers: site,
}).then((response) => response.json());
const accepted = await fetch('/edgekit/v1/org/accept-invitation', {
method: 'POST',
credentials: 'include',
headers: { ...site, 'content-type': 'application/json' },
body: JSON.stringify({ invitationId: invitations[0].id }),
});

Accepting takes the seat and makes the organization the user’s active one. It is refused unless the user signed in with the address the invitation went to, and that address is verified, so an invitation cannot be claimed by someone else.

  • An invitation expires after 48 hours. Send a new one after that.
  • A user organization holds at most 50 pending invitations at once.
  • Inviting an address that already has a pending invitation replaces it with a new one, with a new id. Set resend to choose whether the email goes out again.
  • An outstanding invitation holds no seat. The seat ceiling is checked when it is accepted.

Accepting answers 403 seat_limit_reached, This organization has no free seats., when the organization has no free seat. EdgeKit records the person as a seat refusal, as it does for someone refused a seat through an auto-join domain. Owners and admins list them, most recent first, until each person holds a seat:

Terminal window
curl 'https://your-tenant-host/v1/org/list-seat-refusals?organizationId=01926f3a-8b4e-7b33-a944-5e6f7a8b9c0d' \
-H 'x-edgekit-site-id: site_example' \
-H 'Authorization: Bearer <session-token>'

Anyone else gets 404 not_found. For how the ceiling is set, see Seats and the seat limit.

  • List members: GET /v1/org/list-members, paged with limit and offset. Each member carries their role, when they joined, and the user’s id, email and name.
  • Change a role: POST /v1/org/update-member-role with the memberId and role.
  • Remove a member: POST /v1/org/remove-member with memberIdOrEmail. The seat is free at once. The removed user keeps their own record and their own grants. Decide can keep reading the organization’s access from their access cookie for up to an hour after it was last refreshed; see How quickly an ended session stops working.
  • Leave: POST /v1/org/leave with the organizationId, by the member themselves.
  • Rename: POST /v1/org/update with name, up to 200 characters, and optionally logo; logo: null clears it. The slug cannot change.

Remove, leave, accept and reject cannot be retried after they succeed: the member or the invitation is gone.

A user can belong to several user organizations. POST /v1/org/set-active with an organizationId makes one active for the session, and the routes above that take an optional organizationId act on it when none is sent. null clears it, and the user acts for themselves again. GET /v1/org/get-active-member answers the user’s own membership in it, which is what a page reads to decide whether to show team management at all.

Your site’s users cannot create or delete a user organization: it exists because seats were bought.