Spaces:
Sleeping
User Invitations API Guide
Overview
Complete API documentation for the user invitation system.
Base URL
/api/v1/invitations
Endpoints
1. Create Invitation
POST /api/v1/invitations
Create a new user invitation and send notification.
Authorization: Required (platform_admin, client_admin, contractor_admin)
Request Body:
{
"email": "john@example.com",
"phone": "+254712345678",
"role": "field_agent",
"contractor_id": "uuid",
"invitation_method": "whatsapp"
}
Response: 201 Created
{
"id": "uuid",
"email": "john@example.com",
"phone": "+254712345678",
"invited_role": "field_agent",
"contractor_id": "uuid",
"client_id": null,
"token": "secure_random_token",
"status": "pending",
"invitation_method": "whatsapp",
"invited_by_user_id": "uuid",
"invited_at": "2025-11-16T10:00:00Z",
"expires_at": "2025-11-19T10:00:00Z",
"accepted_at": null,
"whatsapp_sent": true,
"whatsapp_sent_at": "2025-11-16T10:00:01Z",
"email_sent": false,
"email_sent_at": null,
"invitation_metadata": {},
"created_at": "2025-11-16T10:00:00Z",
"updated_at": "2025-11-16T10:00:00Z"
}
2. List Invitations
GET /api/v1/invitations
List invitations with pagination and filters.
Authorization: Required
Query Parameters:
skip(int, default: 0) - Number of records to skiplimit(int, default: 50, max: 100) - Records per pagestatus(string, optional) - Filter by status: pending, accepted, expired, cancelledclient_id(uuid, optional) - Filter by clientcontractor_id(uuid, optional) - Filter by contractor
Response: 200 OK
{
"invitations": [...],
"total": 100,
"page": 1,
"page_size": 50,
"total_pages": 2
}
3. Get Invitation
GET /api/v1/invitations/{invitation_id}
Get a specific invitation by ID.
Authorization: Required
Response: 200 OK
{
"id": "uuid",
"email": "john@example.com",
...
}
4. Resend Invitation
POST /api/v1/invitations/{invitation_id}/resend
Resend invitation notification.
Authorization: Required
Request Body:
{
"invitation_method": "email"
}
Response: 200 OK
{
"id": "uuid",
"email": "john@example.com",
...
}
5. Cancel Invitation
DELETE /api/v1/invitations/{invitation_id}
Cancel a pending invitation.
Authorization: Required
Response: 204 No Content
Public Endpoints (No Authentication)
6. Validate Invitation Token
POST /api/v1/invitations/validate
Validate an invitation token before showing registration form.
Authorization: None (Public)
Request Body:
{
"token": "secure_random_token"
}
Response: 200 OK
{
"id": "uuid",
"email": "john@example.com",
"invited_role": "field_agent",
"status": "pending",
"expires_at": "2025-11-19T10:00:00Z",
"organization_name": "TechInstall Ltd",
"organization_type": "contractor",
"is_expired": false,
"is_valid": true
}
Error Response: 400 Bad Request
{
"detail": "Invalid or expired invitation token"
}
7. Accept Invitation
POST /api/v1/invitations/accept
Accept invitation and create user account.
Authorization: None (Public)
Request Body:
{
"token": "secure_random_token",
"first_name": "John",
"last_name": "Doe",
"password": "SecurePass123!",
"phone": "+254712345678"
}
Response: 200 OK
{
"access_token": "jwt_token_here",
"token_type": "bearer",
"user": {
"id": "uuid",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"role": "field_agent",
"is_active": true
}
}
Authorization Rules
| Role | Can Invite To | Can View | Can Resend | Can Cancel |
|---|---|---|---|---|
platform_admin |
Any organization | All invitations | All invitations | All invitations |
client_admin |
Their client only | Their client's invitations | Their client's invitations | Their client's invitations |
contractor_admin |
Their contractor only | Their contractor's invitations | Their contractor's invitations | Their contractor's invitations |
| Other roles | Cannot invite | Invitations they created | Invitations they created | Invitations they created |
Invitation Flow
1. Admin creates invitation
POST /api/v1/invitations
β
2. System sends WhatsApp/Email with link
https://swiftops.atomio.tech/accept-invitation?token=xxx
β
3. User clicks link, frontend validates token
POST /api/v1/invitations/validate
β
4. Frontend shows registration form
β
5. User fills form and submits
POST /api/v1/invitations/accept
β
6. Backend creates Supabase user + local profile
β
7. User is logged in automatically
Error Codes
| Code | Description |
|---|---|
| 400 | Invalid request data or expired token |
| 401 | Authentication required |
| 403 | Insufficient permissions |
| 404 | Invitation not found |
| 500 | Server error |
Notification Delivery
Default Behavior
- Try WhatsApp first (saves email credits)
- If WhatsApp fails β Automatically fallback to Email
- Track delivery status for both methods
Method Options
whatsapp- Try WhatsApp, fallback to Email if failsemail- Send via Email onlyboth- Send via both WhatsApp AND Email
Testing with cURL
Create Invitation
curl -X POST "http://localhost:8000/api/v1/invitations" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"phone": "+254712345678",
"role": "field_agent",
"contractor_id": "uuid",
"invitation_method": "whatsapp"
}'
Validate Token (Public)
curl -X POST "http://localhost:8000/api/v1/invitations/validate" \
-H "Content-Type: application/json" \
-d '{
"token": "secure_random_token"
}'
Accept Invitation (Public)
curl -X POST "http://localhost:8000/api/v1/invitations/accept" \
-H "Content-Type: application/json" \
-d '{
"token": "secure_random_token",
"first_name": "John",
"last_name": "Doe",
"password": "SecurePass123!",
"phone": "+254712345678"
}'
Frontend Integration
Step 1: User receives invitation
Email/WhatsApp contains link: https://swiftops.atomio.tech/accept-invitation?token=xxx
Step 2: Validate token
const response = await fetch('/api/v1/invitations/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
});
const invitation = await response.json();
// Show registration form with pre-filled email
Step 3: Accept invitation
const response = await fetch('/api/v1/invitations/accept', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
first_name,
last_name,
password,
phone
})
});
const { access_token, user } = await response.json();
// Store token and redirect to dashboard
Database Migrations
Run these migrations in order:
supabase/migrations/11_user_invitations.sql- Creates table and enumssupabase/migrations/12_user_invitations_rls.sql- Enables RLS and policies
Environment Variables Required
APP_DOMAIN=swiftops.atomio.tech
APP_PROTOCOL=https
INVITATION_TOKEN_EXPIRY_HOURS=72
RESEND_API_KEY=re_xxx
RESEND_FROM_EMAIL=swiftops@atomio.tech
WASENDER_API_KEY=xxx
WASENDER_PHONE_NUMBER=+254xxx
WASENDER_API_URL=https://api.wasender.com/v1