kamau1's picture
feat(project): add complete project setup workflow with service methods and API endpoints for regions, roles, subcontractors, and finalization including validation and authorization
4835b24

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 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

{
  "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

  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

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:

  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

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