Widget Upload API
Overview
The widget sends SDAC workbook uploads to its same-origin backend at /api/mastra/sdac/upload. The backend proxies to Mastra, and Mastra submits the workbook to the ingestion worker internally.
Do not point browser clients at the ingestion worker. The ingestion worker is an internal parser surface; Mastra owns upload status, report analysis startup, and the durable job record.
Authentication and Access Model
The widget backend should be protected by the normal widget/APIM authentication controls. It forwards calls to Mastra server-to-server.
- Browser clients call
/api/mastra/sdac/upload. - Mastra calls the ingestion worker with service-to-service credentials.
- The ingestion worker should not be exposed directly to untrusted browser clients.
Request
Use multipart/form-data.
| Attribute | Value |
|---|---|
| Endpoint | POST /api/mastra/sdac/upload |
| Response | JSON |
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Excel workbook in .xlsx or .xls format |
user_email | string | Yes | Email of the uploader |
user_name | string | Yes | Display name of the uploader |
district | string | Yes | District or organization label |
force_reingest | boolean | No | Force a new ingest even if the file hash already exists |
Response Format
The route renders HTML only when the request explicitly prefers text/html. Browser fetch() calls and widget integrations receive JSON by default.
Accepted Response
{
"status": "processing",
"jobId": "11111111-1111-4111-8111-111111111111",
"stage": "processing_file",
"message": "The ingestion worker accepted the file and is parsing it now.",
"statusUrl": "/sdac/uploads/11111111-1111-4111-8111-111111111111/status",
"largeFile": false,
"stalled": false
}
statusUrl is Mastra-relative. Browser/widget clients should not fetch it
directly; poll /api/mastra/sdac/uploads/{jobId}/status on the widget origin.
Once the status response includes reportId, the widget polls
/api/mastra/sdac/report-analysis/{reportId}/status until report analysis
completes.
Duplicate Response
{
"status": "duplicate",
"report_id": "8201EDC2-2EDE-4CA1-AF44-D0F5AA185CDB",
"record_count": 21,
"message": "This file has already been uploaded. You can use the existing report or re-ingest under a new ID.",
"can_reingest": true,
"existing_report": {
"district": "Maplewood Richmond Heights",
"quarter": "Q3",
"year": 2025,
"processed_at": "2026-03-29T18:42:11.000Z"
}
}
Error Response
{
"status": "error",
"report_id": "8201EDC2-2EDE-4CA1-AF44-D0F5AA185CDB",
"record_count": 0,
"message": "Processing failed: Validation failed"
}
HTTP Status Codes
| Status | Description |
|---|---|
| 200 | Immediate duplicate or completed upload response |
| 202 | Upload accepted; poll the returned job status URL |
| 400 | Missing file, invalid file extension, invalid workbook structure, or missing workbook data |
| 502 | Mastra could not submit to or refresh the ingestion worker |
| 500 | Unexpected Mastra upload failure |
Side Effects
- Successful uploads create
SDAC.ingestion_UploadJobs, insert parsed report rows through the ingestion worker, then start Mastra report analysis. - Duplicate uploads return the existing report reference and do not create a new report unless
force_reingestis set. - Failed uploads persist error state on the upload job when a job was created.