Skip to content

Developers · Partner API

Product lead rather than engineer? The partner overview says what the integration takes and what we build for you.

API reference

Everything the partner API accepts and returns. Base URL https://app.fire-mic.com/v1, JSON in and out, bearer authentication, one error shape.

Last updated September 2026.

Conventions

  • Base URL https://app.fire-mic.com/v1. HTTPS only.
  • Requests are JSON (Content-Type: application/json). Audio travels base64-encoded inside the JSON body, or, above about 3 MB, as a file uploaded first with POST /v1/uploads.
  • Request size: the platform refuses any request body over 4.5 MB with 413 before our code runs, and that response carries no CORS headers, so a browser reports it as a network error. Base64 adds a third, so inline audio tops out near 3.3 MB; send anything larger by upload.
  • Responses are JSON. Success bodies carry an object field naming the shape. Every error, from every route, is:
{ "error": { "code": "DEPARTMENT_NOT_FOUND", "message": "No department with NERIS id FD00000000. …", "details": { "…": "optional" } } }
  • Authentication is Authorization: Bearer <credential>, where the credential is an API key (fm_live_… or fm_test_…) or a session token from POST /v1/sessions.
  • CORS is open on every route, so browsers and webviews can call /v1 directly with a session token. Preflight OPTIONS returns 204.
  • Every response carries an X-Request-Id header. Quote it when you report a problem.
  • Caching: responses are Cache-Control: no-store, except GET /v1/processing-messages, which is public and cacheable for an hour.
  • Timeouts: POST /v1/narrations is synchronous and typically returns in 10 to 15 seconds for a one-minute narration; set your client timeout to at least 120 seconds.

Authentication

API keys

Issued by FireMic, shown once, stored hashed. Two properties are fixed at issue time:

Property Values Meaning
mode test, live Same pipeline, same responses. Test keys have tighter rate limits and their narrations are flagged as test data.
scope partner, department A partner-scoped key names the department on each request. A department-scoped key is pinned to one department and rejects any other with 403 DEPARTMENT_SCOPE_MISMATCH. Use department-scoped keys anywhere a key must live on a device, such as a desktop installer with no server tier.

Keys are for servers. Do not embed a partner-scoped key in a browser, mobile app or desktop client; mint sessions instead. Revoking a key takes effect within one request and also invalidates every session minted from it.

Session tokens

POST /v1/sessions exchanges an API key for a signed token that can only act for one department, for at most an hour. This is the credential for anything running on a user’s device. A session token cannot create sessions, cannot read other departments, and is refused everywhere with 401 SESSION_EXPIRED once past its expiry.

Failures

Status Code When
401 MISSING_CREDENTIAL No Authorization: Bearer header.
401 INVALID_CREDENTIAL Unknown key, malformed token, or a session token used where a key is required.
401 KEY_REVOKED The key (or the key behind this session) was revoked.
401 SESSION_EXPIRED The session token is past expires_at. Mint a new one.
403 PARTNER_SUSPENDED The partner account is suspended.
503 PARTNER_API_UNAVAILABLE Our side cannot verify credentials right now. Retry with backoff.

Rate limits

Limits are per hour unless stated, counted on the expensive route (POST /v1/narrations) across five dimensions at once. A refusal is 429 RATE_LIMITED with a Retry-After header and details.retry_after_seconds.

Dimension Test key Live key
Per key 60 600
Per partner (all keys) 120 3,000
Per partner per day 500 20,000
Per department 200 400
Per source IP 300 600

The cheap routes (sessions, reads, outcome) share one ceiling of 1,200 per key per hour. Per-partner ceilings can be raised for a live partner; GET /v1/ping reports the ones your credential currently has. If our limiter itself is unavailable, test keys are refused (503) and live keys are allowed through: a limiter outage must never block a firefighter mid-incident.

Input limits

Input Limit
Audio, inline (base64 in the JSON body) about 3.3 MB decoded: the whole request must stay under 4.5 MB. At 32 kbps that is about 14 minutes of speech
Audio, by upload (POST /v1/uploads) 15 MB (15,000,000 bytes)
Audio, duration 15 minutes: AUDIO_TOO_LONG when duration_seconds says more, or when the audio itself measures longer
Audio, types audio/webm (opus), audio/ogg (opus), audio/mp4, audio/m4a, audio/aac, audio/wav, audio/mpeg, audio/mp3, audio/flac
Transcript 50,000 characters
external_ref 200 characters
metadata 4 KB of JSON
Outcome body 256 KB of JSON

