Members & Invitations
Membership lifecycle of a team organization, including inviting users, accepting invitations, managing roles, and offboarding.
Members & Invitations
Overview
This guide covers the membership lifecycle of a team organization: viewing members, inviting users, accepting invitations, changing roles, and removing members (offboarding).
Capabilities required for each action are listed inline — see Roles & Permissions for which roles carry them.
Viewing Members
Requires member.view (all roles).
UI: Organization → Members.
API:
curl https://app.uptimehunt.io/api/v1/orgs/acme/members \
-H "Authorization: Bearer {access_token}"Returns each member's user info, role, and join date. The endpoint is paginated (limit/offset, default 50, maximum 200).
Each member's user object also carries its identity realm: "realm": {"type": "password"} for password accounts, {"type": "social", "provider": "github"} for accounts signed in with Google or GitHub, or {"type": "sso", "idp_label": "..."} for accounts provisioned through an organization identity provider. Because the same email address can belong to multiple separate accounts, the realm — shown as a badge next to each member in the UI — is how you tell them apart.
Inviting Members
Requires member.invite (Admin and Owner). The grant ceiling applies: Admins may invite as Member only; only Owners may invite as Admin or Owner.
UI: Organization → Members → Invite member — enter an email address and choose a role.
API:
curl -X POST https://app.uptimehunt.io/api/v1/orgs/acme/invitations \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"email": "alex@example.com", "role": "member"}'Invitation rules:
- Invitations are delivered by email and expire after 7 days.
- At most one pending invitation per email address per organization.
- Pending invitations can be listed (
GET /orgs/{slug}/invitations) and revoked (DELETE /orgs/{slug}/invitations/{id}) by anyone withmember.invite. - Invitation creation, revocation, and acceptance are recorded in the audit log (
member.invited,member.invite_revoked,member.joined).
Accepting an Invitation
The invitation email contains a link that handles both cases:
- New user — the link opens sign-up with the email pre-filled and carries the invitation token (
invite_tokenonPOST /auth/register). Everything happens in one step: the account is created, the email is verified automatically — no verification email is sent, because the invitation was delivered to that address — and the membership is created with the invited role. You can sign in immediately. The sign-up email must match the invitation email; a sign-up with a different address is rejected. Accounts created this way are organization-only accounts: they belong to the team organization and do not get a separate personal workspace (see the note below). - Existing user — sign in first, then the invitation is accepted for the signed-in account:
curl -X POST https://app.uptimehunt.io/api/v1/auth/invitations/accept \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"token": "{invitation_token}"}'Who the invitation binds to
On the existing-user path, the invitation email address is advisory routing, not an identity check — the membership is created for whichever authenticated account accepts the invitation (the same behavior as GitHub organization invites). Revoke a pending invitation if it was sent to the wrong person.
Inviting into an SSO organization
If your organization uses SSO, you can hand the invitee the organization sign-in link with the invitation token attached — https://app.uptimehunt.io/login?org={slug}&invite={token}. They sign in through your identity provider and the invitation is accepted by that authenticated account. With jit_requires_invitation enabled, a pending invitation is exactly what authorizes their first SSO sign-in (invitation-gated JIT).
On the new-user sign-up path, the email is checked strictly: it must equal the invitation email, because delivery of the invitation doubles as the proof of address that lets the new account skip email verification.
Invitation sign-ups are organization-only accounts
An account created through an invitation link gets no separate personal workspace — the same policy as the team-founder sign-up path and SSO-provisioned (JIT) members. This is deliberate: a member onboarded into a team setting has no personal side-channel for resources or API tokens that would escape the organization's roles, audit log, and token oversight.
The policy applies only to accounts created through an invitation. An existing account that accepts an invitation is unchanged — a personal user keeps their personal workspace, and tokens they hold for it remain outside this organization's token oversight. If every member identity must be governed end to end, connect SSO and enforce it.
Per-Org Contact Email
Each membership has an effective email — the address the organization uses for contact, notifications, and audit display. By default this is the member's account primary email, but members can override it per-organization to any verified address they own.
How Effective Email Is Resolved
| State | Effective email |
|---|---|
No override set (member_email: null) | Member's account primary email (user.email) |
| Override set | The member's chosen verified address for this org |
The effective email is visible to anyone with member.view via the member list
(GET /orgs/{slug}/members), or individually:
curl https://app.uptimehunt.io/api/v1/orgs/acme/members/42/email \
-H "Authorization: Bearer {access_token}"Response:
{
"data": {
"user_id": 42,
"member_email": "alice@work.com",
"effective_email": "alice@work.com"
}
}Self-Service Override (Member Only)
Contact email overrides are self-service only — a member sets their own org-facing email; org admins cannot set it on their behalf.
# Set an override (must be a verified address on the caller's account)
curl -X PUT https://app.uptimehunt.io/api/v1/orgs/acme/members/42/email \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"email": "alice@work.com"}'
# Clear the override (revert to account primary)
curl -X PUT https://app.uptimehunt.io/api/v1/orgs/acme/members/42/email \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"email": null}'The supplied address must be verified and must comply with the org's
allowed_member_email_domains policy (see below).
Domain Policy (allowed_member_email_domains)
An org Owner can restrict which email domains members may use as their org-facing contact email. The policy is a plain list of domains — no DNS verification required — and is separate from the DNS-verified routing domains used for SSO login routing.
| Policy state | Effect |
|---|---|
| Empty list (default) | Any verified address is allowed |
| Non-empty list | Only addresses whose domain appears in the list are accepted |
When a member tries to set an override that violates the policy, they receive a 422 member_email_policy_violation error naming the org. Setting or updating this policy does
not retroactively change existing members' contact email — only new set/promote operations
are checked.
The policy also applies at primary email promotion: when a member promotes a secondary address to their account primary, the new primary is checked against every org's policy they belong to. If any org rejects the domain, the promotion is blocked.
To configure the policy (Owner, via org PATCH):
curl -X PATCH https://app.uptimehunt.io/api/v1/orgs/acme \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"allowed_member_email_domains": ["acme.com", "acme.io"]}'Pass [] to remove all domain restrictions.
Plan requirement
Setting a non-empty domain policy requires the Pro plan or above — below Pro the request is
refused with 403 and {"code": "EMAIL_DOMAIN_POLICY_NOT_IN_PLAN"}. Clearing the policy
([] or null) is always allowed, on any plan.
Invitation Signup and the Contact Email
When a new account is created through an invitation link, the invited email becomes the
account primary. The per-org contact email override (member_email) is not explicitly
assigned — it stays null, so effective_email resolves to the account primary via the
null-fallback path. A call to GET /orgs/{slug}/members/{user_id}/email after invitation
signup will return {"member_email": null, "effective_email": "invited@example.com"} — not
a stored override. The effective address is correct; only the mechanism differs. The member
can set an explicit override at any time using the self-service endpoint above.
What Happens When an Override Address Is Removed
If a member removes an email address from their account that was set as their per-org contact
email, that membership's override is cleared automatically (SET_NULL), and the membership
falls back to the account primary. A member.org_email_set audit event is emitted recording
the automatic clear.
See Email Management for the full email-address lifecycle (add, verify, set primary, remove).
Changing a Member's Role
Requires member.role_edit (Owner only). The grant ceiling and the last-owner invariant apply.
API:
curl -X PATCH https://app.uptimehunt.io/api/v1/orgs/acme/members/42 \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"role": "admin"}'Role changes are audited as member.role_changed with the old and new role in the event details. A role downgrade also invalidates the member's active web sessions (their JWT refresh tokens are blacklisted), so reduced permissions take effect immediately rather than at next login.
Blocking a Member (Reversible Suspension)
Requires member.remove (Owner only). A block is a reversible access suspension — the member keeps their role and all org-bound tokens, but has no access to the organization until explicitly unblocked.
Use blocking when:
- Temporarily suspending access (pending investigation, credential rotation, or account activity review).
- Deferring permanent removal to keep audit trails and historical data intact.
- Enforcing the organization's member-seat limit (blocked members don't count against the cap).
Blocking a member
curl -X POST https://app.uptimehunt.io/api/v1/orgs/acme/members/42/block \
-H "Authorization: Bearer {access_token}"A blocked member:
- Cannot access the organization — all permission checks fail, API calls return
403, and the UI treats the membership as if it doesn't exist. - Keeps their role and tokens — the membership row, role assignment, and organization API tokens are retained (not deleted).
- Is excluded from seat-cap counting — unblocking a member who would push the organization over its plan's member limit will be refused (see below).
- Cannot rejoin via SSO — if the organization uses SSO, a blocked member cannot sign in through the identity provider until unblocked. The block overrides JIT auto-provisioning.
- Is logged in the audit trail as
member.blocked.
Last Owner
Blocking the organization's only Owner is rejected with 409 Conflict, matching the rule for removal. An Owner can always unblock themselves if they were blocked by accident.
Unblocking a member
curl -X DELETE https://app.uptimehunt.io/api/v1/orgs/acme/members/42/block \
-H "Authorization: Bearer {access_token}"Unblocking restores the member's full access immediately. However, if unblocking the member would exceed your organization's plan's member-seat limit, the unblock is refused with 403 MEMBER_CAP_REACHED — upgrade your plan or block another member first. This guard prevents unblocking from becoming a loophole around your seat cap.
Successful unblocks are logged as member.unblocked in the audit trail.
Removing a Member (Offboarding)
Requires member.remove (Owner only). Removal permanently deletes the membership and is irreversible.
curl -X DELETE https://app.uptimehunt.io/api/v1/orgs/acme/members/42 \
-H "Authorization: Bearer {access_token}"Removal is designed to sever the member's access to the organization completely, in one transaction:
- The membership is deleted — every permission check for this organization fails from this moment on.
- The member's organization-bound API tokens are automatically revoked (each revocation is audited with reason
member_removed). Tokens the user holds for other organizations or their personal workspace are untouched. - The member's active web sessions are invalidated (JWT refresh tokens blacklisted).
- OAuth application tokens (e.g. MCP connectors) are user-identity tokens and are not deleted — but because organization access flows entirely through membership, they lose all access to this organization's data within at most 60 seconds (token-introspection cache window). See Token Oversight for the full rationale.
The removal and each token revocation appear in the audit log, giving you a complete offboarding evidence trail.
Last Owner
Removing (or self-removal of) the organization's only Owner is rejected with 409 Conflict. Promote another Owner first — see the last-owner invariant.
Offboarding checklist
- Option A: Temporary suspension — block the member (see Blocking a Member above) if you want to suspend access without permanent deletion.
- Option B: Permanent removal — remove the member (above) — tokens and sessions are handled automatically.
- Check the token oversight inventory to confirm no active organization tokens remain for the user.
- If the user was provisioned through your SSO, also deactivate them in your identity provider so they cannot sign in again.
- Review the audit log for the
member.removed(ormember.blocked) andtoken.revokedevents as evidence.
Related Documentation
- Roles & Permissions
- Token Oversight
- Audit Log
- Single Sign-On — JIT-provisioned members and invitation-gated JIT
- Email Management — member's self-service email lifecycle
- Email Routing & Auto-Join — DNS-verified routing domains (distinct from the domain policy on this page)