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

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 state
  • 400 Bad Request: No template associated
  • 403 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 signing
  • 400 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 status
  • role (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 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):

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

  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)