Endpoints

GET /v1/ping

Confirms a credential and reports what it can do. Accepts a key or a session.

{
  "ok": true,
  "partner": { "slug": "alpine", "name": "Alpine Software" },
  "mode": "test",
  "credential": { "via": "api_key", "scope": "partner", "department": null, "department_name": null },
  "limits": {
    "partner_narration_key": { "per_window": 60, "window_seconds": 3600 },
    "partner_narration_partner_hour": { "per_window": 120, "window_seconds": 3600 },
    "partner_narration_partner_day": { "per_window": 500, "window_seconds": 86400 }
  },
  "server_time": "2026-09-02T10:51:30.003Z"
}

credential.department is the NERIS id a session token is scoped to (with its department_name), or null for a key.

POST /v1/sessions

API key only. Mints a department-scoped session token for a client device.

Request

Field Type Required Notes
department string for partner-scoped keys NERIS department id, FD + 8 digits. Omit for a department-scoped key.
external_ref string no Your incident or record reference, up to 200 characters. Carried on every narration made with the session.
ttl_seconds number no 60 to 3600. Default 900.

Response 201

{
  "object": "session",
  "token": "eyJhbGciOiJIUzI1NiJ9…",
  "token_type": "Bearer",
  "expires_at": "2026-09-02T11:04:55.000Z",
  "mode": "test",
  "department": { "neris_id": "FD34007744", "name": "Collingswood Fire Department", "city": "Collingswood", "state": "NJ" },
  "external_ref": "RA-SESSION-1"
}

Errors: 400 DEPARTMENT_REQUIRED, 400 DEPARTMENT_INVALID, 400 INVALID_TTL, 400 INVALID_EXTERNAL_REF, 403 DEPARTMENT_SCOPE_MISMATCH, 404 DEPARTMENT_NOT_FOUND, and 401 INVALID_CREDENTIAL when a session token is presented instead of a key.

POST /v1/uploads

For audio too large to send inline (over about 3 MB, or any recording you would rather not base64). Accepts a key or a session. Three steps:

  1. POST /v1/uploads with the file’s type and size.
  2. PUT the raw file to the returned upload_url, with the file’s Content-Type. No Authorization header: the URL is pre-signed.
  3. POST /v1/narrations with "audio": { "upload_id": "<id>", "mime": "…" } instead of data.

Request

Field Type Required Notes
mime string no The audio type you will upload. Refused early with UNSUPPORTED_AUDIO_TYPE if it is not one we accept.
bytes number no The file size. Refused early with AUDIO_TOO_LARGE above 15 MB.

Response 201

{
  "object": "upload",
  "id": "upl_4XbGf0Qk8VtR2sYp1LmNa7Cd",
  "upload_url": "https://…/partner-audio/…/uploads/upl_4XbGf0Qk8VtR2sYp1LmNa7Cd?token=…",
  "method": "PUT",
  "headers": { "Content-Type": "audio/wav" },
  "max_bytes": 15000000,
  "expires_at": "2026-09-23T16:28:55.624Z"
}

Treat upload_url as opaque. PUT the file within two hours; it answers CORS preflights. A narration takes the file, after which the id no longer resolves: use one upload per narration. The audio is then kept, or deleted, under the same retention as inline audio (see data handling); an upload no narration used is deleted after a day.

UPLOAD=$(curl -s https://app.fire-mic.com/v1/uploads \
  -H "Authorization: Bearer $FIREMIC_KEY" -H "Content-Type: application/json" \
  -d "{\"mime\": \"audio/wav\", \"bytes\": $(wc -c < narration.wav)}")
