Skip to content

Stakeholder Engagement API

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


Purpose

Define REST endpoints for managing stakeholder engagement records and grievance mechanisms to support GRI 2-29 (Approach to stakeholder engagement) and related stakeholder reporting requirements.

This API enables: - Collectors: Submit stakeholder engagement logs and grievance records - Reviewers/Approvers: Review and validate stakeholder engagement data - Admins: Manage stakeholder categories, engagement platforms, and export reports - All Roles: Track engagement activities and grievance resolution status


Table of Contents

  1. Authentication
  2. Master Data Management
  3. Stakeholder Engagement Records
  4. Grievance Records
  5. Evidence Management
  6. Report Export
  7. Filtering and Pagination
  8. Error Handling
  9. PII Protection
  10. Implementation Guide

Authentication

All stakeholder engagement endpoints require JWT authentication. See API Overview - Authentication for details.

Quick Example:

GET /api/v1/stakeholder-engagement/categories
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...


Master Data Management

GET /api/v1/stakeholder-engagement/categories

Fetch stakeholder category master list (e.g., Community, Employees, Suppliers, Investors).

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

Query Parameters:

Parameter Type Required Description
includeInactive boolean No Include deactivated categories (default: false)

Request Example:

GET /api/v1/stakeholder-engagement/categories
Authorization: Bearer token...

Response (200 OK):

{
  "data": [
    {
      "id": "cat-uuid-1",
      "name": "Community",
      "description": "Local communities affected by operations",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    },
    {
      "id": "cat-uuid-2",
      "name": "Employees",
      "description": "Full-time and contract employees",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    },
    {
      "id": "cat-uuid-3",
      "name": "Suppliers",
      "description": "Supply chain partners and vendors",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    },
    {
      "id": "cat-uuid-4",
      "name": "Investors",
      "description": "Shareholders and financial stakeholders",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    }
  ],
  "pagination": {
    "totalItems": 4
  }
}

Error Responses:

Status Error Code Description
401 AUTH_TOKEN_INVALID Invalid or expired JWT token
403 AUTH_INSUFFICIENT_PERMISSIONS User lacks required role

POST /api/v1/stakeholder-engagement/categories

Create a new stakeholder category.

Authentication: Required (ADMIN role)

Request Body:

{
  "name": "Regulators",
  "description": "Government regulatory bodies and agencies"
}

Response (201 Created):

{
  "id": "cat-uuid-5",
  "name": "Regulators",
  "description": "Government regulatory bodies and agencies",
  "isActive": true,
  "createdAt": "2026-01-17T10:00:00Z",
  "updatedAt": "2026-01-17T10:00:00Z"
}

Error Responses:

Status Error Code Description
400 VALIDATION_ERROR Invalid request format or missing required fields
401 AUTH_TOKEN_INVALID Invalid or expired JWT token
403 AUTH_INSUFFICIENT_PERMISSIONS User lacks ADMIN role
409 RESOURCE_ALREADY_EXISTS Category with this name already exists

PATCH /api/v1/stakeholder-engagement/categories/{id}

Update or deactivate a stakeholder category.

Authentication: Required (ADMIN role)

Path Parameters:

Parameter Type Description
id UUID Category UUID

Request Body:

{
  "name": "Local Communities",
  "description": "Communities within 10km of operations",
  "isActive": true
}

Response (200 OK):

{
  "id": "cat-uuid-1",
  "name": "Local Communities",
  "description": "Communities within 10km of operations",
  "isActive": true,
  "createdAt": "2025-01-01T10:00:00Z",
  "updatedAt": "2026-01-17T10:15:00Z"
}


GET /api/v1/stakeholder-engagement/platforms

Fetch engagement platform master list (e.g., Community Meeting, Written Correspondence, Online Survey).

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

Query Parameters:

Parameter Type Required Description
includeInactive boolean No Include deactivated platforms (default: false)

Request Example:

GET /api/v1/stakeholder-engagement/platforms
Authorization: Bearer token...

Response (200 OK):

{
  "data": [
    {
      "id": "plat-uuid-1",
      "name": "Community Meeting",
      "description": "In-person community forums and town halls",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    },
    {
      "id": "plat-uuid-2",
      "name": "Written Correspondence",
      "description": "Letters, emails, and formal written communication",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    },
    {
      "id": "plat-uuid-3",
      "name": "Online Survey",
      "description": "Digital surveys and feedback forms",
      "isActive": true,
      "createdAt": "2025-01-01T10:00:00Z",
      "updatedAt": "2025-01-01T10:00:00Z"
    }
  ],
  "pagination": {
    "totalItems": 3
  }
}


POST /api/v1/stakeholder-engagement/platforms

Create a new engagement platform.

Authentication: Required (ADMIN role)

Request Body:

{
  "name": "Social Media Engagement",
  "description": "Engagement via Twitter, LinkedIn, and Facebook"
}

Response (201 Created):

{
  "id": "plat-uuid-4",
  "name": "Social Media Engagement",
  "description": "Engagement via Twitter, LinkedIn, and Facebook",
  "isActive": true,
  "createdAt": "2026-01-17T10:00:00Z",
  "updatedAt": "2026-01-17T10:00:00Z"
}


PATCH /api/v1/stakeholder-engagement/platforms/{id}

Update or deactivate an engagement platform.

Authentication: Required (ADMIN role)

Path Parameters:

Parameter Type Description
id UUID Platform UUID

Request Body:

{
  "name": "Public Consultation",
  "description": "Formal public consultation sessions",
  "isActive": false
}

Response (200 OK):

{
  "id": "plat-uuid-1",
  "name": "Public Consultation",
  "description": "Formal public consultation sessions",
  "isActive": false,
  "createdAt": "2025-01-01T10:00:00Z",
  "updatedAt": "2026-01-17T10:20:00Z"
}


Stakeholder Engagement Records

POST /api/v1/stakeholder-engagement/engagements

Create a new stakeholder engagement record. Supports idempotency to prevent duplicate submissions.

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:

{
  "engagementUuid": "eng-550e8400-e29b-41d4-a716-446655440000",
  "reportingPeriodId": "period-uuid",
  "organisationId": "org-uuid",
  "siteId": "site-uuid",
  "initialDate": "2026-01-15",
  "stakeholderCategories": ["cat-uuid-1", "cat-uuid-2"],
  "stakeholderName": "Local Community Leaders",
  "engagementPurpose": "Discuss environmental impact mitigation plans",
  "engagementPlatforms": ["plat-uuid-1", "plat-uuid-2"],
  "outcome": "Agreed on quarterly monitoring meetings",
  "responsiblePerson": "Jane Smith",
  "status": "Closed",
  "statusDate": "2026-01-15"
}

Field Descriptions:

Field Type Required Description
engagementUuid UUID Yes Client-generated UUID for deduplication
reportingPeriodId UUID Yes Target reporting period
organisationId UUID Yes Organisation responsible for engagement
siteId UUID No Site where engagement occurred (if applicable)
initialDate Date Yes Date when engagement began (ISO 8601)
stakeholderCategories Array[UUID] Yes Stakeholder category IDs (1 or more)
stakeholderName String Yes Name of stakeholder group/organization (max 255 chars)
engagementPurpose String Yes Purpose of engagement (max 1000 chars)
engagementPlatforms Array[UUID] Yes Engagement platform IDs (1 or more)
outcome String No Outcome of engagement (required if status=Closed, max 1000 chars)
responsiblePerson String Yes Person responsible for engagement (max 255 chars)
status String Yes Engagement status: Open, WIP, Closed
statusDate Date No Date when status was last updated

Response (201 Created):

{
  "id": "engagement-uuid",
  "engagementUuid": "eng-550e8400-e29b-41d4-a716-446655440000",
  "reportingPeriod": {
    "id": "period-uuid",
    "name": "FY2026",
    "startDate": "2026-01-01",
    "endDate": "2026-12-31"
  },
  "organisation": {
    "id": "org-uuid",
    "name": "Acme Corporation"
  },
  "site": {
    "id": "site-uuid",
    "name": "Factory A",
    "code": "FAC-A"
  },
  "initialDate": "2026-01-15",
  "stakeholderCategories": [
    {
      "id": "cat-uuid-1",
      "name": "Community"
    },
    {
      "id": "cat-uuid-2",
      "name": "Employees"
    }
  ],
  "stakeholderName": "Local Community Leaders",
  "engagementPurpose": "Discuss environmental impact mitigation plans",
  "engagementPlatforms": [
    {
      "id": "plat-uuid-1",
      "name": "Community Meeting"
    },
    {
      "id": "plat-uuid-2",
      "name": "Written Correspondence"
    }
  ],
  "outcome": "Agreed on quarterly monitoring meetings",
  "responsiblePerson": "Jane Smith",
  "status": "Closed",
  "statusDate": "2026-01-15",
  "createdAt": "2026-01-17T10:30:00Z",
  "updatedAt": "2026-01-17T10:30:00Z",
  "createdBy": {
    "id": "user-uuid",
    "name": "John Collector",
    "email": "john@example.com"
  },
  "evidenceCount": 0,
  "nextSteps": [
    "Upload evidence files (meeting minutes, attendance registers)",
    "Evidence is optional but recommended for audit trail"
  ]
}

Response (200 OK - Idempotent Retry):

If the same Idempotency-Key is used within 24 hours, the original 201 response is returned:

{
  "id": "engagement-uuid",
  "engagementUuid": "eng-550e8400-e29b-41d4-a716-446655440000",
  "status": "Closed",
  "createdAt": "2026-01-17T10:30:00Z",
  "message": "This engagement was already created (idempotent retry)"
}

Error Responses:

Status Error Code Description
400 VALIDATION_ERROR Invalid request format or missing required fields
401 AUTH_TOKEN_INVALID Invalid or expired JWT token
403 AUTH_SCOPE_VIOLATION User cannot submit to this organisation/site
404 RESOURCE_NOT_FOUND Reporting period, category, or platform not found
409 RESOURCE_ALREADY_EXISTS Duplicate engagementUuid (idempotency bypass)
409 RESOURCE_LOCKED Reporting period is locked
422 VALIDATION_RULE_FAILED Business rule violation

Error Example (422 - Validation Failed):

{
  "error": "VALIDATION_RULE_FAILED",
  "message": "Engagement submission failed validation",
  "timestamp": "2026-01-17T10:30:00Z",
  "request_id": "req_abc123",
  "details": [
    {
      "field": "outcome",
      "code": "FIELD_REQUIRED_WHEN_CLOSED",
      "message": "Outcome is required when status is Closed"
    },
    {
      "field": "stakeholderCategories",
      "code": "ARRAY_MIN_LENGTH",
      "message": "At least one stakeholder category is required"
    },
    {
      "field": "initialDate",
      "code": "DATE_OUT_OF_RANGE",
      "message": "Initial date must be within reporting period 2026-01-01 to 2026-12-31"
    }
  ]
}


GET /api/v1/stakeholder-engagement/engagements

Retrieve stakeholder engagement records 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
siteId UUID No Filter by site
status String No Filter by status (Open, WIP, Closed)
stakeholderCategoryId UUID No Filter by stakeholder category
engagementPlatformId UUID No Filter by engagement platform
initialDateFrom ISO 8601 No Filter engagements from this date
initialDateTo ISO 8601 No Filter engagements until this date
search String No Full-text search in purpose, outcome, and stakeholder name
page Integer No Page number (default: 1)
pageSize Integer No Items per page (default: 50, max: 100)
sort String No Sort field and direction (e.g., initialDate:desc)

Request Example:

GET /api/v1/stakeholder-engagement/engagements?reportingPeriodId=period-uuid&status=Open&page=1&pageSize=20
Authorization: Bearer token...

Response (200 OK):

