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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKey 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
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.
| Scope | What 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.
Request body
{
"callsign": "KV57A",
"status": "Available",
"crewOnBoard": 4,
"crewRequired": 5
}| Field | Type | Required | Notes |
|---|---|---|---|
| callsign | string | Yes | Must match the callsign configured in MusterHub |
| status | string | Yes | Available, Unavailable, Responding, AtScene, Returning |
| crewOnBoard | integer | No | Current crew count |
| crewRequired | integer | No | Full 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.
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
}| Field | Type | Required | Notes |
|---|---|---|---|
| stationCode | string | Yes | Station code as configured in MusterHub (e.g. KV57) |
| title | string | Yes | Alert headline, shown on the device lock screen |
| description | string | No | Additional detail shown in the notification body |
| incidentReference | string | No | Links the alert to an existing incident record if the reference matches |
| targetUserId | UUID | No | Omit to broadcast to all station members; supply to alert a single member only |
| targetEmployeeNumber | string | No | Alternative to targetUserId: alert the single member with this employee number. Rejected if the number matches no one or more than one person |
| isTest | boolean | No | Marks 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.
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.
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"
}| Field | Type | Required |
|---|---|---|
| title | string | Yes |
| description | string | Yes |
| startAtUtc | ISO 8601 datetime | Yes |
| endAtUtc | ISO 8601 datetime | Yes |
| location | string | No |
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
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"
}
]
}| Field | Type | Required | Notes |
|---|---|---|---|
| reference | string | Yes | Unique reference from your mobilisation system, used for idempotent matching |
| incidentType | string | Yes | Free-text, e.g. House fire, RTC, Flooding |
| description | string | No | |
| startedAtUtc | ISO 8601 datetime | Yes | |
| endedAtUtc | ISO 8601 datetime | No | Can be omitted and updated in a later sync once the incident is closed |
| appliancesAttended | string array | No | List of callsigns that attended |
| stationCode | string | No | Links 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
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" }
]
}
]
}| Field | Type | Required | Notes |
|---|---|---|---|
| externalId | string | Yes | Your LMS's unique ID, used for idempotent matching on re-sync |
| title | string | Yes | |
| startDate / endDate | date (YYYY-MM-DD) | Yes | |
| startTime / endTime | time (HH:MM:SS) | Yes | |
| venue | string | Yes | |
| facilitator | string | Yes | |
| additionalDetails | string | No | |
| resources | array | No | Pre-course materials: each needs resourceDisplayName and resourceLink |
| attendees | array | No | Each 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
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
| Field | Type | Required | Notes |
|---|---|---|---|
| externalRef | string | No | Recommended: your TMS's unique ID for this member, used for matching on subsequent syncs |
| employeeNumber | string | No | Member's employee number in MusterHub (each service can label this differently in its portal, e.g. PR number) |
| memberName | string | Yes | |
| qualifications | array | Yes | Can be an empty array |
Qualification fields
| Field | Type | Required | Notes |
|---|---|---|---|
| externalRef | string | No | Strongly recommended: prevents duplicate qualifications on re-sync |
| name | string | Yes | |
| category | string | Yes | Free-text grouping, e.g. Operational, Compliance, Leadership |
| achievedDate | date (YYYY-MM-DD) | Yes | |
| expiryDate | date (YYYY-MM-DD) | No | |
| type | string | No | Practical (default) or ELearning |
| renewalMonths | integer | No | Used to calculate the next renewal date and surface expiry warnings |
| eLearningUrl | string | No | Displayed 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
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": "..." } ]
}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
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
}| Field | Type | Required | Notes |
|---|---|---|---|
| userId | UUID | Yes | Must belong to the same service your API key is scoped to; otherwise returns 404 |
| title | string | Yes | |
| body | string | Yes | |
| navigationType | string | No | Tells the app which screen to open when the notification is tapped |
| navigationId | string | No | Optional ID passed alongside navigationType, e.g. a specific record to open |
Response (200)
{
"devicesNotified": 2
}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.
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 / property | Expected value |
|---|---|
| Signature | Valid against the current key published at /.well-known/jwks.json |
| iss | MusterHub |
| aud | MusterHubApp |
| exp | Access tokens are short-lived (30 minutes) — reject anything expired, don't extend it yourself |
| sub | The MusterHub user's ID |
| org_id | The user's MusterHub service (organisation) ID — scope everything you do to this |
| entitlements | Array of bolt-on module codes enabled for the whole organisation (e.g. rota) |
| module_admin | Array of module codes this specific person administers, a subset of what your service has enabled |
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.
| Code | Meaning |
|---|---|
| 200 | Success. Check the warnings array in the response body for partial issues that did not block the overall request. |
| 400 | Bad request: a required field is missing or a value is invalid (e.g. an unknown appliance status string). |
| 401 | Unauthorised: 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. |
| 404 | Not found: a referenced entity does not exist in MusterHub (e.g. an unknown callsign or station code passed as a required field). |
Getting started
- Sign in to your MusterHub admin portal and go to Settings > API Keys.
- Click Generate new key, give it a name (e.g. "Capita Parity"), and select the scopes you need.
- Copy the key immediately: it is shown once only and cannot be retrieved afterwards.
- Configure your external system to send requests to
https://api.musterhub.appwith theX-Api-Key: <your-key>header. - Send a test request with
"isTest": true(for turnout) or a single record (for imports) and verify the response.
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:
- Email: hello@musterhub.app
- Phone: 07400 129 746