Developer Documentation

MusterHub Integrations API

Connect your control room, training system and LMS to MusterHub. Data flows in automatically: no manual re-entry, no middleware, no per-seat charges for integrations.

Authentication

All integration endpoints use a service-scoped API key passed as the X-Api-Key request header. Keys are separate from user accounts. They belong to your service, not to any individual.

X-Api-Key: mh_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Key management

  • Generated in your MusterHub admin portal under Settings > API Keys
  • Scoped to specific capabilities: a control room key cannot import training records
  • Optionally time-limited with an expiry date
  • Revocable at any time without affecting other integrations or users
  • The plain-text key is shown once on creation only, so store it securely immediately
  • Only a one-way hash is ever stored by MusterHub
Tip Issue a separate key per integration system. If a key is compromised you can revoke just that one without disrupting anything else.

Scopes

When generating a key you choose which scopes to grant. A single key can hold multiple scopes, or you can issue one key per system.

ScopeWhat it unlocks
Control Room Push appliance availability, trigger turnout alerts, create control room events
Incident Import Upsert incident and callout records
Course Import Sync course catalogue and attendee lists from an LMS
Training Import Sync training records and qualifications from a TMS
Directory Export Read-only export of your service's groups, stations, personnel and appliances
Notification Send Send a push notification to one of your service's users

Control Room API

Real-time integration for mobilisation and CAD systems. These endpoints are designed for low-latency pushes directly from your control room. No polling, no scheduled jobs required.

Compatible systems include Capita Parity, FireWatch and FireStation, and any system that can make HTTPS requests with a JSON body.

Requires: Control Room scope

Update appliance status

Push the live availability state of an appliance by its callsign. MusterHub immediately reflects the change in the appliance status view for all members at that station.

POST /api/control-room/appliance-status

Request body

{ "callsign": "KV57A", "status": "Available", "crewOnBoard": 4, "crewRequired": 5 }
FieldTypeRequiredNotes
callsignstringYesMust match the callsign configured in MusterHub
statusstringYesAvailable, Unavailable, Responding, AtScene, Returning
crewOnBoardintegerNoCurrent crew count
crewRequiredintegerNoFull crew requirement for this appliance

Response (200)

{ "callsign": "KV57A", "status": "Available", "crewOnBoard": 4, "crewRequired": 5, "updatedAtUtc": "2026-07-02T08:30:00Z" }

Trigger a turnout alert

Send a turnout alert directly from your control room or mobilisation system. MusterHub delivers a critical push notification to all on-call members at the target station, bypassing silent and Do Not Disturb modes on iOS where the critical alerts entitlement is active.

POST /api/control-room/turnout

Request body

{ "stationCode": "KV57", "title": "House fire, persons reported", "description": "14 High Street, Tavistock. 3-pump attendance.", "incidentReference": "2026-INC-04821", "targetEmployeeNumber": "5893", "isTest": false }
FieldTypeRequiredNotes
stationCodestringYesStation code as configured in MusterHub (e.g. KV57)
titlestringYesAlert headline, shown on the device lock screen
descriptionstringNoAdditional detail shown in the notification body
incidentReferencestringNoLinks the alert to an existing incident record if the reference matches
targetUserIdUUIDNoOmit to broadcast to all station members; supply to alert a single member only
targetEmployeeNumberstringNoAlternative to targetUserId: alert the single member with this employee number. Rejected if the number matches no one or more than one person
isTestbooleanNoMarks as a test: prefixes title with [TEST] and does not send as a critical notification

Returns the created turnout alert object (200), including its id which can be used to track responses via the app.

On-call filtering Members who have enabled the "only alert me when on call" preference in the app will only receive this notification if they have an active on-call rota window at the time of the request. A per-station buffer (configurable in the admin portal under Stations) is subtracted from the rota start time: a 5-minute buffer on an 08:00 shift means the member receives alerts from 07:55. Filtering is applied server-side, so the critical push is never sent to off-call devices.

Create a control room event

Push a scheduled event into MusterHub from an external system, for example a planned exercise or training burn from a mobilisation system. The event is visible to all members across your service.

POST /api/control-room/events

Request body

{ "title": "Scheduled exercise: breathing apparatus", "description": "Quarterly BA drill at Tavistock station.", "startAtUtc": "2026-07-10T09:00:00Z", "endAtUtc": "2026-07-10T12:00:00Z", "location": "KV57 Tavistock Fire Station" }
FieldTypeRequired
titlestringYes
descriptionstringYes
startAtUtcISO 8601 datetimeYes
endAtUtcISO 8601 datetimeYes
locationstringNo

Incident Import

Batch import incident and callout records from your mobilisation system. Records are matched by reference: re-posting the same reference updates the existing record rather than creating a duplicate. Safe to run as a scheduled sync.

Requires: Incident Import scope

POST /api/integrations/incidents

Request body