{
  "data": [
    {
      "id": "engagement-uuid-1",
      "engagementUuid": "eng-550e8400-e29b-41d4-a716-446655440000",
      "reportingPeriod": {
        "id": "period-uuid",
        "name": "FY2026"
      },
      "organisation": {
        "id": "org-uuid",
        "name": "Acme Corporation"
      },
      "site": {
        "id": "site-uuid",
        "name": "Factory A"
      },
      "initialDate": "2026-01-15",
      "stakeholderCategories": [
        {
          "id": "cat-uuid-1",
          "name": "Community"
        }
      ],
      "stakeholderName": "Local Community Leaders",
      "engagementPurpose": "Discuss environmental impact mitigation plans",
      "engagementPlatforms": [
        {
          "id": "plat-uuid-1",
          "name": "Community Meeting"
        }
      ],
      "outcome": null,
      "responsiblePerson": "Jane Smith",
      "status": "Open",
      "statusDate": "2026-01-15",
      "evidenceCount": 0,
      "createdAt": "2026-01-17T10:30:00Z",
      "updatedAt": "2026-01-17T10:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalPages": 3,
    "totalItems": 47,
    "hasNext": true,
    "hasPrevious": false
  },
  "links": {
    "self": "/api/v1/stakeholder-engagement/engagements?reportingPeriodId=period-uuid&status=Open&page=1&pageSize=20",
    "next": "/api/v1/stakeholder-engagement/engagements?reportingPeriodId=period-uuid&status=Open&page=2&pageSize=20",
    "last": "/api/v1/stakeholder-engagement/engagements?reportingPeriodId=period-uuid&status=Open&page=3&pageSize=20"
  }
}


GET /api/v1/stakeholder-engagement/engagements/{id}

Retrieve detailed information about a specific engagement.

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

Path Parameters:

Parameter Type Description
id UUID Engagement UUID

Request Example:

GET /api/v1/stakeholder-engagement/engagements/engagement-uuid
Authorization: Bearer token...

Response (200 OK):

{
  "id": "engagement-uuid",
  "engagementUuid": "eng-550e8400-e29b-41d4-a716-446655440000",
  "reportingPeriod": {
    "id": "period-uuid",
    "name": "FY2026",
    "startDate": "2026-01-01",
    "endDate": "2026-12-31"
  },
  "organisation": {
    "id": "org-uuid",
    "name": "Acme Corporation"
  },
  "site": {
    "id": "site-uuid",
    "name": "Factory A",
    "code": "FAC-A"
  },
  "initialDate": "2026-01-15",
  "stakeholderCategories": [
    {
      "id": "cat-uuid-1",
      "name": "Community"
    },
    {
      "id": "cat-uuid-2",
      "name": "Employees"
    }
  ],
  "stakeholderName": "Local Community Leaders",
  "engagementPurpose": "Discuss environmental impact mitigation plans",
  "engagementPlatforms": [
    {
      "id": "plat-uuid-1",
      "name": "Community Meeting"
    },
    {
      "id": "plat-uuid-2",
      "name": "Written Correspondence"
    }
  ],
  "outcome": "Agreed on quarterly monitoring meetings",
  "responsiblePerson": "Jane Smith",
  "status": "Closed",
  "statusDate": "2026-01-15",
  "evidence": [
    {
      "id": "evidence-uuid",
      "filename": "meeting_minutes_jan2026.pdf",
      "evidenceType": "MEETING_MINUTES",
      "fileSize": 128540,
      "uploadedAt": "2026-01-17T10:35:00Z"
    }
  ],
  "createdAt": "2026-01-17T10:30:00Z",
  "updatedAt": "2026-01-17T10:30:00Z",
  "createdBy": {
    "id": "user-uuid",
    "name": "John Collector",
    "email": "john@example.com"
  }
}

Error Responses:

Status Error Code Description
401 AUTH_TOKEN_INVALID Invalid or expired JWT token
403 AUTH_SCOPE_VIOLATION User cannot access this engagement
404 RESOURCE_NOT_FOUND Engagement not found

PATCH /api/v1/stakeholder-engagement/engagements/{id}

Update an existing stakeholder engagement record. Engagement records can be updated at any time (no DRAFT state required).

Authentication: Required (COLLECTOR, REVIEWER, ADMIN roles)

Path Parameters:

Parameter Type Description
id UUID Engagement UUID

Request Body:

{
  "status": "Closed",
  "statusDate": "2026-01-20",
  "outcome": "Agreement reached on mitigation measures"
}

Response (200 OK):

{
  "id": "engagement-uuid",
  "engagementUuid": "eng-550e8400-e29b-41d4-a716-446655440000",
  "status": "Closed",
  "statusDate": "2026-01-20",
  "outcome": "Agreement reached on mitigation measures",
  "updatedAt": "2026-01-20T14:00:00Z",
  "message": "Engagement updated successfully"
}

Error Responses:

Status Error Code Description
400 VALIDATION_ERROR Invalid request format
401 AUTH_TOKEN_INVALID Invalid or expired JWT token
403 AUTH_SCOPE_VIOLATION User cannot modify this engagement
404 RESOURCE_NOT_FOUND Engagement not found
409 RESOURCE_LOCKED Reporting period is locked
422 VALIDATION_RULE_FAILED Updated value fails validation

Grievance Records

POST /api/v1/stakeholder-engagement/grievances

Create a new grievance record. Supports idempotency to prevent duplicate submissions.

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:

{
  "grievanceUuid": "grv-660e8400-e29b-41d4-a716-446655440000",
  "reportingPeriodId": "period-uuid",
  "organisationId": "org-uuid",
  "siteId": "site-uuid",
  "dateReported": "2026-01-10",
  "isInternal": false,
  "stakeholderGroup": "Local community members",
  "natureOfGrievance": "Concerns about air quality from factory emissions",
  "intervention": "Environmental assessment commissioned; community meeting scheduled",
  "resolutionStatus": "WIP"
}

Field Descriptions:

Field Type Required Description
grievanceUuid UUID Yes Client-generated UUID for deduplication
reportingPeriodId UUID Yes Target reporting period
organisationId UUID Yes Organisation where grievance was raised
siteId UUID No Site where grievance occurred (if applicable)
dateReported Date Yes Date grievance was reported (ISO 8601)
isInternal Boolean Yes Internal (employee) or external (community/supplier)
stakeholderGroup String Yes General stakeholder group (NOT individual names, max 255 chars)
natureOfGrievance String Yes Description of grievance (max 1000 chars)
intervention String No Action taken to address grievance (max 1000 chars)
resolutionStatus String Yes Resolution status: Open, WIP, Closed

Response (201 Created):

{
  "id": "grievance-uuid",
  "grievanceUuid": "grv-660e8400-e29b-41d4-a716-446655440000",
  "reportingPeriod": {
    "id": "period-uuid",
    "name": "FY2026",
    "startDate": "2026-01-01",
    "endDate": "2026-12-31"
  },
  "organisation": {
    "id": "org-uuid",
    "name": "Acme Corporation"
  },
  "site": {
    "id": "site-uuid",
    "name": "Factory A",
    "code": "FAC-A"
  },
  "dateReported": "2026-01-10",
  "isInternal": false,
  "stakeholderGroup": "Local community members",
  "natureOfGrievance": "Concerns about air quality from factory emissions",
  "intervention": "Environmental assessment commissioned; community meeting scheduled",
  "resolutionStatus": "WIP",
  "createdAt": "2026-01-17T11:00:00Z",
  "updatedAt": "2026-01-17T11:00:00Z",
  "createdBy": {
    "id": "user-uuid",
    "name": "John Collector",
    "email": "john@example.com"
  },
  "evidenceCount": 0,
  "nextSteps": [
    "Upload evidence files (grievance forms, correspondence)",
    "Evidence is optional but recommended for audit trail"
  ]
}

Error Responses:

Status Error Code Description
400 VALIDATION_ERROR Invalid request format or missing required fields
401 AUTH_TOKEN_INVALID Invalid or expired JWT token
403 AUTH_SCOPE_VIOLATION User cannot submit to this organisation/site
404 RESOURCE_NOT_FOUND Reporting period not found
409 RESOURCE_ALREADY_EXISTS Duplicate grievanceUuid (idempotency bypass)
409 RESOURCE_LOCKED Reporting period is locked
422 VALIDATION_PII_DETECTED stakeholderGroup contains individual names (PII violation)

Error Example (422 - PII Detected):

{
  "error": "VALIDATION_PII_DETECTED",
  "message": "Grievance submission contains potential PII",
  "timestamp": "2026-01-17T11:00:00Z",
  "request_id": "req_def456",
  "details": [
    {
      "field": "stakeholderGroup",
      "code": "PII_INDIVIDUAL_NAME_DETECTED",
      "message": "Use general group labels (e.g., 'Local community members') instead of individual names to protect privacy"
    }
  ]
}


GET /api/v1/stakeholder-engagement/grievances

Retrieve grievance records 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
siteId UUID No Filter by site
resolutionStatus String No Filter by status (Open, WIP, Closed)
isInternal Boolean No Filter by internal/external
dateReportedFrom ISO 8601 No Filter grievances from this date
dateReportedTo ISO 8601 No Filter grievances until this date
search String No Full-text search in nature of grievance and intervention
page Integer No Page number (default: 1)
pageSize Integer No Items per page (default: 50, max: 100)
sort String No Sort field and direction (e.g., dateReported:desc)

Request Example:

GET /api/v1/stakeholder-engagement/grievances?reportingPeriodId=period-uuid&resolutionStatus=Open&page=1&pageSize=20
Authorization: Bearer token...

Response (200 OK):

{
  "data": [
    {
      "id": "grievance-uuid-1",
      "grievanceUuid": "grv-660e8400-e29b-41d4-a716-446655440000",
      "reportingPeriod": {
        "id": "period-uuid",
        "name": "FY2026"
      },
      "organisation": {
        "id": "org-uuid",
        "name": "Acme Corporation"
      },
      "site": {
        "id": "site-uuid",
        "name": "Factory A"
      },
      "dateReported": "2026-01-10",
      "isInternal": false,
      "stakeholderGroup": "Local community members",
      "natureOfGrievance": "Concerns about air quality from factory emissions",
      "intervention": "Environmental assessment commissioned; community meeting scheduled",
      "resolutionStatus": "WIP",
      "evidenceCount": 1,
      "createdAt": "2026-01-17T11:00:00Z",
      "updatedAt": "2026-01-17T11:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalPages": 2,
    "totalItems": 23,
    "hasNext": true,
    "hasPrevious": false
  },
  "links": {
    "self": "/api/v1/stakeholder-engagement/grievances?reportingPeriodId=period-uuid&resolutionStatus=Open&page=1&pageSize=20",
    "next": "/api/v1/stakeholder-engagement/grievances?reportingPeriodId=period-uuid&resolutionStatus=Open&page=2&pageSize=20",
    "last": "/api/v1/stakeholder-engagement/grievances?reportingPeriodId=period-uuid&resolutionStatus=Open&page=2&pageSize=20"
  }
}


GET /api/v1/stakeholder-engagement/grievances/{id}

Retrieve detailed information about a specific grievance.

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

Path Parameters:

Parameter Type Description
id UUID Grievance UUID

Request Example:

GET /api/v1/stakeholder-engagement/grievances/grievance-uuid
Authorization: Bearer token...

Response (200 OK):

{
  "id": "grievance-uuid",
  "grievanceUuid": "grv-660e8400-e29b-41d4-a716-446655440000",
  "reportingPeriod": {
    "id": "period-uuid",
    "name": "FY2026",
    "startDate": "2026-01-01",
    "endDate": "2026-12-31"
  },
  "organisation": {
    "id": "org-uuid",
    "name": "Acme Corporation"
  },
  "site": {
    "id": "site-uuid",
    "name": "Factory A",
    "code": "FAC-A"
  },
  "dateReported": "2026-01-10",
  "isInternal": false,
  "stakeholderGroup": "Local community members",
  "natureOfGrievance": "Concerns about air quality from factory emissions",
  "intervention": "Environmental assessment commissioned; community meeting scheduled",
  "resolutionStatus": "WIP",
  "evidence": [
    {
      "id": "evidence-uuid",
      "filename": "grievance_form_jan2026.pdf",
      "evidenceType": "GRIEVANCE_FORM",
      "fileSize": 95240,
      "uploadedAt": "2026-01-17T11:05:00Z"
    }
  ],
  "createdAt": "2026-01-17T11:00:00Z",
  "updatedAt": "2026-01-17T11:00:00Z",
  "createdBy": {
    "id": "user-uuid",
    "name": "John Collector",
    "email": "john@example.com"
  }
}


PATCH /api/v1/stakeholder-engagement/grievances/{id}

Update an existing grievance record (e.g., update intervention or resolution status).

Authentication: Required (COLLECTOR, REVIEWER, ADMIN roles)

Path Parameters:

Parameter Type Description
id UUID Grievance UUID

Request Body:

{
  "intervention": "Environmental assessment completed; mitigation measures implemented",
  "resolutionStatus": "Closed"
}

Response (200 OK):

{
  "id": "grievance-uuid",
  "grievanceUuid": "grv-660e8400-e29b-41d4-a716-446655440000",
  "intervention": "Environmental assessment completed; mitigation measures implemented",
  "resolutionStatus": "Closed",
  "updatedAt": "2026-01-25T10:00:00Z",
  "message": "Grievance updated successfully"
}


Evidence Management

POST /api/v1/stakeholder-engagement/engagements/{id}/evidence

Link uploaded evidence to an engagement. Evidence must be uploaded first via POST /api/v1/collector/evidence (see Collector API).

Authentication: Required (COLLECTOR role)

Path Parameters:

Parameter Type Description
id UUID Engagement UUID

Request Body:

{
  "evidenceId": "evidence-uuid"
}

Response (200 OK):

{
  "engagementId": "engagement-uuid",
  "evidenceId": "evidence-uuid",
  "linkedAt": "2026-01-17T10:40:00Z",
  "message": "Evidence linked successfully"
}

Error Responses:

Status Error Code Description
403 AUTH_SCOPE_VIOLATION User cannot modify this engagement
404 RESOURCE_NOT_FOUND Engagement or evidence not found
409 RESOURCE_ALREADY_EXISTS Evidence already linked to this engagement

POST /api/v1/stakeholder-engagement/grievances/{id}/evidence

Link uploaded evidence to a grievance. Evidence must be uploaded first via POST /api/v1/collector/evidence.

Authentication: Required (COLLECTOR role)

Path Parameters:

Parameter Type Description
id UUID Grievance UUID

Request Body:

{
  "evidenceId": "evidence-uuid"
}

Response (200 OK):

{
  "grievanceId": "grievance-uuid",
  "evidenceId": "evidence-uuid",
  "linkedAt": "2026-01-17T11:10:00Z",
  "message": "Evidence linked successfully"
}


Report Export

POST /api/v1/stakeholder-engagement/reports/export

Generate and download stakeholder engagement report in CSV or XLSX format. Includes GRI 2-29 engagement logs and grievance mechanism data.

Authentication: Required (REVIEWER, APPROVER, ADMIN roles)

Request Body:

{
  "reportingPeriodId": "period-uuid",
  "organisationId": "org-uuid",
  "siteId": "site-uuid",
  "format": "XLSX",
  "includeEngagements": true,
  "includeGrievances": true,
  "filterStatus": ["Open", "WIP", "Closed"]
}

Field Descriptions:

Field Type Required Description
reportingPeriodId UUID Yes Reporting period to export
organisationId UUID No Filter by organisation (default: all accessible)
siteId UUID No Filter by site (default: all accessible)
format String Yes Export format: CSV or XLSX
includeEngagements Boolean Yes Include engagement log
includeGrievances Boolean Yes Include grievance log
filterStatus Array[String] No Filter by status (default: all statuses)

Response (202 Accepted):

{
  "jobId": "export-job-uuid",
  "status": "QUEUED",
  "message": "Export job queued for processing",
  "estimatedCompletionTime": "2-5 minutes",
  "statusUrl": "/api/v1/stakeholder-engagement/reports/export/export-job-uuid"
}

Check Export Status:

GET /api/v1/stakeholder-engagement/reports/export/{jobId}
Authorization: Bearer token...

Response (200 OK - In Progress):

{
  "jobId": "export-job-uuid",
  "status": "PROCESSING",
  "progress": 45,
  "message": "Generating XLSX report"
}

Response (200 OK - Completed):

{
  "jobId": "export-job-uuid",
  "status": "COMPLETED",
  "downloadUrl": "/api/v1/stakeholder-engagement/reports/export/export-job-uuid/download",
  "expiresAt": "2026-01-18T11:00:00Z",
  "fileSize": 458720,
  "recordCounts": {
    "engagements": 47,
    "grievances": 23
  }
}

Download Export:

GET /api/v1/stakeholder-engagement/reports/export/{jobId}/download
Authorization: Bearer token...

Response (200 OK):

Returns the file binary data with appropriate headers:

HTTP/1.1 200 OK
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="stakeholder_engagement_FY2026.xlsx"
Content-Length: 458720

[binary file data]

XLSX Structure:

The XLSX export contains multiple sheets: 1. Categories & Platforms: Master lists of stakeholder categories and engagement platforms 2. Engagements: Complete stakeholder engagement log 3. Grievances: Complete grievance mechanism log

CSV Structure:

The CSV export generates separate files in a ZIP archive: - stakeholder_categories.csv - engagement_platforms.csv - stakeholder_engagements.csv - grievance_log.csv


Filtering and Pagination

All collection endpoints support filtering via query parameters and pagination. See API Overview - Pagination for general conventions.

Stakeholder Engagement Specific Filters:

Filter Applies To Description
reportingPeriodId Engagements, Grievances Filter by reporting period
organisationId Engagements, Grievances Filter by organisation
siteId Engagements, Grievances Filter by site
status Engagements Filter engagement status (Open, WIP, Closed)
resolutionStatus Grievances Filter grievance status (Open, WIP, Closed)
stakeholderCategoryId Engagements Filter by stakeholder category
engagementPlatformId Engagements Filter by engagement platform
isInternal Grievances Filter internal/external grievances
initialDateFrom/To Engagements Filter by engagement date range
dateReportedFrom/To Grievances Filter by reporting date range

Error Handling

All error responses follow the standard error format defined in Error Handling.

Stakeholder Engagement Specific Errors

PII Protection Error:

{
  "error": "VALIDATION_PII_DETECTED",
  "message": "Submission contains potential PII",
  "timestamp": "2026-01-17T11:00:00Z",
  "request_id": "req_ghi789",
  "details": [
    {
      "field": "stakeholderGroup",
      "code": "PII_INDIVIDUAL_NAME_DETECTED",
      "message": "Use general group labels instead of individual names"
    }
  ]
}

Business Rule Violation:

{
  "error": "VALIDATION_RULE_FAILED",
  "message": "Engagement validation failed",
  "timestamp": "2026-01-17T10:30:00Z",
  "request_id": "req_jkl012",
  "details": [
    {
      "field": "outcome",
      "code": "FIELD_REQUIRED_WHEN_CLOSED",
      "message": "Outcome is required when status is Closed"
    }
  ]
}

PII Protection

PII Handling Requirements

Stakeholder Name (Engagements): - Use organization/group names (e.g., "Local Community Leaders", "Employee Union Representatives") - Avoid individual names where possible - If individuals must be named, ensure proper consent and data protection compliance

Stakeholder Group (Grievances): - MUST use general group labels (e.g., "Local community members", "Factory employees") - NEVER use individual names - PII validation enforced at API level with VALIDATION_PII_DETECTED error

Pattern Detection:

The API performs basic PII detection on the stakeholderGroup field in grievances: - Rejects common name patterns (e.g., "John Smith", "Mr. Ahmed") - Suggests using general group labels instead

Example Acceptable Values: - ✅ "Local community members" - ✅ "Factory shift workers" - ✅ "Supplier representatives" - ✅ "Anonymous complainant"

Example Rejected Values: - ❌ "John Smith" - ❌ "Ms. Jane Doe" - ❌ "Ahmed Al-Rashid"

GDPR/Privacy Compliance:

  • Individual names in grievances violate privacy principles
  • Use role-based or group-based identification
  • Evidence files may contain PII but are access-controlled via RBAC
  • Audit logs track all access to grievance records

Implementation Guide

Backend Implementation Notes

Quarkus JAX-RS Resource Example:

import jakarta.inject.Inject
import jakarta.validation.Valid
import jakarta.ws.rs.*
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import io.quarkus.security.identity.SecurityIdentity
import java.util.UUID

@Path("/api/v1/stakeholder-engagement")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class StakeholderEngagementResource @Inject constructor(
    private val categoryService: StakeholderCategoryService,
    private val engagementService: StakeholderEngagementService,
    private val grievanceService: GrievanceService,
    private val validationService: ValidationService,
    private val securityIdentity: SecurityIdentity
) {

    @GET
    @Path("/categories")
    fun getCategories(
        @QueryParam("includeInactive") includeInactive: Boolean = false
    ): Response {
        val user = securityIdentity.principal as AuthUser

        val categories = categoryService.findAll(
            tenantId = user.tenantId,
            includeInactive = includeInactive
        )

        return Response.ok(categories.toResponse()).build()
    }

    @POST
    @Path("/engagements")
    @RolesAllowed("COLLECTOR")
    fun createEngagement(
        @HeaderParam("Idempotency-Key") idempotencyKey: String,
        @Valid request: CreateEngagementRequest
    ): Response {
        val user = securityIdentity.principal as AuthUser

        // Check idempotency
        val existing = engagementService.findByIdempotencyKey(idempotencyKey)
        if (existing != null) {
            return Response.ok(existing.toResponse()).build()
        }

        // Validate user has access to organisation/site
        if (!user.hasAccessToOrganisation(request.organisationId)) {
            throw AuthScopeViolationException("Cannot submit to this organisation")
        }

        // Create engagement
        val engagement = engagementService.create(
            request = request,
            userId = user.id,
            tenantId = user.tenantId,
            idempotencyKey = idempotencyKey
        )

        return Response.status(201).entity(engagement.toResponse()).build()
    }

    @POST
    @Path("/grievances")
    @RolesAllowed("COLLECTOR")
    fun createGrievance(
        @HeaderParam("Idempotency-Key") idempotencyKey: String,
        @Valid request: CreateGrievanceRequest
    ): Response {
        val user = securityIdentity.principal as AuthUser

        // Check idempotency
        val existing = grievanceService.findByIdempotencyKey(idempotencyKey)
        if (existing != null) {
            return Response.ok(existing.toResponse()).build()
        }

        // PII validation
        if (validationService.containsPII(request.stakeholderGroup)) {
            throw ValidationPIIDetectedException(
                field = "stakeholderGroup",
                message = "Use general group labels instead of individual names"
            )
        }

        // Validate user has access to organisation/site
        if (!user.hasAccessToOrganisation(request.organisationId)) {
            throw AuthScopeViolationException("Cannot submit to this organisation")
        }

        // Create grievance
        val grievance = grievanceService.create(
            request = request,
            userId = user.id,
            tenantId = user.tenantId,
            idempotencyKey = idempotencyKey
        )

        return Response.status(201).entity(grievance.toResponse()).build()
    }
}

PII Validation Service:

import java.util.regex.Pattern

class ValidationService {

    private val namePatterns = listOf(
        Pattern.compile("\\b[A-Z][a-z]+ [A-Z][a-z]+\\b"), // "John Smith"
        Pattern.compile("\\bMr\\.? [A-Z][a-z]+"), // "Mr. Ahmed"
        Pattern.compile("\\bMs\\.? [A-Z][a-z]+"), // "Ms. Jane"
        Pattern.compile("\\bMrs\\.? [A-Z][a-z]+"), // "Mrs. Smith"
        Pattern.compile("\\bDr\\.? [A-Z][a-z]+")  // "Dr. Johnson"
    )

    fun containsPII(stakeholderGroup: String): Boolean {
        return namePatterns.any { pattern ->
            pattern.matcher(stakeholderGroup).find()
        }
    }
}

Cross-References


Change Log

Version Date Author Changes
1.0 2026-01-17 Ralph (AI Agent) Initial stakeholder engagement API documentation for GRI 2-29 reporting