Skip to main content

K12 Implementation Guide

This guide walks through integrating with the K12 Safety API and its three workflow families: EOP Editing, QRG Generation, and QRG Editing.


Authentication

The K12 API requires two authentication mechanisms on every request.

OAuth 2.0 Client Credentials

Obtain an access token from Azure Entra ID:

TOKEN=$(curl -s -X POST \
"https://login.microsoftonline.com/ef3e1dbf-4cc9-4a06-bc23-71dd7a46fd1b/oauth2/v2.0/token" \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}" \
-d "scope=api://dev-k12-aitools-t27p-api/.default" \
-d "grant_type=client_credentials" \
| jq -r '.access_token')

Required Headers

Every API call must include:

HeaderValue
AuthorizationBearer {access_token}
Ocp-Apim-Subscription-Key{subscription_key}
Content-Typeapplication/json

Environment URLs

EnvironmentBase URLOAuth Scope
Developmenthttps://dev-k12-aitools-t27p-apim.azure-api.netapi://dev-k12-aitools-t27p-api/.default
note

Replace {CLIENT_ID} and {CLIENT_SECRET} with your credentials. These will be provided separately by your administrator.


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 in a data envelope:

{
"data": {
"field1": "value1",
"field2": "value2"
}
}

Domain Routes (Alternative)

The API also exposes domain-scoped routes with cleaner URLs and flat request bodies (no data wrapper):

POST {BASE_URL}/ai/k12/tools/{slug}                    -- Tool execution (direct JSON)
POST {BASE_URL}/ai/k12/workflows/{slug} -- Synchronous workflow
POST {BASE_URL}/ai/k12/workflows/{slug}/start-async -- Async workflow (returns runId)
GET {BASE_URL}/ai/k12/workflows/{slug}/runs/{runId} -- Poll workflow run status
Legacy (/ai/api/tools/.../execute)Domain (/ai/k12/tools/...)
Request body{ "data": { ... } }{ ... } (flat JSON)
Validation errors400422
URL styleGeneric tool IDDescriptive domain path
note

Both patterns reach the same underlying tools. The examples in this guide use the legacy endpoint. To use domain routes, drop the data wrapper and replace the tool slug (e.g., k12-qrg-generation-kickoff becomes /ai/k12/tools/qrg-generation-kickoff).


EOP Editing

EOP (Emergency Operation Plan) editing enables AI-assisted revision of functional annex sections.

Workflow

Kickoff

curl -X POST "${BASE_URL}/ai/api/tools/k12-functional-annex-kickoff/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"emergencyOperationPlan": {"id": "d4e5f6a7-b8c9-0123-d456-e78901234567", "organizationId": "c3d4e5f6-a7b8-9012-cdef-345678901234"},
"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 after building clearance"
}
}'

Kickoff Fields

FieldRequiredDescription
emergencyOperationPlanIdConditionalEOP identifier. Organization context still requires emergencyOperationPlan.organizationId
emergencyOperationPlanConditionalFrontend-provided EOP object. Its id, emergencyOperationPlanId, planId, or eopId is normalized as the EOP identifier; organizationId is used directly for district/organization details
emergencyOperationPlanConfigurationIdConditionalPre-EOP configuration ID. Organization context still requires emergencyOperationPlanConfiguration.organizationId; mutually exclusive with plan and Master Copy identifiers
emergencyOperationPlanConfigurationConditionalFrontend-provided configuration object. Its ID aliases are normalized as the configuration identifier; organizationId is used directly for district/organization details
eopMasterCopyIdConditionalMaster Copy identifier for pre-EOP template form editing. Organization context still requires emergencyOperationPlanMasterCopy.organizationId
emergencyOperationPlanMasterCopyConditionalFrontend-provided Master Copy object. Saved objects may provide an ID alias; organizationId is used directly for district/organization details
userIdYesRequesting user identifier
roleIdNoOptional role identifier retained for traceability
userTagIdsNoAlias for selected role/tag IDs. May be an empty array while frontend tag filling is pending
roles, userTags, generalUserTagsNoFrontend-provided role/tag objects or names. These override backend-resolved role tags for prompt context when present
form.field.contentYesCurrent annex content (HTML). Empty string for prompt-only edits
promptNoInstructions describing the desired changes
sessionIdNoCorrelation ID (auto-generated if omitted)
waitForResultNotrue to block until complete (default: false)

