Spaces:
Sleeping
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:** | |
| ```json | |
| { | |
| "email": "john@example.com", | |
| "phone": "+254712345678", | |
| "role": "field_agent", | |
| "contractor_id": "uuid", | |
| "invitation_method": "whatsapp" | |
| } | |
| ``` | |
| **Response:** `201 Created` | |
| ```json | |
| { | |
| "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 skip | |
| - `limit` (int, default: 50, max: 100) - Records per page | |
| - `status` (string, optional) - Filter by status: pending, accepted, expired, cancelled | |
| - `client_id` (uuid, optional) - Filter by client | |
| - `contractor_id` (uuid, optional) - Filter by contractor | |
| **Response:** `200 OK` | |
| ```json | |
| { | |
| "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` | |
| ```json | |
| { | |
| "id": "uuid", | |
| "email": "john@example.com", | |
| ... | |
| } | |
| ``` | |
| --- | |
| ### 4. Resend Invitation | |
| **POST** `/api/v1/invitations/{invitation_id}/resend` | |
| Resend invitation notification. | |
| **Authorization:** Required | |
| **Request Body:** | |
| ```json | |
| { | |
| "invitation_method": "email" | |
| } | |
| ``` | |
| **Response:** `200 OK` | |
| ```json | |
| { | |
| "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:** | |
| ```json | |
| { | |
| "token": "secure_random_token" | |
| } | |
| ``` | |
| **Response:** `200 OK` | |
| ```json | |
| { | |
| "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` | |
| ```json | |
| { | |
| "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:** | |
| ```json | |
| { | |
| "token": "secure_random_token", | |
| "first_name": "John", | |
| "last_name": "Doe", | |
| "password": "SecurePass123!", | |
| "phone": "+254712345678" | |
| } | |
| ``` | |
| **Response:** `200 OK` | |
| ```json | |
| { | |
| "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 | |
| 1. Try WhatsApp first (saves email credits) | |
| 2. If WhatsApp fails → Automatically fallback to Email | |
| 3. Track delivery status for both methods | |
| ### Method Options | |
| - `whatsapp` - Try WhatsApp, fallback to Email if fails | |
| - `email` - Send via Email only | |
| - `both` - Send via both WhatsApp AND Email | |
| --- | |
| ## Testing with cURL | |
| ### Create Invitation | |
| ```bash | |
| 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) | |
| ```bash | |
| curl -X POST "http://localhost:8000/api/v1/invitations/validate" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "token": "secure_random_token" | |
| }' | |
| ``` | |
| ### Accept Invitation (Public) | |
| ```bash | |
| 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 | |
| ```typescript | |
| 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 | |
| ```typescript | |
| 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: | |
| 1. `supabase/migrations/11_user_invitations.sql` - Creates table and enums | |
| 2. `supabase/migrations/12_user_invitations_rls.sql` - Enables RLS and policies | |
| --- | |
| ## Environment Variables Required | |
| ```env | |
| 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 | |
| ``` | |