curl -s -X PUT -H "Content-Type: audio/wav" --data-binary @narration.wav "$(echo "$UPLOAD" | python3 -c 'import json,sys;print(json.load(sys.stdin)["upload_url"])')"
UPLOAD_ID=$(echo "$UPLOAD" | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
curl -s https://app.fire-mic.com/v1/narrations \
  -H "Authorization: Bearer $FIREMIC_KEY" -H "Content-Type: application/json" \
  -d "{\"department\": \"FD34007744\", \"audio\": {\"upload_id\": \"$UPLOAD_ID\", \"mime\": \"audio/wav\"}}"

Errors: 400 UNSUPPORTED_AUDIO_TYPE, 400 AUDIO_TOO_LARGE, 400 INVALID_UPLOAD, the authentication failures above, 429 RATE_LIMITED, 503 PARTNER_API_UNAVAILABLE.

POST /v1/narrations

The call. Accepts a key or a session. Synchronous: the response is the finished draft.

Request

Field Type Required Notes
department string see notes NERIS department id. Required with a partner-scoped key. Optional with a session or a department-scoped key, and if present it must match.
audio object one of audio / transcript { "data": "<base64>", "mime": "audio/mp4", "duration_seconds": 59.2 }, or { "upload_id": "upl_…", "mime": "audio/wav" } for a file sent by upload. A data: URL prefix is tolerated. duration_seconds is optional.
transcript string one of audio / transcript Already-transcribed narration, or typed text. Skips transcription.
external_ref string no Up to 200 characters. Defaults to the session’s external_ref if any.
metadata object no Anything you want stored with the narration and echoed back, up to 4 KB. Not interpreted.

Response 200: a narration object with status: "complete".

Errors

Status Code Meaning
400 INVALID_BODY Body is not a JSON object.
400 INPUT_REQUIRED Neither or both of audio and transcript.
400 INVALID_AUDIO, UNSUPPORTED_AUDIO_TYPE, AUDIO_TOO_LARGE, AUDIO_TOO_LONG See input limits.
400 UPLOAD_NOT_FOUND No uploaded file for that upload_id: it was never PUT, a narration already used it, it belongs to another partner, or it sat unused for more than a day.
400 INVALID_TRANSCRIPT, TRANSCRIPT_TOO_LONG Empty, non-string, or over 50,000 characters.
400 INVALID_EXTERNAL_REF, INVALID_METADATA Over the limit or the wrong type.
400 DEPARTMENT_REQUIRED, DEPARTMENT_INVALID Missing or malformed NERIS id with a partner-scoped key.
403 DEPARTMENT_SCOPE_MISMATCH The body names a department the credential cannot act for.
404 DEPARTMENT_NOT_FOUND Not in the national directory.
422 EMPTY_TRANSCRIPT The audio held no usable speech: silence, a muted mic, room noise. Speech recognition returns words even for silence (often the department’s own name), so this is decided from its confidence, not from the text alone. details.id and details.transcript are set; nothing was extracted.
429 RATE_LIMITED See rate limits.
502 TRANSCRIPTION_FAILED, EXTRACTION_FAILED An upstream stage failed. details.id is set; for EXTRACTION_FAILED, details.transcript carries the transcript so you can still show it. Retry once after a few seconds.
503 PARTNER_API_UNAVAILABLE Retry with backoff.

The call is not idempotent: a retry creates a second narration. Send external_ref so you can reconcile duplicates on your side, and do not retry a request that returned 200.

GET /v1/narrations/{id}

Re-reads a narration. Accepts a key or a session; only the partner that created the narration can read it. Returns the narration object, including outcome once reported. 404 NOT_FOUND for an unknown id or another partner’s narration.

POST /v1/narrations/{id}/outcome

Reports what the firefighter did with the draft. Optional, idempotent (the latest report wins), and the single most useful thing a partner can send.

Field Type Required Notes
accepted boolean yes Did the firefighter keep the draft as the basis of the report?
edited_fields string[] no Paths the firefighter changed before saving, up to 200. Use your own field names or the neris paths; both are useful.
neris object no The final NERIS payload as submitted, if you want us to learn from the corrected values. Up to 256 KB.
submitted_to_neris boolean no Whether the report was filed.
neris_incident_id string no The NERIS incident id after filing, up to 120 characters.
review_seconds number no Time from draft to save, 0 to 86,400.
notes string no Free text, up to 2,000 characters.

Response 200 { "ok": true, "id": "nar_…", "outcome_at": "2026-09-02T10:55:28.397Z" }. Errors: 400 INVALID_OUTCOME, 400 OUTCOME_TOO_LARGE, 404 NOT_FOUND.

GET /v1/departments/{neris_id}

Directory check. Accepts a key or a session.

{ "object": "department", "neris_id": "FD34007744", "name": "Collingswood Fire Department", "city": "Collingswood", "state": "NJ" }

Errors: 400 DEPARTMENT_INVALID, 404 DEPARTMENT_NOT_FOUND.

GET /v1/processing-messages

Public, no authentication, cacheable. The rotating lines FireMic’s own app shows while a narration is processing, grouped by the phase they describe. Show one at a time, in order, from a random starting point, changing every suggested_interval_ms. Entirely optional.

{
  "object": "processing_messages",
  "suggested_interval_ms": 2200,
  "phases": {
    "transcribing": ["Transcribing your narration…", "Turning speech into text…", "…"],
    "extracting_core": ["Reading the incident…", "Reading the callout…", "…"],
    "extracting_modules": ["…"]
  },
  "note": "Show one line at a time, in order, from a random start. Typical total wait: 10–15 s for a one-minute narration."
}

The narration object

Returned by POST /v1/narrations and GET /v1/narrations/{id}. This is a real response for a 55-second recording, lightly trimmed. Times are the department’s wall clock with its UTC offset (Sayville, New York, in September: -04:00):

{
  "id": "nar_bTPw6g7ulZhFnrJlIidxqVvk",
  "object": "narration",
  "status": "complete",
  "mode": "test",
  "department": {
    "neris_id": "FD36103862",
    "name": "Sayville Fire Department",
    "city": "Sayville",
    "state": "NY"
  },
  "external_ref": "RA-2026-001848",
  "input": {
    "kind": "audio",
    "mime": "audio/mp4",
    "audio_seconds": 54.8
  },
  "transcript": "Engine 31 responded to 214 Maple Avenue for a reported structure fire. Dispatched at 1421, en route at 1423, on scene at 1427. On arrival we had a two-story wood-frame single-family dwelling with light smoke showing from \u2026",
  "neris": {
    "base": {
      "department_neris_id": "FD36103862",
      "location": {
        "number": 214,
        "complete_number": "214",
        "street": "Maple",
        "street_postfix": "AVENUE",
        "incorporated_municipality": "Sayville",
        "state": "NY"
      },
      "location_use": {
        "use_type": "RESIDENTIAL||DETATCHED_SINGLE_FAMILY_DWELLING",
        "in_use": {
          "in_use": true
        }
      },
      "people_present": true,
      "animals_rescued": 0,
      "outcome_narrative": "Engine 31 responded to 214 Maple Avenue for a reported structure fire. The unit was dispatched at 14:21, en route at 14:23, and on scene at 14:27. On arrival, crews found \u2026",
      "displacement_count": 1,
      "displacement_causes": [
        "FIRE",
        "SMOKE"
      ]
    },
    "incident_types": [
      {
        "type": "FIRE||STRUCTURE_FIRE||ROOM_AND_CONTENTS_FIRE",
        "primary": true
      }
    ],
    "dispatch": {
      "incident_clear": "2026-09-23T15:45:00-04:00",
      "call_arrival": "2026-09-23T14:21:00-04:00",
      "call_answered": "2026-09-23T14:21:00-04:00",
      "call_create": "2026-09-23T14:21:00-04:00",
      "location": {
        "number": 214,
        "complete_number": "214",
        "street": "Maple",
        "street_postfix": "AVENUE",
        "incorporated_municipality": "Sayville",
        "state": "NY"
      },
      "unit_responses": [
        {
          "reported_unit_id": "Engine 31",
          "dispatch": "2026-09-23T14:21:00-04:00",
          "enroute_to_scene": "2026-09-23T14:23:00-04:00",
          "on_scene": "2026-09-23T14:27:00-04:00",
          "unit_clear": "2026-09-23T15:45:00-04:00",
          "response_mode": "EMERGENT"
        },
        {
          "reported_unit_id": "Ladder 5",
          "on_scene": "2026-09-23T14:30:00-04:00",
          "unit_clear": "2026-09-23T15:45:00-04:00",
          "response_mode": "EMERGENT"
        }
      ]
    },
    "tactic_timestamps": {
      "command_established": "2026-09-23T14:27:00-04:00",
      "completed_sizeup": "2026-09-23T14:29:00-04:00",
      "primary_search_begin": "2026-09-23T14:32:00-04:00",
      "primary_search_complete": "2026-09-23T14:38:00-04:00",
      "water_on_fire": "2026-09-23T14:30:00-04:00",
      "fire_knocked_down": "2026-09-23T14:35:00-04:00"
    },
    "nonfd_aids": [
      "LAW_ENFORCEMENT",
      "UTILITIES_PUBLIC_WORKS",
      "SOCIAL_SERVICES"
    ],
    "smoke_alarm": {
      "presence": {
        "type": "PRESENT",
        "working": true
      }
    },
    "fire_alarm": {
      "presence": {
        "type": "NOT_PRESENT"
      }
    },
    "other_alarm": {
      "presence": {
        "type": "NOT_PRESENT"
      }
    },
    "fire_suppression": {
      "presence": {
        "type": "NOT_PRESENT"
      }
    },
    "fire_detail": {
      "location_detail": {
        "type": "STRUCTURE",
        "floor_of_origin": 2,
        "arrival_condition": "SMOKE_SHOWING",
        "damage_type": "MINOR_DAMAGE",
        "room_of_origin_type": "BEDROOM",
        "cause": "HEAT_FROM_ANOTHER_OBJECT"
      },
      "water_supply": "HYDRANT_LESS_500",
      "investigation_needed": "NO",
      "investigation_types": [
        "NONE"
      ],
      "suppression_appliances": [
        "SMALL_DIAMETER_FIRE_HOSE"
      ]
    }
  },
  "firemic_report": {
    "\u2026": "the FireMic-native report the NERIS payload was built from; see \"firemic_report\" below"
  },
  "gaps": [
    {
      "field": "incident_number",
      "label": "Incident Number",
      "question": "What was the incident number assigned by dispatch?"
    }
  ],
  "modules": {
    "activated": [
      "fire_detail"
    ],
    "failed": []
  },
  "timings": {
    "transcribe_ms": 818,
    "stage1_ms": 9489,
    "stage2_ms": 1421,
    "total_ms": 11730
  },
  "model": {
    "extraction": "firemic-extract-2026.10.01b",
    "transcription": "firemic-asr-2026.09.23"
  },
  "metadata": null,
  "error": null,
  "outcome": null,
  "outcome_at": null,
  "created_at": "2026-09-23T14:41:09.299Z",
  "completed_at": "2026-09-23T14:41:09.299Z"
}
Field Type Meaning
id string nar_ + 24 characters. Opaque.
object "narration"
status complete, failed, processing A POST only ever returns complete; failed rows exist for reads after a 422/502.
mode test, live The key’s mode.
department object neris_id, name, city, state of the department the narration is for.
external_ref string or null Yours, echoed.
input object kind (audio or text), mime, audio_seconds as sent.
transcript string What was heard, after our fire-service vocabulary. Show it to the firefighter.
neris object The incident as a NERIS v1 incident payload: the same module keys and enumerated values NERIS accepts on its own incident endpoint. Empty modules are omitted. Incident types are `L1
firemic_report object The FireMic-native report the payload was built from. Superset of neris: it also carries fields NERIS has no slot for (smoke-detector notes, a few special-study answers) and keeps empty modules with their defaults, which is convenient for rendering a form. Treat it as extended and additive: keys may be added without notice.
gaps array Required fields the narration did not cover. Each has a stable field id, a human label, and the question we would ask the firefighter. A clarification endpoint that takes the answer and returns field updates is on the roadmap; today, the questions are yours to ask in your form.
modules object activated: which detail modules the core extraction decided applied (fire_detail, medical_details, hazsit_detail, exposures). failed: any of those whose detail call failed; the module is present but empty.
timings object Milliseconds: transcribe_ms (null for text input), stage1_ms, stage2_ms, total_ms.
model object Opaque version labels for the transcription and extraction stages. Quote them in a bug report. They change when the extraction changes materially.
metadata object or null Yours, echoed.
error object or null { "code": … } on a failed narration.
outcome, outcome_at object, string What you reported, if anything.
created_at, completed_at string ISO 8601 UTC.

What extraction does and does not do

  • Times spoken as clock times (“dispatched at 14:21”) become full ISO timestamps on the incident date, in the department’s own time zone with its UTC offset: 14:21 in New Jersey in September is 2026-09-23T14:21:00-04:00. A narration that names no date is dated by the department’s calendar, so a call narrated at 9 pm is not stamped with tomorrow’s date. Times not spoken are estimated only for tactic milestones the firefighter described happening, in sequence after arrival; they are never invented for events that were not mentioned.
  • The department’s own city and state fill in when the narration omits them, and a mis-heard spelling of the department’s city is corrected to the directory spelling. A different town, such as mutual aid to the next department over, is left as spoken.
  • A call cancelled before arrival is coded NOEMERG||CANCELLED regardless of the nature it was dispatched as; the dispatched nature goes in the narrative.
  • Nothing NERIS requires is defaulted or invented. A required value the narration did not give (the incident number, when the call came in, water supply, the alarm and sprinkler answers on a structure fire, each casualty’s injury and rescue, …) is left out of neris and listed in gaps with the question to ask. A value NERIS would reject is listed there too instead of being sent or dropped. For repeated rows the field id carries the row index, for example casualty_rescue_nonff[0].rescue_type or exposures[1].damage_type.
  • Actions (actions_tactics) list only what the narration says the department’s own crews did. Work done by another agency, such as police running traffic or the utility shutting off gas, is recorded as non-FD aid, not as an action; nor is anything a call of that kind usually involves but the firefighter did not mention.
  • Nothing carries a confidence score. A field is either filled from what was said or absent and listed in gaps. Show the transcript; the firefighter is the confidence score.

Data handling

What a narration stores: partner, key id, mode, department, your external_ref and metadata, the input kind, the transcript, the neris draft and firemic_report we returned, gaps, modules, timings, model versions, a hash of the caller’s IP, and the outcome you report.

  • Audio is stored in a private bucket only we can read and deleted after the partner’s retention window, 30 days by default, by a daily job. A partner can set the window shorter, including zero: inline audio is then never written, and an uploaded file is deleted as soon as its narration completes. An upload no narration used is deleted after a day.
  • Transcript, draft and outcome are retained. They are what lets us measure extraction quality per department and improve it, and they are the basis of any accuracy figures we report to you.
  • Isolation: a partner reads only its own narrations. Narration data is never shared with another partner and is not used to train anything outside FireMic.
  • Deletion on request at the partner, department or narration level.
  • NERIS credentials: never requested, never held. This API cannot submit to NERIS.
  • Test mode rows are flagged as test data and excluded from any quality reporting.

Versioning

  • The path carries the major version. Additive changes (new fields, new error codes, new endpoints) ship on /v1 without notice; read responses tolerantly. Anything that removes or changes the meaning of a field ships as /v2 with the old version kept alive.
  • firemic_report is explicitly extended and additive.
  • model.extraction and model.transcription change when the underlying extraction changes materially, so a partner can correlate a change in drafts with a change on our side.

Changelog

  • 2026-10-01 (evening) A time that would still be in the future when filed (NERIS refuses those) is listed in gaps with its own question, for example dispatch.time_incident_clear or tactic_timestamps.fire_knocked_down, instead of going into neris to be rejected. A spoken time more than ten minutes ahead is still read as the previous day (a call before midnight narrated after it).
  • 2026-10-01 (later) Actions are no longer inferred: actions_tactics lists only what the narration says the department’s crews did. Another agency’s work (police traffic control, a utility securing the gas or power) is non-FD aid only, and a typical-but-unmentioned step (forcible entry, overhaul, establishing command) is left out. Model label moves to firemic-extract-2026.10.01b.
  • 2026-10-01 Required values are no longer filled with defaults in neris: no generated AUTO-… incident number, no call time of “now” when none was spoken, no “not present” alarms, no assumed investigation, evacuation count or injury status. Each one is listed in gaps instead, and gaps now covers everything NERIS requires (fire module on every fire, alarm and suppression answers on structure fires, hazmat chemicals, casualties and rescues, exposures). New field ids are additive; existing ids are unchanged. Model label moves to firemic-extract-2026.10.01.
  • 2026-09-23 POST /v1/uploads for audio over about 3 MB (the platform refuses request bodies over 4.5 MB). Timestamps carry the department’s UTC offset, and an undated narration is dated by the department’s calendar; they were labelled UTC before, which put them hours early. Silence and room noise now return 422 EMPTY_TRANSCRIPT instead of a draft. A zero audio-retention window is applied when the audio arrives. Model labels move to 2026.09.23.
  • 2026-09 v1 published: sessions, narrations (audio and text), outcome reporting, department lookup, processing messages.