Skip to main content

Upload API

Overview

The SDAC upload contract is implemented by Mastra at POST /sdac/upload.

Some deployments may additionally expose the same contract through APIM under /ai/sdac/upload, but that alias is deployment-specific and should be verified in the target environment before you depend on it.

The upload flow registers a durable Mastra upload job, deduplicates by file hash, submits the workbook to an internal ingestion worker for parsing, and starts report analysis after parsing writes the report rows. Large files usually return 202 with a jobId; poll the upload status endpoint until reportId is available.

Authentication

When this route is exposed through APIM, call it with:

  • Authorization: Bearer {access_token}
  • Ocp-Apim-Subscription-Key: {subscription_key}

See Authentication for token acquisition and File Uploads for the route and deployment split.

Endpoints

EndpointMethodDescription
/sdac/uploadPOSTUploads and processes an SDAC workbook
/sdac/uploads/{jobId}/statusGETReturns durable upload progress and the parsed reportId when available
/sdac/report-analysis/{reportId}/statusGETReturns post-ingestion report-analysis workflow status

Upload Request

Use multipart/form-data.

FieldTypeRequiredDescription
filefileYesExcel workbook in .xlsx or .xls format
user_emailstringYesEmail of the uploader
user_namestringYesDisplay name of the uploader
districtstringYesDistrict or organization label for the upload
force_reingeststringNoSet to true to bypass duplicate-file detection and create a new report ID

Accepted Response

For normal async processing:

{
"status": "processing",
"jobId": "11111111-1111-4111-8111-111111111111",
"stage": "processing_file",
"message": "The ingestion worker accepted the file and is parsing it now.",
"fileName": "Q1-2026-district-costs.xlsx",
"fileSizeBytes": 328192,
"largeFile": false,
"statusUrl": "/sdac/uploads/11111111-1111-4111-8111-111111111111/status",
"stalled": false,
"stalledAfterSeconds": 120
}
FieldTypeDescription
jobIdUUID stringDurable Mastra upload job ID
statusstringprocessing, success, duplicate, or error
stagestringCurrent upload stage
messagestringHuman-readable progress message
statusUrlstringMastra-relative status polling URL; widget/browser clients should poll /api/mastra/sdac/uploads/{jobId}/status on the widget origin instead of fetching this value directly
largeFilebooleanWhether the upload is expected to take longer
stalledbooleanWhether no heartbeat has been observed for the configured timeout

Status Response

Server-side Mastra callers can poll GET /sdac/uploads/{jobId}/status until reportId appears. Widget/browser callers should use GET /api/mastra/sdac/uploads/{jobId}/status on the widget origin.

{
"status": "success",
"jobId": "11111111-1111-4111-8111-111111111111",
"reportId": "8201EDC2-2EDE-4CA1-AF44-D0F5AA185CDB",
"report_id": "8201EDC2-2EDE-4CA1-AF44-D0F5AA185CDB",
"stage": "parsed",
"analysisStatus": {
"status": "queued",
"runId": "run-001"
}
}

Once reportId appears, poll GET /sdac/report-analysis/{reportId}/status when the UI needs report-analysis progress or readiness.

Duplicate Upload Response

If the file hash already exists and force_reingest is not set:

{
"report_id": "8201EDC2-2EDE-4CA1-AF44-D0F5AA185CDB",
"status": "duplicate",
"record_count": 21,
"message": "This file has already been uploaded. You can use the existing report or re-ingest under a new ID.",
"existing_report": {
"district": "Maplewood Richmond Heights",
"quarter": "Q3",
"year": 2025,
"processed_at": "2026-03-29T18:42:11.000Z"
},
"can_reingest": true
}

Error Cases

StatusDescription
400Missing required form fields or invalid file type
401Missing or expired bearer token
403Invalid subscription key or insufficient scope
500Upload or processing failure

Processing failures may still return a JSON body that includes report_id, status: "error", and a message explaining the failure.

Side Effects

Successful uploads do more than allocate a report_id:

  1. Mastra creates SDAC.ingestion_UploadJobs
  2. the internal ingestion worker parses the workbook
  3. workbook rows are inserted into SDAC.data_CostReports and SDAC.data_PersonnelRecords
  4. Mastra starts sdac-report-analysis
  5. analysis results are persisted in SDAC.analysis_Results

If the workbook hash already exists and force_reingest is not set, the service returns the existing report reference instead of writing a new report.

Example

curl -X POST "$UPLOAD_URL" \
-F "file=@./Q1-2026-district-costs.xlsx" \
-F "user_email=auditor@state.gov" \
-F "user_name=Alex Auditor" \
-F "district=Maplewood Richmond Heights"

When calling through APIM, set UPLOAD_URL="$BASE_URL/ai/sdac/upload" and include the Authorization and Ocp-Apim-Subscription-Key headers. For direct server-to-server calls, set UPLOAD_URL to the deployed Mastra /sdac/upload origin. Browser/widget clients should use /api/mastra/sdac/upload on the widget origin instead.

Operational Notes

  • Only .xlsx and .xls uploads are accepted.
  • Duplicate detection is based on the uploaded file hash.
  • Use force_reingest=true only when you intentionally want a new report ID for a workbook that already exists.
  • After a successful upload, use the returned report_id with the Report Info API, Validation API, or Chat API.