Skip to content

Community Investment API

Status: Final Version: 1.0 Last Updated: 2026-01-21


Purpose

Define REST endpoints for managing Community Investment and Development activities (CSR/CSIR) to support the H.1 Quarterly Report and GRI 203 reporting.

This API enables: - Collectors: Submit community investment activity logs (projects, donations, sponsorships) - Reviewers/Approvers: Review and validate financial and non-financial contributions - Admins: Export H.1 reports for quarterly reporting - All Roles: Track budget vs. actuals and beneficiary impact


Table of Contents

  1. Authentication
  2. Community Investment Activities
  3. Evidence Management
  4. Report Export
  5. Filtering and Pagination
  6. Error Handling
  7. Implementation Guide

Authentication

All community investment endpoints require JWT authentication. See API Overview - Authentication for details.


Community Investment Activities

POST /api/v1/community-investment/activities

Create a new community investment activity record. Supports idempotency.

Authentication: Required (COLLECTOR role)

Headers:

Header Required Description
Authorization Yes Bearer JWT token
Idempotency-Key Yes UUID v4 for idempotent submission
Content-Type Yes application/json

Request Body:

{
  "activityUuid": "act-550e8400-e29b-41d4-a716-446655440000",
  "reportingPeriodId": "period-uuid",
  "organisationId": "org-uuid",
  "siteId": "site-uuid",
  "description": "Annual School Uniform Donation Drive",
  "pillar": "Education & Sports",
  "startDate": "2026-01-15",
  "endDate": "2026-01-15",
  "budget": 5000.00,
  "actual": 4850.50,
  "currency": "USD",
  "beneficiaries": "150 students from Local Primary School"
}

Field Descriptions:

Field Type Required Description
activityUuid UUID Yes Client-generated UUID for deduplication
reportingPeriodId UUID Yes Target reporting period
organisationId UUID Yes Organisation responsible for activity
siteId UUID No Site where activity occurred (if applicable)
description String Yes Description of the initiative (max 1000 chars)
pillar String Yes 'Education & Sports', 'Social Empowerment', 'Community Development', 'Donations & Sponsorship', 'Other'
startDate Date Yes Start date of activity (ISO 8601)
endDate Date Yes End date of activity (ISO 8601). Same as startDate for single-day events.
budget Decimal No Budgeted amount. Optional.
actual Decimal Yes Actual amount spent/invested. Required.
currency String Yes 3-letter ISO 4217 currency code (e.g., "USD", "ZAR")
beneficiaries String No Description of beneficiaries or impact (max 1000 chars)

Response (201 Created):

{
  "id": "activity-uuid",
  "activityUuid": "act-550e8400-e29b-41d4-a716-446655440000",
  "reportingPeriod": {
    "id": "period-uuid",
    "name": "Q1 2026",
    "startDate": "2026-01-01",
    "endDate": "2026-03-31"
  },
  "organisation": {
    "id": "org-uuid",
    "name": "Acme Corporation"
  },
  "site": {
    "id": "site-uuid",
    "name": "Factory A"
  },
  "description": "Annual School Uniform Donation Drive",
  "pillar": "Education & Sports",
  "startDate": "2026-01-15",
  "endDate": "2026-01-15",
  "budget": 5000.00,
  "actual": 4850.50,
  "variance": -149.50,
  "currency": "USD",
  "beneficiaries": "150 students from Local Primary School",
  "createdAt": "2026-01-21T10:30:00Z",
  "updatedAt": "2026-01-21T10:30:00Z",
  "createdBy": {
    "id": "user-uuid",
    "name": "John Collector",
    "email": "john@example.com"
  },
  "evidenceCount": 0
}

Error Responses:

Status Error Code Description
400 VALIDATION_ERROR Invalid request format or missing required fields
422 VALIDATION_RULE_FAILED Business rule violation (e.g., negative actuals, invalid currency)
409 RESOURCE_LOCKED Reporting period is locked

GET /api/v1/community-investment/activities

Retrieve community investment activities with filtering and pagination.

Authentication: Required (COLLECTOR, REVIEWER, APPROVER, ADMIN roles)

Query Parameters:

Parameter Type Required Description
reportingPeriodId UUID No Filter by reporting period
organisationId UUID No Filter by organisation
pillar String No Filter by pillar
startDateFrom Date No Filter by start date
startDateTo Date No Filter by start date
search String No Full-text search in description and beneficiaries
page Integer No Page number (default: 1)
pageSize Integer No Items per page (default: 50)