{ "incidents": [ { "reference": "2026-INC-04821", "incidentType": "House fire", "description": "14 High Street, Tavistock. Persons reported.", "startedAtUtc": "2026-07-02T08:14:00Z", "endedAtUtc": "2026-07-02T10:42:00Z", "appliancesAttended": ["KV57A", "KV57B"], "stationCode": "KV57" } ] }
FieldTypeRequiredNotes
referencestringYesUnique reference from your mobilisation system, used for idempotent matching
incidentTypestringYesFree-text, e.g. House fire, RTC, Flooding
descriptionstringNo
startedAtUtcISO 8601 datetimeYes
endedAtUtcISO 8601 datetimeNoCan be omitted and updated in a later sync once the incident is closed
appliancesAttendedstring arrayNoList of callsigns that attended
stationCodestringNoLinks the incident to a station; must match a station code in MusterHub

Response (200)

{ "created": 1, "updated": 0, "warnings": [] }

Warnings are returned for skippable issues such as an unrecognised station code. The rest of the batch still processes, and warnings do not cause a failure response.

Course Import

Sync your course catalogue from an external learning management system. Courses are matched by externalId so re-running the sync updates rather than duplicates. Attendees are matched to MusterHub members by employee number first, then email.

Compatible systems include Gartan, Moodle, TalentLMS and Zoho Learn.

Requires: Course Import scope

POST /api/integrations/courses

Request body

{ "courses": [ { "externalId": "LMS-COURSE-9871", "title": "Incident Command Level 2", "startDate": "2026-08-14", "endDate": "2026-08-15", "startTime": "09:00:00", "endTime": "17:00:00", "venue": "Devon & Somerset Fire HQ, Exeter", "facilitator": "Station Manager A. Hughes", "additionalDetails": "Bring your Level 1 certificate. PPE provided.", "resources": [ { "resourceDisplayName": "Pre-course reading", "resourceLink": "https://your-lms.example.com/ic2-prereading" } ], "attendees": [ { "employeeNumber": "PR04821" }, { "email": "j.smith@example-brigade.gov.uk" } ] } ] }
FieldTypeRequiredNotes
externalIdstringYesYour LMS's unique ID, used for idempotent matching on re-sync
titlestringYes
startDate / endDatedate (YYYY-MM-DD)Yes
startTime / endTimetime (HH:MM:SS)Yes
venuestringYes
facilitatorstringYes
additionalDetailsstringNo
resourcesarrayNoPre-course materials: each needs resourceDisplayName and resourceLink
attendeesarrayNoEach attendee needs at least one of employeeNumber or email

Response (200)

{ "created": 1, "updated": 0, "warnings": [ "Course 'IC Level 2' (LMS-COURSE-9871): no user found for attendee (employee number 'PR09999', email '')." ] }

Unmatched attendees generate a warning but do not fail the import. The course itself is still created or updated.

Training & Qualifications Import

Sync training records and qualifications from your training management system. Members are matched by externalRef first, then by employeeNumber. If a member cannot be matched, an unlinked record is created and can be manually linked later in the admin portal.

Qualifications within each record are matched by their own externalRef, which is strongly recommended to prevent duplicates on re-sync.

Compatible systems include Gartan and any TMS that can export member records.

Requires: Training Import scope

POST /api/integrations/training

Request body

{ "records": [ { "externalRef": "TMS-MEMBER-1042", "employeeNumber": "PR04821", "memberName": "James Smith", "qualifications": [ { "externalRef": "TMS-QUAL-55001", "name": "Breathing Apparatus Wearers", "category": "Operational", "achievedDate": "2023-04-12", "expiryDate": "2026-04-12", "type": "Practical", "renewalMonths": 36 }, { "externalRef": "TMS-QUAL-55042", "name": "Fire Safety Awareness", "category": "Compliance", "achievedDate": "2025-01-08", "type": "ELearning", "eLearningUrl": "https://your-lms.example.com/fire-safety-awareness" } ] } ] }

Member record fields

FieldTypeRequiredNotes
externalRefstringNoRecommended: your TMS's unique ID for this member, used for matching on subsequent syncs
employeeNumberstringNoMember's employee number in MusterHub (each service can label this differently in its portal, e.g. PR number)
memberNamestringYes
qualificationsarrayYesCan be an empty array

Qualification fields

FieldTypeRequiredNotes
externalRefstringNoStrongly recommended: prevents duplicate qualifications on re-sync
namestringYes
categorystringYesFree-text grouping, e.g. Operational, Compliance, Leadership
achievedDatedate (YYYY-MM-DD)Yes
expiryDatedate (YYYY-MM-DD)No
typestringNoPractical (default) or ELearning
renewalMonthsintegerNoUsed to calculate the next renewal date and surface expiry warnings
eLearningUrlstringNoDisplayed as a link to the member for ELearning type qualifications

Response (200)

{ "recordsCreated": 1, "recordsUpdated": 0, "qualificationsCreated": 2, "qualificationsUpdated": 0, "warnings": [] }

Bolt-on Products

