Skip to content

Invitations

  • You are an admin on the project. Minting and revoking also need a fresh second factor, and the CSRF header on a cookie session, so they are browser-session work rather than API-key work.
  • The project has an active email notification channel if you want z4j to auto-send the invite. Without one, the dashboard surfaces the link and you share it manually.

Dashboard, the project's Members page (/projects/<slug>/settings/members), Invite.

Fill in:

  • Email -- target user's email.
  • Role -- viewer, auditor, operator, or admin. There is no owner role; admin is the highest tier and the brain enforces a last-admin protection so you cannot demote or remove the only admin on a project.
  • The dialog has no TTL field; links minted from the dashboard are valid for 7 days. Set ttl_days on the API call to change it.

Submit. With an active email channel the invitee receives an email containing the single-use accept URL. Without one, the response carries the URL for out-of-band delivery.

  • Single-use: accepting invalidates the token immediately. Re-using returns 404 with error: "not_found" and the message invalid_or_expired. An unknown, revoked, or expired token returns exactly the same thing, deliberately, so the response never confirms that a token was real.
  • Default TTL: 7 days (Z4J_* does not control this; the field is on the invitation record itself).
  • Bound to the target email: accepting creates the account for the invited address, so the link itself is the credential and no mailbox check happens. If a user with that email already exists, accept is refused with 409; add that user as a member instead.
  • Stored hashed in Postgres; the plaintext token is shown to the admin once and embedded in the URL.

The accept URL points the invitee at the dashboard's accept page, backed by:

POST /api/v1/invitations/preview
Body: {"token": "<single-use-token>"}
POST /api/v1/invitations/accept

The preview returns the invited email, role, and project name. The accept POST takes {token, display_name, password} (password is 12..200 chars, hashed with argon2id); it materialises the user and the membership in one step.

Before acceptance: the project's Members page, "Pending invitations" table, Revoke, then confirm the "Revoke invitation" dialog (or DELETE /api/v1/projects/{slug}/invitations/{invitation_id}).

After acceptance: treat it as a membership change. Demote the role to a lower tier (PATCH membership) or remove the member entirely (DELETE membership). The new authorization takes effect on later project requests, but the user's global browser sessions remain valid for other projects; membership changes do not revoke those sessions.

The accept and preview endpoints share a single per-IP bucket: 30 hits per minute combined. The mint endpoint has no separate per-email cap; the per-IP login / general API caps apply to the admin issuing invites.

See rate limits.

Every invite, accept, and revoke writes an audit log entry (invitation.mint, invitation.accept, invitation.revoke). Email delivery adds invitation.email_delivery_requested and invitation.email_delivery_result.