Poll Status

curl -X POST "${BASE_URL}/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..."]}}'

Fetch Result

curl -X POST "${BASE_URL}/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..."}}'

Result Fields

FieldDescription
requestIdTracking ID
sessionIdSession this job belongs to
statussuccess or error
messageEdited content (HTML) or error description
isEditWhether the content was modified

Cancel

curl -X POST "${BASE_URL}/ai/api/tools/k12-cancel-task-request/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "req-7f3a..."}}'

QRG Generation

QRG (Quick Reference Guide) generation transforms an entire EOP into role-specific action guides with BEFORE, DURING, and AFTER phases.

Pipeline

For each annex in the EOP, the system runs four AI steps:

Kickoff

curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-generation-kickoff/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"emergencyOperationPlanId": "d4e5f6a7-b8c9-0123-d456-e78901234567",
"generalUserTagIds": ["a1b2c3d4-e5f6-7890-abcd-100000000001"],
"sessionId": null,
"waitForResult": false
}
}'

Kickoff Fields

FieldTypeRequiredDescription
emergencyOperationPlanIdstring (UUID)YesEOP to generate QRGs from
generalUserTagIdsarray of UUIDNoFilter to specific role tags (all roles used if omitted)
sessionIdstringNoCorrelation ID (auto-generated if null)
waitForResultbooleanNoBlock until complete (default: false)

Poll and Fetch

# Poll status
curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-generation-status/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "a1b2c3d4-..."}}'

# Fetch result (once status is SUCCESS)
curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-generation-result/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "a1b2c3d4-..."}}'

Result Structure

Each generated QRG contains:

{
"name": "Fire Safety - Teacher",
"organizationId": "c3d4e5f6-...",
"type": "EMERGENCY",
"hazard": "FIRE",
"generalUserTagManagementType": "ASSIGN_SELECTED",
"generalUserTagIds": ["a1b2c3d4-..."],
"contentBlocks": [
{ "type": "BEFORE", "content": "<ul><li>Know your evacuation routes</li>...</ul>" },
{ "type": "DURING", "content": "<ul><li>Alert students calmly</li>...</ul>" },
{ "type": "AFTER", "content": "<ul><li>Account for all students</li>...</ul>" }
]
}

The result also includes aiStats with per-step success/error counts for diagnostics.

Role Batching

When an EOP has a large number of role tags (more than 12), the system automatically splits them into batches and runs the role mapping step for each batch in parallel. Results are merged with deduplication, so the output is always a single unified mapping. This is transparent to the caller -- no changes are needed in the kickoff request.

Expected Timelines

EOP SizeAnnex CountDuration
Small1-330s - 1 min
Medium4-81-3 min
Large10+3-5 min
warning

QRG Generation returns UPPERCASE status values (SUCCESS, ERROR, PROCESSING). This differs from EOP Editing and QRG Editing which use lowercase. Your polling logic must handle both conventions.


QRG Editing

QRG editing modifies a single section (BEFORE, DURING, or AFTER) of an existing Quick Reference Guide.

Kickoff

curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-editing-kickoff/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"userId": "user-456",
"roleId": "a1b2c3d4-e5f6-7890-abcd-100000000001",
"section": "DURING",
"content": "<ul><li>Alert students calmly and begin evacuation</li></ul>",
"prompt": "Add a step about checking classroom headcount before leaving",
"sessionId": null,
"quickReferenceGuide": {
"name": "Fire Safety - Teacher",
"type": "EMERGENCY",
"instructionFor": "FIRE",
"generalUserTagManagementType": "ASSIGN_SELECTED",
"generalUserTags": [
{ "label": "Teacher", "value": "a1b2c3d4-e5f6-7890-abcd-100000000001" }
],
"organization": {
"id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"name": "Lincoln Elementary",
"type": "FACILITY",
"parent": {
"id": "d4e5f6a7-b8c9-0123-d456-e78901234567",
"name": "Springfield School District",
"type": "DISTRICT"
}
}
}
}
}'