These two endpoints exist specifically for MusterHub's own companion products (currently MusterHub Rota) that plug into your organisation's data without holding a copy of it. The same API key mechanism applies: generate a key with the scope the bolt-on needs.

Directory export

Read-only snapshot of your service's organisational structure: groups, stations, personnel and their station memberships, and appliances. Intended for a bolt-on's own initial import or a periodic re-sync, not for polling.

Requires: Directory Export scope

GET /api/integrations/directory

Response (200)

{ "serviceId": "5a2e...", "serviceName": "Devon & Somerset Fire and Rescue", "groups": [ { "id": "...", "name": "Exeter Group" } ], "stations": [ { "id": "...", "code": "KV57", "name": "Tavistock", "groupId": "..." } ], "users": [ { "id": "...", "fullName": "James Smith", "rank": "Firefighter", "employeeNumber": "PR04821", "stations": [ { "stationId": "...", "isHome": true } ] } ], "appliances": [ { "id": "...", "callsign": "KV57A", "description": "Pump", "stationId": "..." } ] }
Deliberately minimal This export excludes email addresses and notification preferences. It gives a bolt-on identity, structure and membership, nothing more, so it can match people to your existing MusterHub roster without becoming a second copy of contact data.

Notification relay

Lets a bolt-on send an ordinary push notification to one of your service's users through MusterHub's own push pipeline, without ever holding device tokens itself. For example, MusterHub Rota uses this to alert someone their shift swap was approved.

Requires: Notification Send scope

POST /api/integrations/notifications

Request body

{ "userId": "5a2e...", "title": "Shift swap approved", "body": "Your swap with J. Smith for Saturday's shift has been approved.", "navigationType": "rota", "navigationId": null }
FieldTypeRequiredNotes
userIdUUIDYesMust belong to the same service your API key is scoped to; otherwise returns 404
titlestringYes
bodystringYes
navigationTypestringNoTells the app which screen to open when the notification is tapped
navigationIdstringNoOptional ID passed alongside navigationType, e.g. a specific record to open

Response (200)

{ "devicesNotified": 2 }
Ordinary, not critical This endpoint always sends a normal push notification. It cannot be used to trigger a critical, Do-Not-Disturb-bypassing alert: that stays exclusive to MusterHub's own turnout system.

Verifying MusterHub Tokens

When a signed-in MusterHub user opens a bolt-on product, MusterHub can hand it a short-lived access token so the person doesn't have to sign in a second time. The bolt-on validates that token itself, entirely offline: it never has to call back into MusterHub or hold a shared secret.

GET /.well-known/jwks.json

This endpoint is public and unauthenticated. It publishes MusterHub's current RSA public key in standard JWKS format, which any JWT library can use directly to verify a token's signature.

What to check

Claim / propertyExpected value
SignatureValid against the current key published at /.well-known/jwks.json
issMusterHub
audMusterHubApp
expAccess tokens are short-lived (30 minutes) — reject anything expired, don't extend it yourself
subThe MusterHub user's ID
org_idThe user's MusterHub service (organisation) ID — scope everything you do to this
entitlementsArray of bolt-on module codes enabled for the whole organisation (e.g. rota)
module_adminArray of module codes this specific person administers, a subset of what your service has enabled
org_id is the trust boundary Always scope the request to org_id, not just a valid signature. A validly-signed token from a different MusterHub organisation should never be treated as authorised for yours.

Error handling

All endpoints return standard HTTP status codes. Errors return a plain-text body describing the issue.

CodeMeaning
200Success. Check the warnings array in the response body for partial issues that did not block the overall request.
400Bad request: a required field is missing or a value is invalid (e.g. an unknown appliance status string).
401Unauthorised: the X-Api-Key header is missing, the key is invalid or expired, or the key does not have the required scope for this endpoint.
404Not found: a referenced entity does not exist in MusterHub (e.g. an unknown callsign or station code passed as a required field).
Warnings vs. errors Import endpoints (incidents, courses, training) distinguish between hard errors and warnings. A hard error (4xx) means nothing was processed. A warning means one item in the batch was skipped, for example, an unrecognised station code on an incident, or an attendee whose email doesn't match any member. The rest of the batch still succeeds and the response is still 200.

Getting started

  1. Sign in to your MusterHub admin portal and go to Settings > API Keys.
  2. Click Generate new key, give it a name (e.g. "Capita Parity"), and select the scopes you need.
  3. Copy the key immediately: it is shown once only and cannot be retrieved afterwards.
  4. Configure your external system to send requests to https://api.musterhub.app with the X-Api-Key: <your-key> header.
  5. Send a test request with "isTest": true (for turnout) or a single record (for imports) and verify the response.
Base URL All integration endpoints are hosted at https://api.musterhub.app. If your service uses a self-hosted or custom-domain deployment, replace this with your own API base URL.

Need help?

If your system isn't listed, you're not sure which scope you need, or you'd like us to help configure the integration, get in touch: