Admin API
Status: Final Version: 1.0 Last Updated: 2026-01-03
Purpose
Define REST endpoints for web dashboard users (Reviewers, Approvers, Admins) to manage configurations, review submissions, approve data, and generate reports.
Endpoints
1. Submission Management
GET /api/v1/admin/submissions
Fetch submissions for review.
Query Parameters:
- filter[state]: Filter by state (validated, processed, approved, rejected)
- filter[reporting_period_id]: Filter by period
- filter[site_id]: Filter by site
- sort: Sort field (created_at, updated_at, state)
Response (200):
{
"data": [
{
"id": 5001,
"submission_uuid": "550e8400-...",
"metric": {
"metric_id": "GRI_302_1_ELECTRICITY",
"name": "Electricity Consumption"
},
"site": {"id": 123, "name": "Factory A"},
"value": 1250.50,
"unit": "MWh",
"state": "VALIDATED",
"submitted_at": "2025-04-05T10:30:00Z",
"submitted_by": {"id": 42, "name": "John Collector"}
}
],
"meta": {"current_page": 1, "per_page": 50, "total": 500},
"links": {"next": "...?cursor=abc", "prev": null}
}
POST /api/v1/admin/submissions/{id}/approve
Approve a submission.
Request:
Response (200):
{
"id": 5001,
"state": "APPROVED",
"approved_by": {"id": 88, "name": "Jane Approver"},
"approved_at": "2025-04-10T14:00:00Z"
}
POST /api/v1/admin/submissions/{id}/reject
Reject a submission with feedback.
Request:
{
"reason": "Value appears too high; please verify and resubmit",
"required_corrections": ["Check meter reading", "Confirm unit conversion"]
}
Response (200):
{
"id": 5001,
"state": "REJECTED",
"reviewer_feedback": "Value appears too high; please verify and resubmit"
}
2. Reporting Period Management
GET /api/v1/admin/reporting-periods
Response (200):
{
"data": [
{
"id": 10,
"name": "FY2025",
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"state": "OPEN",
"completion_percentage": 65.5,
"submissions_count": {"total": 500, "approved": 327, "pending": 173}
}
]
}
POST /api/v1/admin/reporting-periods/{id}/lock
Lock a reporting period (requires Approver role).
Request:
Response (200):
{
"id": 10,
"state": "LOCKED",
"locked_at": "2025-04-15T16:00:00Z",
"locked_by": {"id": 88, "name": "Jane Approver"},
"content_hash": "sha256:def456..."
}
3. Metric Catalog Management (Admin Only)
GET /api/v1/admin/metrics
Response (200):
{
"data": [
{
"metric_id": "GRI_302_1_ELECTRICITY",
"name": "Electricity Consumption",
"unit": "MWh",
"is_mandatory": true,
"disclosure": {
"code": "302-1",
"name": "Energy consumption within the organization"
}
}
]
}
POST /api/v1/admin/metrics
Create custom KPI.
Request:
{
"metric_id": "CUSTOM_WATER_RECYCLING",
"name": "Water Recycling Rate",
"unit": "%",
"data_type": "numeric",
"is_custom_kpi": true,
"dimensionality": "site",
"validation_rules": [
{"type": "domain", "rule": "min", "value": 0},
{"type": "domain", "rule": "max", "value": 100}
]
}
4. Report Generation
POST /api/v1/admin/reports/generate
Request:
{
"reporting_period_id": 10,
"report_type": "gri_sustainability_report",
"format": "pdf",
"include_sections": ["environmental", "social", "governance"]
}
Response (202 Accepted):
{
"job_id": "job_abc123",
"status": "queued",
"estimated_completion": "2025-04-15T16:30:00Z",
"status_url": "/api/v1/admin/reports/jobs/job_abc123"
}
GET /api/v1/admin/reports/jobs/{job_id}
Response (200):
{
"job_id": "job_abc123",
"status": "completed",
"download_url": "/api/v1/admin/reports/download/report_xyz.pdf",
"expires_at": "2025-04-22T16:30:00Z"
}
5. Audit Log
GET /api/v1/admin/audit-logs
Query Parameters:
- filter[entity_type]: Filter by entity (MetricSubmission, ReportingPeriod)
- filter[action]: Filter by action (created, approved, locked)
- filter[actor_user_id]: Filter by user
Response (200):
{
"data": [
{
"id": 9001,
"actor": {"id": 88, "name": "Jane Approver"},
"action": "submission.approved",
"entity_type": "MetricSubmission",
"entity_id": 5001,
"before_state": {"state": "PROCESSED"},
"after_state": {"state": "APPROVED"},
"justification": "Data verified",
"created_at": "2025-04-10T14:00:00Z"
}
]
}
Laravel Controllers
class AdminSubmissionController extends Controller
{
public function index(Request $request)
{
$this->authorize('admin.submissions.view');
return MetricSubmission::query()
->filter($request->only(['state', 'reporting_period_id', 'site_id']))
->with(['metric', 'site', 'submittedBy'])
->orderBy($request->sort ?? '-created_at')
->cursorPaginate(50);
}
public function approve(Request $request, MetricSubmission $submission)
{
$this->authorize('approve', $submission);
$submission->approve(auth()->user(), $request->comment);
event(new SubmissionApproved($submission));
return response()->json($submission);
}
public function reject(Request $request, MetricSubmission $submission)
{
$this->authorize('reject', $submission);
$submission->reject($request->reason, $request->required_corrections);
event(new SubmissionRejected($submission));
return response()->json($submission);
}
}
class AdminReportingPeriodController extends Controller
{
public function lock(Request $request, ReportingPeriod $period)
{
$this->authorize('lock', $period);
if ($period->hasUnreviewedSubmissions()) {
throw new StatePrerequisiteException('All submissions must be reviewed before locking');
}
$period->lock(auth()->user(), $request->justification);
return response()->json($period);
}
}
Acceptance Criteria
- All endpoints enforce role-based authorization
- Submission approval creates audit log entry
- Period locking generates content hash
- Report generation queued as background job
- Audit log immutable (append-only)
Cross-References
Change Log
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-03 | Senior Product Architect | Initial Admin API specification |