Kickoff Fields

FieldTypeRequiredDescription
userIdstring (UUID)YesRequesting user
roleIdstring (UUID)YesRole performing the edit
sectionstringYesBEFORE, DURING, or AFTER
contentstringYesCurrent section HTML content
promptstringYesEdit instructions (min 1 character)
sessionIdstringNoCorrelation ID (pass null for new session)
quickReferenceGuideobjectYesQRG metadata (name, type, instructionFor, tags, organization)

Poll and Fetch

# Poll status
curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-editing-status/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestIds": ["req-abc..."]}}'

# Fetch result (once status is success)
curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-editing-result/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "req-abc..."}}'

Result Fields

FieldDescription
requestIdTracking ID
sessionIdSession this edit belongs to
statussuccess or error
messageEdited section content (HTML)
isEditBoolean confirming the content was modified

Cancel

curl -X POST "${BASE_URL}/ai/api/tools/k12-qrg-cancel/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "req-abc...", "jobType": "editing"}}'

Tool Reference Summary

Tool IDGroupPurpose
k12-district-planning-kickoffEOPStart district-level EOP planning edits
k12-facility-planning-kickoffEOPStart facility-level EOP planning edits
k12-functional-annex-kickoffEOPStart functional annex editing
k12-hazard-annex-kickoffEOPStart hazard annex editing
k12-functional-annex-suggest-roles-kickoffEOPSuggest missing roles for a functional annex
k12-hazard-annex-suggest-roles-kickoffEOPSuggest missing roles for a hazard annex
k12-annex-editing-kickoffEOPCompatibility kickoff for general EOP annex editing
k12-annex-editing-statusEOPPoll editing status
k12-annex-editing-resultEOPFetch editing result
k12-qrg-generation-kickoffQRG GenStart QRG generation from EOP
k12-qrg-generation-statusQRG GenPoll generation status
k12-qrg-generation-resultQRG GenFetch generated QRGs
k12-qrg-editing-kickoffQRG EditStart QRG section editing
k12-qrg-editing-statusQRG EditPoll editing status
k12-qrg-editing-resultQRG EditFetch editing result
k12-cancel-task-requestEOPCancel EOP editing job
k12-qrg-cancelQRGCancel QRG generation or editing job

Status Reference

All workflows follow the async pattern: kickoff returns accepted, poll until terminal, then fetch result.

EOP Editing and QRG Editing (lowercase)

StatusTerminalMeaning
acceptedNoJob queued
processingNoAI processing
successYesComplete
errorYesFailed
cancelledYesCancelled

QRG Generation (UPPERCASE)

StatusTerminalMeaning
acceptedNoJob queued
PROCESSINGNoAI pipeline running
SUCCESSYesComplete
ERRORYesFailed
CANCELLEDYesCancelled

Error Handling

HTTP StatusMeaning
400Missing required fields or invalid format
401Invalid token or missing subscription key
403Token lacks required scope
404Tool ID not found
422Validation error (schema failure)
429Rate limit exceeded
500Internal server error

Integration Best Practices

  1. Poll with exponential backoff -- Start at 2 seconds, increase the interval. Set a maximum timeout based on expected EOP size.
  2. Persist requestId and sessionId -- Both are required for status polling and result retrieval.
  3. Handle both status conventions -- QRG Generation uses UPPERCASE; EOP and QRG Editing use lowercase. Normalize before comparison.
  4. Expect partial success in QRG Generation -- Individual annexes can fail while others succeed. Check aiStats for per-step diagnostics.
  5. Use sessions for multi-step flows -- Pass sessionId from a previous response to maintain conversation context across related edits.
  6. Verify organization type -- QRGs can only be saved to FACILITY organizations, not DISTRICT.
  7. Cache tokens -- OAuth tokens expire after ~1 hour. Cache and refresh proactively.

Next Steps