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
- Authentication
- Community Investment Activities
- Evidence Management
- Report Export
- Filtering and Pagination
- Error Handling
- 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:
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:
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_MISSINGcode: CURRENCY_REQUIREDcode: 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.