Quick Start
Direct Gateway Flow
This is the shortest end-to-end path for a server-to-server integration that calls APIM directly for the routes that APIM is confirmed to expose.
1. Acquire a Token
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&scope={SCOPE}&grant_type=client_credentials" \
| jq -r '.access_token')
BASE_URL="https://dev-sdac-aitools-wkhc-apim.azure-api.net"
SDAC_API_PREFIX="${SDAC_API_PREFIX:-/sdac}" # set to /ai/sdac only when APIM publishes that alias
2. Upload a Workbook
UPLOAD_URL="$BASE_URL$SDAC_API_PREFIX/upload"
REPORT_ID=$(curl -s -X POST "$UPLOAD_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-F "file=@./Q1-2026-district-costs.xlsx" \
-F "user_email=auditor@state.gov" \
-F "user_name=Alex Auditor" \
-F "district=Maplewood Richmond Heights" \
| jq -r '.report_id')
The source-backed Mastra upload route is /sdac/upload. If your APIM product
publishes the deployment-specific /ai/sdac/upload alias, set
SDAC_API_PREFIX=/ai/sdac. Do not point browser code at the ingestion worker
directly.
3. Validate the Uploaded Report
curl -X POST "$BASE_URL$SDAC_API_PREFIX/validate" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d "{
\"reportId\": \"$REPORT_ID\",
\"forceRefresh\": false
}"
As with upload, set SDAC_API_PREFIX=/ai/sdac only when the target APIM
environment publishes that alias. The source-backed Mastra route is
POST /sdac/validate; browser/widget callers should use the widget same-origin
proxy route instead.
Widget-First Flow
Use this path when the browser should stay on the widget origin and let the widget server proxy to Mastra. Mastra then calls the ingestion worker service-to-service.
1. Acquire a Widget Access Token
The host application server should request the widget access token with the OAuth client-credentials flow. Do not mint this token in browser JavaScript.
WIDGET_TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
--data-urlencode "client_id={CLIENT_ID}" \
--data-urlencode "client_secret={CLIENT_SECRET}" \
--data-urlencode "scope={SCOPE}" \
| jq -r '.access_token')
The token must be issued for the configured widget/API audience and include the required access_as_application app role. The widget uses this token to authorize protected widget API calls; district, quarter, year, and user context still come from host page metadata.
2. Render District Context Metadata
<div
id="sdac-meta-data"
data-sdac='{
"districtId": "12345",
"districtName": "Maplewood Richmond Heights",
"quarter": "Q1",
"year": "2026",
"userId": "auditor-001",
"userName": "Alex Auditor",
"userEmail": "auditor@state.gov",
"userRole": "State Auditor"
}'>
</div>
3. Load the Widget Script
Render the widget script from the SDAC widget base URL for your environment and pass the access token to the widget bootstrap. A normal browser <script> tag cannot send custom Authorization headers, so the token is passed to the iframe and then sent as a bearer token on protected widget API calls.
<script
src="{WIDGET_BASE_URL}/widget/embed.js"
data-widget-token="{WIDGET_TOKEN}">
</script>
4. Let the Widget Bootstrap Session Context
The widget calls POST /api/ingestion/sdac/sessions, resolves the latest available report_id for the district when possible, and then uses the returned session_id plus any resolved report context for follow-up validation or chat.
For validation and chat, the widget uses POST /api/ingestion/sdac/validate
and POST /api/ingestion/sdac/chat from the widget origin.
For workbook uploads, the widget calls POST /api/mastra/sdac/upload, then
polls /api/mastra/sdac/uploads/{jobId}/status and
/api/mastra/sdac/report-analysis/{reportId}/status.