Invitations API — Creation
Create an invitation: prerequisites, POST /api/invitations endpoint, body, response and error handling.
Invitations API — Creation
The Invitations API lets your Wakapp invite users to join a Customer. WakaStart handles email sending, Keycloak account creation, and rights provisioning.
Prerequisites
invitationfeature enabled in cascade (App ∧ Network ∧ Customer)invitations.createright on the token or API key — this right is anAppRightgranted via the profile(s) assigned to the inviter, checked byRequireRight("invitations.create")adminLevel >= CUSTOMER_ADMINfor the caller — human (BFF) or technical (API key). For an API key, the check usesApiKey.adminLevel(resolved aseffectiveAdminLevel); a key without an explicitadminLevel(defaultNONE) is rejected like any insufficiently privileged caller- Optional:
App.acceptInvitationUrlconfigured to customize the URL in the email
The
userLevelconcept (and theuserLevel >= INVITErule) has been removed from invitation access control. Creation is now gated by theinvitations.createapplication right and by the caller'sadminLevel >= CUSTOMER_ADMIN. The request body no longer accepts auserLevelfield: sending one now returns a400(strict DTO,whitelist/forbidNonWhitelisted).
Invitation lifecycle
Loading diagram…
| Status | Description |
|---|---|
PENDING | Created, awaiting acceptance |
ACCEPTED | The invitee has finalized their account |
EXPIRED | Deadline exceeded (10 days by default) |
CANCELLED | Revoked by the inviter |
Full invitation flow
Loading diagram…
Endpoint — Create an invitation
httpPOST /api/invitations Authorization: Bearer <wakaToken> Content-Type: application/json { "customerId": "550e8400-e29b-41d4-a716-446655440000", "invitedEmail": "alice@acme.com", "invitedFirstName": "Alice", "invitedLastName": "Martin", "profileId": "22222222-2222-2222-2222-222222222222", "teamAssignments": [ { "teamId": "33333333-3333-3333-3333-333333333333", "teamLevel": "MEMBER" } ], "message": "Welcome to our application!", "language": "fr" }
Body fields:
| Field | Type | Required | Description |
|---|---|---|---|
customerId | UUID | Yes | Target Customer |
invitedEmail | string | Yes | Email (lowercase applied automatically) |
invitedFirstName | string (max 100) | No | Pre-fills the account |
invitedLastName | string (max 100) | No | Pre-fills the account |
profileId | UUID | Yes | Profile to assign (determines the rights) |
teamAssignments | array | No | Teams to join |
message | string | No | Custom message in the email |
language | string | No | fr, en, es, de, nl, it, pt (default: fr) |
teamLevel ∈ NONE | VIEWER | CONTRIBUTOR | MEMBER | MANAGER | ADMIN
WID alternative:
customerId,profileId, andteamId(insideteamAssignments) can also be provided via their WID (customerWid,profileWid,teamWid) as an alternative to the UUID — both forms are accepted in XOR, never both at once for the same field.
201 Response:
json{ "id": "uuid", "customerId": "uuid", "invitedEmail": "alice@acme.com", "status": "PENDING", "token": "a3f8c2d1e4b7...", "expiresAt": "2026-05-29T10:30:00.000Z", "createdAt": "2026-05-19T10:30:00.000Z", "inviter": { "firstName": "Bob", "lastName": "Smith", "email": "bob@..." }, "profile": { "name": "Participant", "app": { "name": "MyApp" } } }
Errors:
| Code | Cause |
|---|---|
400 | Invalid or disposable email (yopmail, etc.) |
400 | Team assignment references a team from another Customer |
400 | The Network's realm is not provisioned on the target envType |
400 | Unrecognized field in the body (e.g. userLevel, removed from the contract) |
403 | Missing invitations.create right, adminLevel < CUSTOMER_ADMIN, or feature disabled |
403 | App.invitationPerUserLimit reached |
404 | Customer or Profile not found |
409 | A user with this email already exists in this Customer |
409 | A PENDING invitation already exists for this email + Customer |
Management endpoints
List invitations
httpGET /api/invitations/sent # By the current user GET /api/invitations/sent/app/:appId # Filtered by App GET /api/invitations/by-customer/:customerId # All for a Customer
Query params: status, page (default 1), limit (default 20)
Resend an invitation
httpPOST /api/invitations/:id/resend
Generates a new token, resets expiresAt to +10 days, resends the email. The old token is immediately invalidated.
Usable on PENDING or EXPIRED invitations.
Revoke an invitation
httpDELETE /api/invitations/:id
Sets the invitation to CANCELLED. Response 200 OK. Required right: invitations.revoke.
curl example
bash# Create the invitation from your backend curl -X POST https://api.wakastart.app/api/invitations \ -H "x-api-key: sk_live_yourkey" \ -H "Content-Type: application/json" \ -d '{ "customerId": "11111111-1111-1111-1111-111111111111", "invitedEmail": "alice@acme.com", "invitedFirstName": "Alice", "profileId": "22222222-2222-2222-2222-222222222222", "language": "fr" }' # → 201: invitation created, email sent automatically
Best practices
- Idempotency on creation: if a call fails after creation (network issue), retrying will raise a
409 Conflict— this is safe, the existing invitation is still valid. - Never log the invitation token — it's a secret that allows creating an account.
- Pre-fill
invitedFirstName/LastNamewhen possible — better UX. - Handle the 4 error states with explicit, appropriate messages.
Go further
- Invitations — Verification & Acceptance:
verify,accept, full lifecycle - Feature flags: enabling the
invitationsfeature - Business errors:
409codes and business messages