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:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.signature
Token Generation
Endpoint: POST /api/v1/auth/token
Request:
{
"email": "admin@techpro.co.za",
"password": "secure_password"
}
Response (200 OK):
{
"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):
{
"error": "Invalid credentials"
}
Token Refresh
Endpoint: POST /api/v1/auth/refresh
Headers:
Authorization: Bearer <old_token>
Response (200 OK):
{
"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:
GET /api/v1/cohorts?status=active&page=1&per_page=10
Authorization: Bearer <token>
Response (200 OK):
{
"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):
{
"error": "Unauthorized"
}
Create Cohort
Endpoint: POST /api/v1/cohorts
Authentication: Required (TP Admin)
Request Body:
{
"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):
{
"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):
{
"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:
GET /api/v1/cohorts/1
Authorization: Bearer <token>
Response (200 OK):
{
"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):
{
"error": "Cohort not found"
}
Update Cohort
Endpoint: PATCH /api/v1/cohorts/:id
Authentication: Required (TP Admin)
Request Body:
{
"name": "Updated Cohort Name",
"sponsor_email": "new.sponsor@company.co.za",
"required_student_uploads": ["id_copy", "cv"]
}
Response (200 OK):
{
"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):
{
"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:
POST /api/v1/cohorts/1/start_signing
Authorization: Bearer <token>
Response (200 OK):
{
"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 state400 Bad Request: No template associated403 Forbidden: Insufficient permissions
{
"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:
POST /api/v1/cohorts/1/finalize
Authorization: Bearer <token>
Response (200 OK):
{
"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 signing400 Bad Request: Students haven't completed
{
"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 statusrole(optional): Filter by role (student,sponsor)page(optional): Pagination
Request:
GET /api/v1/cohorts/1/enrollments?status=complete&role=student
Authorization: Bearer <token>
Response (200 OK):
{
"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:
{
"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):
{
"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):
{
"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:
GET /api/v1/enrollments/101
Authorization: Bearer <token>
Response (200 OK):
{
"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:
{
"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):
{
"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:
{
"token": "abc123xyz"
}
Response (200 OK):
{
"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):
{
"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:
GET /api/v1/sponsor/xyz789abc/dashboard
Response (200 OK):
{
"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:
{
"signature": "John Smith",
"agree_to_terms": true
}
Response (200 OK):
{
"signed_count": 15,
"cohort_id": 1,
"status": "sponsor_completed",
"message": "All documents signed successfully. TP has been notified."
}
Error Response (400 Bad Request):
{
"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:
Content-Type: application/json
X-Webhook-Signature: sha256=...
Request Body:
{
"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 createdcohort.activated- Cohort moved to activeenrollment.created- New student enrollmentenrollment.completed- Student submittedsponsor.signed- Sponsor completed signingcohort.finalized- Cohort completed
Response (200 OK):
{
"status": "received",
"event_id": "evt_123456"
}
🔄 Error Handling
Standard Error Responses
400 Bad Request:
{
"error": "Invalid request parameters",
"details": {
"program_type": ["must be one of: learnership, internship, candidacy"]
}
}
401 Unauthorized:
{
"error": "Authentication required",
"code": "AUTH_REQUIRED"
}
403 Forbidden:
{
"error": "Insufficient permissions",
"code": "PERMISSION_DENIED"
}
404 Not Found:
{
"error": "Resource not found",
"code": "RESOURCE_NOT_FOUND"
}
422 Unprocessable Entity:
{
"error": "Validation failed",
"errors": {
"email": ["must be a valid email"],
"name": ["can't be blank"]
}
}
500 Internal Server Error:
{
"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:
{
"data": [...],
"meta": {
"current_page": 1,
"total_pages": 5,
"total_count": 95,
"per_page": 20,
"next_page": 2,
"prev_page": null
}
}
Usage:
GET /api/v1/cohorts?page=2&per_page=10
🎯 Rate Limiting
Rate Limit: 100 requests per minute per API key
Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642186800
429 Too Many Requests:
{
"error": "Rate limit exceeded",
"retry_after": 45
}
🧪 Testing the API
Using cURL
Create Cohort:
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:
curl -X GET "https://api.flodoc.com/api/v1/cohorts?status=active" \
-H "Authorization: Bearer $TOKEN"
Student Enrollment:
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
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
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
- Implement API Controllers - Start with cohorts endpoints
- Add Authentication - JWT token system
- Write Request Specs - Test all endpoints
- Create API Documentation - Auto-generate from specs
- Test Integration - Verify with real data
Document Status: ✅ Complete Ready for: API Implementation (Story 3.x)