# 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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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: ; 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)