Request Example:

GET /api/v1/community-investment/activities?reportingPeriodId=period-uuid&pillar=Education+%26+Sports
Authorization: Bearer token...

Response (200 OK):

{
  "data": [
    {
      "id": "activity-uuid",
      "description": "Annual School Uniform Donation Drive",
      "pillar": "Education & Sports",
      "actual": 4850.50,
      "currency": "USD",
      "startDate": "2026-01-15",
      "evidenceCount": 1
      // ... other fields
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 50,
    "totalItems": 1,
    "totalPages": 1
  }
}


GET /api/v1/community-investment/activities/{id}

Retrieve detailed information about a specific activity.

Authentication: Required

Path Parameters:

Parameter Type Description
id UUID Activity UUID

Response (200 OK):

{
  "id": "activity-uuid",
  // ... all fields
  "evidence": [
    {
      "id": "evidence-uuid",
      "filename": "invoice_123.pdf",
      "evidenceType": "INVOICE",
      "uploadedAt": "2026-01-21T10:35:00Z"
    }
  ]
}


PATCH /api/v1/community-investment/activities/{id}

Update an existing activity record.

Authentication: Required (COLLECTOR, REVIEWER, ADMIN roles)

Request Body:

{
  "actual": 5000.00,
  "beneficiaries": "155 students (updated)"
}

Response (200 OK): Returns updated resource.


Evidence Management

POST /api/v1/community-investment/activities/{id}/evidence

Link uploaded evidence to an activity. Evidence must be uploaded first via POST /api/v1/collector/evidence.

Authentication: Required (COLLECTOR role)

Path Parameters:

Parameter Type Description
id UUID Activity UUID

Request Body:

{
  "evidenceId": "evidence-uuid"
}

Response (200 OK):

{
  "activityId": "activity-uuid",
  "evidenceId": "evidence-uuid",
  "linkedAt": "2026-01-21T10:40:00Z",
  "message": "Evidence linked successfully"
}


Report Export

POST /api/v1/community-investment/reports/export

Generate and download the H.1 Community Investment report in CSV or XLSX format.

Authentication: Required (REVIEWER, APPROVER, ADMIN roles)

Request Body:

{
  "reportingPeriodId": "period-uuid",
  "organisationId": "org-uuid",
  "format": "XLSX",
  "filterPillar": ["Education & Sports", "Social Empowerment"] // Optional
}

Response (202 Accepted):

{
  "jobId": "export-job-uuid",
  "status": "QUEUED",
  "statusUrl": "/api/v1/community-investment/reports/export/export-job-uuid"
}

Check Export Status: GET /api/v1/community-investment/reports/export/{jobId}

Download Export: GET /api/v1/community-investment/reports/export/{jobId}/download

Report Structure: - H.1 Quarterly CSR/CSIR Activities: Matches the columns defined in docs/reporting/report-outputs.md. - Includes calculated totals (Budget, Actual, Variance).


Filtering and Pagination

Standard API pagination and filtering applies.

Specific Filters: - pillar: Exact match on pillar enum. - currency: Filter by currency code.


Error Handling

See Error Handling.

Specific Errors:

  • VALIDATION_RULE_FAILED:
  • code: ACTUAL_REQUIRED_WHEN_BUDGET_MISSING
  • code: CURRENCY_REQUIRED
  • code: INVALID_DATE_RANGE (if end date < start date)

Implementation Guide

Validation Logic (Quarkus Service)

fun validateActivity(request: CreateActivityRequest) {
    if (request.actual < BigDecimal.ZERO) {
        throw ValidationException("Actual amount cannot be negative")
    }
    if (request.budget != null && request.budget < BigDecimal.ZERO) {
        throw ValidationException("Budget amount cannot be negative")
    }
    if (request.endDate.isBefore(request.startDate)) {
        throw ValidationException("End date cannot be before start date")
    }
    if (!isValidCurrency(request.currency)) {
        throw ValidationException("Invalid ISO currency code")
    }
}

Variance Calculation

Variance is calculated dynamically in the API response and export: Variance = Actual - Budget

If Budget is null, Variance is typically Actual (indicating unbudgeted spend) or handled as null depending on reporting logic. For this API, return Actual if budget is missing, or specific logic defined in report-outputs.md.