Members and invitations
Invite people into an organization, accept or decline, create accounts directly, and remove or leave members.
An organization's members are the users with an accepted membership in it. People join by invitation, or an administrator can create an account for them directly. Joining grants almost nothing: what a member may do is decided by the roles they are given afterwards.
All of these routes act on the caller's active organization, taken from the session. None of them takes an organization id or uid, so a caller cannot act on an organization they are not in.
Who can do what, out of the box
| Org owner (signed up and created it) | Administrator (admin role) | Member (joined by invitation) | |
|---|---|---|---|
| See their own organizations, switch, leave (not their home org) | Yes | Yes | Yes |
| See, accept and decline invitations addressed to them | Yes | Yes | Yes |
| Their own sign-in methods, 2FA and sessions | Yes | Yes | Yes |
| List members, invite, create accounts, revoke invitations, remove members, rename, set the 2FA requirement | Yes | Yes | No |
| Assign roles to members, manage the organization's roles and policies | Yes | No | No |
The owner holds the user and superuser roles in their own organization. Someone who accepts an
invitation gets no roles, only the per-person Org Member Baseline in the table's first three
rows. You grant more by giving them a role; see Assigning roles.
Nothing above reaches your app's structure (tables, handlers, endpoints, agents, jobs): that stays
yours unless you grant it on purpose.
Inviting someone
An administrator invites by email
POST /{appUid}/autocrud/org_invitations/actions/org-invites with the person's email. The
invitation lasts expiresInDays (1 to 90, default 7). Inviting the same address again re-sends the
pending invitation rather than creating a second one.
They get an email on your app's behalf
It names your app, the organization and who invited them, and tells them to accept inside the app. It carries no
link: acceptance happens through your own UI. The response's emailSent says whether it went out.
They sign in and accept
The invitee signs up (if they have no account yet) or signs in, lists the invitations addressed to them, and accepts one by its uid. An invitation sent to an address matches the account with that email.
# An administrator, signed in to the organization
curl -X POST "$AUTHORIC_URL/$APP_UID/autocrud/org_invitations/actions/org-invites" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "inviteeEmail": "sam@example.com", "expiresInDays": 14 }'
# The invitee, signed in to their own account
curl "$AUTHORIC_URL/$APP_UID/autocrud/org_invitations/actions/org-invites/received?status=pending" \
-H "Authorization: Bearer $SAM_TOKEN"
curl -X POST "$AUTHORIC_URL/$APP_UID/autocrud/org_invitations/actions/org-invites/accept" \
-H "Authorization: Bearer $SAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "invitationUid": "<invitation uid>" }'The invite answers with the invitation's uid and status:
{
"invitationUid": "0199a6b4-7d21-7e05-8f3a-4b9c1d2e6f70",
"status": "pending",
"emailSent": true,
"message": "Invitation sent to sam@example.com — they accept it in the app",
"invitee": { "subjectType": "user", "email": "sam@example.com" }
}An accepted invitation answers with orgUid, the organization joined. The session stays in its
current organization: refresh with activeOrgUid to
switch into the new one.
The rest of an invitation's life
| Who | Route (under /{appUid}/autocrud/org_invitations/actions/) | Does |
|---|---|---|
| Invitee | POST org-invites/deny { invitationUid } | Declines it |
| Administrator | POST org-invites/cancel { invitationUid } | Cancels a pending invitation addressed to the caller (not in the member baseline by default) |
| Administrator | GET org-invites/sent?status= | Lists the organization's invitations |
| Administrator | POST org-invites/revoke { invitationUid } | Revokes one the organization sent |
Creating an account directly
For back-office tools and imports, an administrator can create the account and its membership in
one call, without an email:
POST /{appUid}/autocrud/org_invitations/actions/org-invites/internal-create with inviteeEmail
and optionally fname, lname and password. If an account with that address already exists it
is added to the organization instead. A newly created account gets its own home organization, like
any other, and joins yours at once. The response names the account by invitee.uid and says
whether it was created.
Listing, removing and leaving
| Route | Does |
|---|---|
GET /{appUid}/autocrud/org_memberships/actions/org-memberships | Lists the active organization's members, each with membershipUid, status, org and subject (uid, email, name) (administrators) |
POST /{appUid}/autocrud/org_memberships/actions/org-memberships/remove { membershipUid } | Removes a member (administrators) |
POST /{appUid}/autocrud/org_memberships/actions/org-memberships/leave | The caller leaves the active organization |
Removing a member ends their authority there in the same step: their roles in that organization are deleted and their pending invitations to it are revoked. Re-admitting them later starts from nothing, never from whatever they held before.
Three removals are refused, so an organization can never be left without someone in charge:
- nobody can leave, or be removed from, their home organization;
- the organization's owner cannot be removed;
- the last member holding an administrator or owner role cannot be removed.
Errors to branch on
These come from the organization handlers and are returned in error.code.
| Code | Status | Meaning |
|---|---|---|
CANNOT_INVITE_SELF | 400 | The caller invited their own address. |
EMAIL_NOT_VERIFIED | 403 | The caller has not confirmed their own email address yet, so they cannot invite anyone. See Confirming the email address. |
INVITEE_ALREADY_MEMBER | 409 | That person is already a member. |
INVITEE_EMAIL_ALREADY_EXISTS | 409 | Creating an account directly collided with an existing address. Invite it instead. |
INVITATION_NOT_FOUND | 404 | No invitation with that uid. |
INVITATION_TARGET_MISMATCH | 403 | The invitation is addressed to someone else. |
INVITATION_NOT_PENDING | 409 | Already accepted, declined or revoked. |
INVITATION_EXPIRED | 410 | Past its expiry. Ask for a new one. |
MEMBERSHIP_NOT_FOUND | 404 | No such member in the active organization. |
OWN_ORG_LEAVE_FORBIDDEN, OWN_ORG_SELF_REMOVAL_FORBIDDEN | 403 | That is the caller's home organization. |
ORG_OWNER_REMOVAL_FORBIDDEN | 403 | The target owns the organization. |
LAST_PRIVILEGED_MEMBER_FORBIDDEN | 409 | The target is the last administrator or owner. |
A caller without the grant for an action gets a 403 whose message names the action that was
refused (for example org_invitations:orgInvitesCreate).