Authentication and Setup
Prerequisites
To call the K12 Safety API, you need:
| Credential | What It Is | Where to Get It |
|---|---|---|
| Client ID | Azure AD application identifier registered for K12 API access | Your Azure AD administrator provisions this when onboarding your application |
| Client Secret | Secret associated with the Client ID | Generated in the Azure AD app registration portal |
| Tenant ID | Azure AD directory (tenant) that contains your app registration | Found in the Azure AD portal under "Overview" |
| Subscription Key | API Management gateway key for the K12 endpoint | Issued when your application is registered in the API Management portal |
You will never send your Client Secret directly to the K12 API. It is only used to obtain an access token from Azure AD. The access token is what you include in API requests.
Authentication
The K12 API uses OAuth 2.0 client credentials for authorization and an API Management subscription key for gateway access. Both must be present on every request.
Step 1: Obtain an Access Token
Request a token from Azure Entra (Azure AD) using the client credentials grant:
curl -X POST "https://login.microsoftonline.com/ef3e1dbf-4cc9-4a06-bc23-71dd7a46fd1b/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}" \
-d "scope={OAUTH_SCOPE}" \
-d "grant_type=client_credentials"
The response includes an access_token field. Use that value in the Authorization header for all subsequent requests.
Token response:
{
"token_type": "Bearer",
"expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi..."
}
Tokens expire after approximately 1 hour. Your application should cache the token and refresh it before expiry.
Step 2: Include Required Headers
Every API call must include these three headers:
| Header | Value | Purpose |
|---|---|---|
Authorization | Bearer {access_token} | OAuth 2.0 bearer token from Step 1 |
Ocp-Apim-Subscription-Key | {subscription_key} | API Management gateway subscription key |
Content-Type | application/json | Request body format |
Environments
Each environment has its own gateway URL and OAuth scope. Use the scope that matches your target environment when requesting an access token.
- Development
| Attribute | Value |
|---|---|
| Base URL | https://dev-k12-aitools-t27p-apim.azure-api.net |
| OAuth Scope | api://dev-k12-aitools-t27p-api/.default |
All examples in this documentation use the Development base URL. Replace it with the appropriate URL for your target environment.
Endpoint Patterns
Legacy Tool Execute (Current)
All K12 tools are available through the tool execution endpoint:
POST {BASE_URL}/ai/api/tools/{tool-id}/execute
Request bodies wrap tool-specific inputs inside a data envelope:
{
"data": {
"field1": "value1",
"field2": "value2"
}
}
The response structure varies by tool but always returns JSON. Kickoff tools return tracking identifiers; status tools return progress information; result tools return the completed output.
Domain Routes (Alternative)
The K12 API also exposes domain-scoped routes with cleaner URLs and a flat request body (no data wrapper):
Tools:
POST {BASE_URL}/ai/k12/tools/{slug}
Workflows:
POST {BASE_URL}/ai/k12/workflows/{slug} -- Synchronous execution
POST {BASE_URL}/ai/k12/workflows/{slug}/start-async -- Async (returns runId)
GET {BASE_URL}/ai/k12/workflows/{slug}/runs/{runId} -- Poll run status
Key differences from the legacy endpoint:
Legacy (/ai/api/tools/.../execute) | Domain (/ai/k12/tools/...) | |
|---|---|---|
| Request body | { "data": { ... } } | { ... } (flat JSON) |
| Validation errors | 400 | 422 |
| URL style | Generic tool ID | Descriptive domain path |
Both endpoint patterns reach the same underlying tools and return the same results. The examples in this documentation use the legacy endpoint pattern, but domain routes can be used interchangeably by removing the data wrapper from request bodies.
Quick Start
This example demonstrates the full flow: obtain a token, then kick off an EOP editing job.
# 1. Get a token (replace placeholders with your credentials)
TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/ef3e1dbf-4cc9-4a06-bc23-71dd7a46fd1b/oauth2/v2.0/token" \
-d "client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&scope=api://dev-k12-aitools-t27p-api/.default&grant_type=client_credentials" \
| jq -r '.access_token')
# 2. Kick off an EOP editing job
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-annex-editing-kickoff/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"emergencyOperationPlanId": "plan-abc123",
"userId": "user-456",
"roleId": "safety-coordinator",
"form": {
"name": "Fire Safety Annex",
"field": {
"group": "Response Procedures",
"name": "evacuation_steps",
"content": "<p>Current evacuation procedures.</p>"
}
},
"prompt": "Add assembly point verification step"
}
}'
# 3. Expected response
# {
# "requestId": "req-7f3a...",
# "sessionId": "sess-a1b2...",
# "status": "accepted"
# }
# 4. Poll status until terminal (success/error/cancelled)
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-annex-editing-status/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestIds": ["req-7f3a..."]}}'
# 5. Fetch result once status is "success"
curl -X POST "https://dev-k12-aitools-t27p-apim.azure-api.net/ai/api/tools/k12-annex-editing-result/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "req-7f3a..."}}'
Standard Error Responses
| Status | Description |
|---|---|
| 400 | Bad Request -- missing required fields or invalid format |
| 401 | Unauthorized -- invalid or expired bearer token, or missing subscription key |
| 403 | Forbidden -- token lacks the required scope for this environment |
| 404 | Not Found -- tool ID or resource does not exist |
| 429 | Too Many Requests -- rate limit exceeded |
| 500 | Internal Server Error -- unexpected failure |