You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/docs/architecture/api-design.md

979 lines
18 KiB

# API Design - FloDoc Architecture
**Document**: RESTful API Specifications
**Version**: 1.0
**Last Updated**: 2026-01-14
---
## 📡 API Overview
FloDoc provides a RESTful API for programmatic access to cohort management, student enrollment, and sponsor workflows. The API follows REST principles and uses JSON for request/response payloads.
**Base URL**: `/api/v1/`
**Authentication**: Bearer token in Authorization header
**Response Format**: JSON
---
## 🔐 Authentication
### JWT Token Authentication
All API requests must include an authentication token:
```http
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.signature
```
### Token Generation
**Endpoint**: `POST /api/v1/auth/token`
**Request**:
```json
{
"email": "admin@techpro.co.za",
"password": "secure_password"
}
```
**Response (200 OK)**:
```json
{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.signature",
"expires_at": "2026-01-15T19:00:00Z",
"user": {
"id": 1,
"email": "admin@techpro.co.za",
"role": "tp_admin"
}
}
```
**Error Response (401 Unauthorized)**:
```json
{
"error": "Invalid credentials"
}
```
### Token Refresh
**Endpoint**: `POST /api/v1/auth/refresh`
**Headers**:
```http
Authorization: Bearer <old_token>
```
**Response (200 OK)**:
```json
{
"token": "new_token_here",
"expires_at": "2026-01-16T19:00:00Z"
}
```
---
## 📚 API Endpoints
### 1. Cohorts Management
#### List Cohorts
**Endpoint**: `GET /api/v1/cohorts`
**Authentication**: Required (TP Admin)
**Query Parameters**:
- `status` (optional): Filter by status (`draft`, `active`, `completed`)
- `page` (optional): Pagination page (default: 1)
- `per_page` (optional): Items per page (default: 20)
**Request**:
```http
GET /api/v1/cohorts?status=active&page=1&per_page=10
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"data": [
{
"id": 1,
"name": "2026 Q1 Learnership",
"program_type": "learnership",
"sponsor_email": "sponsor@company.co.za",
"status": "active",
"required_student_uploads": ["id_copy", "matric_certificate"],
"cohort_metadata": {
"start_date": "2026-02-01",
"duration_months": 12,
"stipend_amount": 3500
},
"tp_signed_at": "2026-01-14T10:00:00Z",
"students_completed_at": null,
"sponsor_completed_at": null,
"finalized_at": null,
"student_count": 15,
"completed_count": 8,
"created_at": "2026-01-10T08:00:00Z",
"updated_at": "2026-01-14T10:00:00Z"
}
],
"meta": {
"current_page": 1,
"total_pages": 3,
"total_count": 25,
"per_page": 10
}
}
```
**Error Response (401 Unauthorized)**:
```json
{
"error": "Unauthorized"
}
```
---
#### Create Cohort
**Endpoint**: `POST /api/v1/cohorts`
**Authentication**: Required (TP Admin)
**Request Body**:
```json
{
"name": "2026 Q2 Internship",
"program_type": "internship",
"sponsor_email": "hr@company.co.za",
"template_id": 42,
"required_student_uploads": ["id_copy", "cv", "tertiary_certificate"],
"cohort_metadata": {
"start_date": "2026-04-01",
"duration_months": 6,
"department": "Engineering"
}
}
```
**Required Fields**:
- `name` (string)
- `program_type` (enum: `learnership`, `internship`, `candidacy`)
- `sponsor_email` (string, valid email)
- `template_id` (integer, must exist)
**Optional Fields**:
- `required_student_uploads` (array of strings)
- `cohort_metadata` (object)
**Response (201 Created)**:
```json
{
"id": 2,
"name": "2026 Q2 Internship",
"program_type": "internship",
"sponsor_email": "hr@company.co.za",
"status": "draft",
"required_student_uploads": ["id_copy", "cv", "tertiary_certificate"],
"cohort_metadata": {
"start_date": "2026-04-01",
"duration_months": 6,
"department": "Engineering"
},
"tp_signed_at": null,
"students_completed_at": null,
"sponsor_completed_at": null,
"finalized_at": null,
"created_at": "2026-01-14T19:00:00Z",
"updated_at": "2026-01-14T19:00:00Z"
}
```
**Error Response (422 Unprocessable Entity)**:
```json
{
"errors": {
"name": ["can't be blank"],
"program_type": ["is not included in the list"],
"sponsor_email": ["must be a valid email"]
}
}
```
---
#### Get Cohort Details
**Endpoint**: `GET /api/v1/cohorts/:id`
**Authentication**: Required (TP Admin, must belong to same institution)
**Path Parameters**:
- `id`: Cohort ID
**Request**:
```http
GET /api/v1/cohorts/1
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"id": 1,
"name": "2026 Q1 Learnership",
"program_type": "learnership",
"sponsor_email": "sponsor@company.co.za",
"status": "active",
"required_student_uploads": ["id_copy", "matric_certificate"],
"cohort_metadata": {
"start_date": "2026-02-01",
"duration_months": 12,
"stipend_amount": 3500
},
"tp_signed_at": "2026-01-14T10:00:00Z",
"students_completed_at": null,
"sponsor_completed_at": null,
"finalized_at": null,
"template": {
"id": 42,
"name": "Standard Learnership Agreement"
},
"enrollments": [
{
"id": 101,
"student_email": "john@example.com",
"student_name": "John",
"student_surname": "Doe",
"status": "waiting",
"role": "student",
"uploaded_documents": {
"id_copy": true,
"matric_certificate": false
},
"completed_at": null
}
],
"stats": {
"total_students": 15,
"completed": 8,
"waiting": 5,
"in_progress": 2
},
"created_at": "2026-01-10T08:00:00Z"
}
```
**Error Response (404 Not Found)**:
```json
{
"error": "Cohort not found"
}
```
---
#### Update Cohort
**Endpoint**: `PATCH /api/v1/cohorts/:id`
**Authentication**: Required (TP Admin)
**Request Body**:
```json
{
"name": "Updated Cohort Name",
"sponsor_email": "new.sponsor@company.co.za",
"required_student_uploads": ["id_copy", "cv"]
}
```
**Response (200 OK)**:
```json
{
"id": 1,
"name": "Updated Cohort Name",
"sponsor_email": "new.sponsor@company.co.za",
"required_student_uploads": ["id_copy", "cv"],
"updated_at": "2026-01-14T19:30:00Z"
}
```
**Error Response (422 Unprocessable Entity)**:
```json
{
"errors": {
"sponsor_email": ["must be a valid email"]
}
}
```
---
#### Start Signing Phase
**Endpoint**: `POST /api/v1/cohorts/:id/start_signing`
**Authentication**: Required (TP Admin)
**Description**: Transitions cohort from `draft` to `active` state. Allows students to enroll.
**Request**:
```http
POST /api/v1/cohorts/1/start_signing
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"id": 1,
"status": "active",
"tp_signed_at": "2026-01-14T19:30:00Z",
"message": "Cohort is now active. Students can enroll."
}
```
**Error Responses**:
- `400 Bad Request`: Cohort is not in draft state
- `400 Bad Request`: No template associated
- `403 Forbidden`: Insufficient permissions
```json
{
"error": "Cohort must be in draft state to start signing"
}
```
---
#### Finalize Cohort
**Endpoint**: `POST /api/v1/cohorts/:id/finalize`
**Authentication**: Required (TP Admin)
**Description**: Marks cohort as completed after sponsor signing. Generates final documents.
**Request**:
```http
POST /api/v1/cohorts/1/finalize
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"id": 1,
"status": "completed",
"finalized_at": "2026-01-14T19:45:00Z",
"message": "Cohort finalized. All documents are ready for download."
}
```
**Error Responses**:
- `400 Bad Request`: Sponsor hasn't completed signing
- `400 Bad Request`: Students haven't completed
```json
{
"error": "Cannot finalize: sponsor signing incomplete"
}
```
---
### 2. Enrollments Management
#### List Cohort Enrollments
**Endpoint**: `GET /api/v1/cohorts/:id/enrollments`
**Authentication**: Required (TP Admin)
**Query Parameters**:
- `status` (optional): Filter by status
- `role` (optional): Filter by role (`student`, `sponsor`)
- `page` (optional): Pagination
**Request**:
```http
GET /api/v1/cohorts/1/enrollments?status=complete&role=student
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"data": [
{
"id": 101,
"cohort_id": 1,
"submission_id": 501,
"student_email": "john@example.com",
"student_name": "John",
"student_surname": "Doe",
"student_id": "STU2026001",
"status": "complete",
"role": "student",
"uploaded_documents": {
"id_copy": true,
"matric_certificate": true,
"cv": true
},
"values": {
"full_name": "John Doe",
"phone": "+27 82 123 4567"
},
"completed_at": "2026-01-14T15:00:00Z",
"created_at": "2026-01-12T10:00:00Z"
}
],
"meta": {
"current_page": 1,
"total_pages": 1,
"total_count": 5
}
}
```
---
#### Create Enrollment (Bulk)
**Endpoint**: `POST /api/v1/cohorts/:id/enrollments`
**Authentication**: Required (TP Admin)
**Description**: Creates multiple student enrollments at once. Sends invitation emails.
**Request Body**:
```json
{
"students": [
{
"email": "john@example.com",
"name": "John",
"surname": "Doe",
"student_id": "STU2026001"
},
{
"email": "jane@example.com",
"name": "Jane",
"surname": "Smith",
"student_id": "STU2026002"
}
]
}
```
**Response (201 Created)**:
```json
{
"created": 2,
"failed": 0,
"enrollments": [
{
"id": 101,
"student_email": "john@example.com",
"status": "waiting",
"token": "abc123xyz"
},
{
"id": 102,
"student_email": "jane@example.com",
"status": "waiting",
"token": "def456uvw"
}
]
}
```
**Error Response (207 Multi-Status)**:
```json
{
"created": 1,
"failed": 1,
"errors": [
{
"email": "duplicate@example.com",
"error": "Student already enrolled in this cohort"
}
]
}
```
---
#### Get Enrollment Details
**Endpoint**: `GET /api/v1/enrollments/:id`
**Authentication**: Required (TP Admin or Student with token)
**Request**:
```http
GET /api/v1/enrollments/101
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"id": 101,
"cohort_id": 1,
"submission_id": 501,
"student_email": "john@example.com",
"student_name": "John",
"student_surname": "Doe",
"status": "in_progress",
"role": "student",
"uploaded_documents": {
"id_copy": true,
"matric_certificate": false
},
"required_documents": ["id_copy", "matric_certificate", "cv"],
"token": "abc123xyz",
"token_expires_at": "2026-01-21T19:00:00Z",
"created_at": "2026-01-12T10:00:00Z"
}
```
---
#### Update Enrollment (Student Portal)
**Endpoint**: `PATCH /api/v1/enrollments/:id`
**Authentication**: Token-based (ad-hoc)
**Description**: Student updates their enrollment, uploads documents, fills forms.
**Request Body**:
```json
{
"token": "abc123xyz",
"uploaded_documents": {
"id_copy": true,
"matric_certificate": true
},
"values": {
"full_name": "John Doe",
"phone": "+27 82 123 4567",
"address": "123 Main St"
}
}
```
**Response (200 OK)**:
```json
{
"id": 101,
"status": "in_progress",
"uploaded_documents": {
"id_copy": true,
"matric_certificate": true
},
"values": {
"full_name": "John Doe",
"phone": "+27 82 123 4567",
"address": "123 Main St"
},
"progress": "66%",
"message": "Progress saved. Submit when complete."
}
```
---
#### Submit Enrollment
**Endpoint**: `POST /api/v1/enrollments/:id/submit`
**Authentication**: Token-based (ad-hoc)
**Description**: Final submission of student enrollment.
**Request Body**:
```json
{
"token": "abc123xyz"
}
```
**Response (200 OK)**:
```json
{
"id": 101,
"status": "complete",
"completed_at": "2026-01-14T15:00:00Z",
"message": "Enrollment submitted successfully. You will be notified of next steps."
}
```
**Error Response (400 Bad Request)**:
```json
{
"error": "Missing required documents: matric_certificate"
}
```
---
### 3. Sponsor Portal
#### Get Sponsor Dashboard
**Endpoint**: `GET /api/v1/sponsor/:token/dashboard`
**Authentication**: Token-based (ad-hoc)
**Path Parameters**:
- `token`: Sponsor token from email
**Request**:
```http
GET /api/v1/sponsor/xyz789abc/dashboard
```
**Response (200 OK)**:
```json
{
"cohort": {
"id": 1,
"name": "2026 Q1 Learnership",
"program_type": "learnership",
"status": "active"
},
"stats": {
"total_students": 15,
"completed": 15,
"pending": 0
},
"documents_ready": true,
"can_sign": true,
"token_expires_at": "2026-01-21T19:00:00Z"
}
```
---
#### Bulk Sign Documents
**Endpoint**: `POST /api/v1/sponsor/:token/sign`
**Authentication**: Token-based (ad-hoc)
**Description**: Sponsor signs once, applies to all student documents.
**Request Body**:
```json
{
"signature": "John Smith",
"agree_to_terms": true
}
```
**Response (200 OK)**:
```json
{
"signed_count": 15,
"cohort_id": 1,
"status": "sponsor_completed",
"message": "All documents signed successfully. TP has been notified."
}
```
**Error Response (400 Bad Request)**:
```json
{
"error": "All students must complete before sponsor signing"
}
```
---
### 4. Webhooks
#### Webhook Endpoint
**Endpoint**: `POST /api/v1/webhooks`
**Authentication**: HMAC signature (optional but recommended)
**Description**: Receives webhook events for workflow state changes.
**Headers**:
```http
Content-Type: application/json
X-Webhook-Signature: sha256=...
```
**Request Body**:
```json
{
"event": "submission.completed",
"timestamp": "2026-01-14T15:00:00Z",
"data": {
"cohort_id": 1,
"enrollment_id": 101,
"student_email": "john@example.com"
}
}
```
**Event Types**:
- `cohort.created` - New cohort created
- `cohort.activated` - Cohort moved to active
- `enrollment.created` - New student enrollment
- `enrollment.completed` - Student submitted
- `sponsor.signed` - Sponsor completed signing
- `cohort.finalized` - Cohort completed
**Response (200 OK)**:
```json
{
"status": "received",
"event_id": "evt_123456"
}
```
---
## 🔄 Error Handling
### Standard Error Responses
**400 Bad Request**:
```json
{
"error": "Invalid request parameters",
"details": {
"program_type": ["must be one of: learnership, internship, candidacy"]
}
}
```
**401 Unauthorized**:
```json
{
"error": "Authentication required",
"code": "AUTH_REQUIRED"
}
```
**403 Forbidden**:
```json
{
"error": "Insufficient permissions",
"code": "PERMISSION_DENIED"
}
```
**404 Not Found**:
```json
{
"error": "Resource not found",
"code": "RESOURCE_NOT_FOUND"
}
```
**422 Unprocessable Entity**:
```json
{
"error": "Validation failed",
"errors": {
"email": ["must be a valid email"],
"name": ["can't be blank"]
}
}
```
**500 Internal Server Error**:
```json
{
"error": "Internal server error",
"code": "SERVER_ERROR"
}
```
---
## 📊 Pagination
All list endpoints support cursor-based pagination:
**Query Parameters**:
- `page`: Page number (default: 1)
- `per_page`: Items per page (default: 20, max: 100)
**Response Structure**:
```json
{
"data": [...],
"meta": {
"current_page": 1,
"total_pages": 5,
"total_count": 95,
"per_page": 20,
"next_page": 2,
"prev_page": null
}
}
```
**Usage**:
```http
GET /api/v1/cohorts?page=2&per_page=10
```
---
## 🎯 Rate Limiting
**Rate Limit**: 100 requests per minute per API key
**Headers**:
```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642186800
```
**429 Too Many Requests**:
```json
{
"error": "Rate limit exceeded",
"retry_after": 45
}
```
---
## 🧪 Testing the API
### Using cURL
**Create Cohort**:
```bash
curl -X POST https://api.flodoc.com/api/v1/cohorts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Cohort",
"program_type": "learnership",
"sponsor_email": "test@example.com",
"template_id": 1
}'
```
**Get Cohorts**:
```bash
curl -X GET "https://api.flodoc.com/api/v1/cohorts?status=active" \
-H "Authorization: Bearer $TOKEN"
```
**Student Enrollment**:
```bash
curl -X PATCH https://api.flodoc.com/api/v1/enrollments/101 \
-H "Content-Type: application/json" \
-d '{
"token": "abc123xyz",
"values": {"full_name": "John Doe"}
}'
```
---
## 🔒 Security Best Practices
### 1. Token Security
- Tokens expire after 7 days
- Use HTTPS in production
- Store tokens securely (not in localStorage for web)
- Implement token refresh mechanism
### 2. Input Validation
- Always validate on backend
- Sanitize all inputs
- Use strong parameters
- Limit file uploads (size, type)
### 3. Rate Limiting
- Implement per-user rate limits
- Track API usage
- Block abusive clients
### 4. Webhook Security
- Verify HMAC signatures
- Validate event payloads
- Implement retry logic with exponential backoff
- Log all webhook deliveries
### 5. CORS
- Restrict origins in production
- Use specific allowed methods
- Implement preflight caching
---
## 📚 API Versioning
### Version Strategy
- URL-based: `/api/v1/`
- Future versions: `/api/v2/`
- Backward compatibility maintained for 6 months
- Deprecation headers for old versions
### Deprecation Headers
```http
Deprecation: true
Sunset: Mon, 31 Dec 2026 23:59:59 GMT
Link: </api/v2/cohorts>; rel="successor-version"
```
---
## 🔄 Webhook Delivery
### Delivery Guarantees
- At-least-once delivery
- Exponential backoff retry (1m, 5m, 15m, 1h, 6h)
- Max 5 retries
- Dead letter queue for failures
### Retry Logic
```ruby
class WebhookDeliveryJob < ApplicationJob
retry_on StandardError, wait: :exponentially_longer, attempts: 5
def perform(event)
# Delivery logic
end
end
```
---
## 📋 API Checklist
- [ ] Authentication required for all endpoints except auth
- [ ] Proper HTTP status codes
- [ ] Consistent JSON response format
- [ ] Error handling with helpful messages
- [ ] Pagination for list endpoints
- [ ] Rate limiting implemented
- [ ] Input validation on all endpoints
- [ ] CORS configured
- [ ] Webhook signature verification
- [ ] API versioning strategy
- [ ] Documentation complete
---
## 🎯 Next Steps
1. **Implement API Controllers** - Start with cohorts endpoints
2. **Add Authentication** - JWT token system
3. **Write Request Specs** - Test all endpoints
4. **Create API Documentation** - Auto-generate from specs
5. **Test Integration** - Verify with real data
---
**Document Status**: ✅ Complete
**Ready for**: API Implementation (Story 3.x)