Getting started — Muster public API
Time to first call: ~15 minutes assuming you already have a Muster-issued license JWT.
Audience: integrator engineers wiring their platform to
api.safesignals.io for the first time. If you're a customer
chief looking to authorize an integrator, the workflow is
different — see the admin console's Integrations tab.
1. Prerequisites
- A Muster-issued license JWT with
aud: api.safesignals.io. Contactintegrations@safesignals.ioto complete Muster-side registration and receive one. This JWT identifies your integration (e.g., "ESO Records Integration v1.2"), not a specific customer department. - At least one customer department has clicked "Authorize Integration" for your integration in their admin console. Your JWT's Layer 1 reads only resolve for authorized departments.
- A HTTPS client. The examples below use
curl, the Dart SDK, and (soon) Python + TypeScript.
2. Sanity check — the health endpoint
The /health endpoint is unauthenticated. If it returns 200,
your network can reach the API.
$ curl https://api.safesignals.io/health
{"status":"SERVING_STATUS_SERVING","server_version":"muster_api …","api_schema_version":"1.0-scaffold"}
3. Verify your JWT — trust-keys endpoint
The trust-keys endpoint returns Muster's currently-active + historical Ed25519 signing keys. Downloading it validates that your JWT parser can verify against a real deployment's keys.
$ curl https://api.safesignals.io/v1/trust/keys
{"keys":[{"key_id":"k1","public_key_pem":"-----BEGIN PUBLIC KEY-----\n…","active_from":"2026-01-01T00:00:00Z"}]}
4. Your first authenticated call — listIncidents
Pick a department ID that has authorized your integration. For
your first call, use the sandbox environment at
api-sandbox.safesignals.io — same schema, fake data.
Dart SDK:
import 'package:muster_api_client/muster_api_client.dart';
Future<void> main() async {
final client = MusterApiClient(
bearerToken: '<your JWT>',
baseUrl: Uri.parse(kMusterApiSandboxBaseUrl),
);
final incidents = await client.listIncidents(
departmentId: 'dept-sandbox-belle-valley',
);
for (final i in incidents) {
print('${i.number} — ${i.status}');
}
client.close();
}
curl:
$ curl -H "Authorization: Bearer <your JWT>" \
https://api-sandbox.safesignals.io/v1/departments/dept-sandbox-belle-valley/incidents
{"incidents":[{"incident_id":"inc-1","number":"26-0001","status":"terminated",…}]}
5. Read one incident's event log
Given an incident ID from step 4, pull its full event log:
$ curl -H "Authorization: Bearer <your JWT>" \
https://api-sandbox.safesignals.io/v1/departments/dept-sandbox-belle-valley/incidents/inc-1/events
{"events":[{"event_id":"e-1","kind":"IncidentCreated","occurred_at":"…","payload_json":{…}}, …]}
Every event's payload_json carries the event-specific
fields; the outer envelope is uniform.
6. Push events (Layer 2 — write)
If your integration is authorized to write, POST a batch:
$ curl -H "Authorization: Bearer <your JWT>" \
-H "Content-Type: application/json" \
-d '{"events":[{"event_id":"e-42","incident_id":"inc-1","kind":"IncidentCreated","occurred_at":"2026-07-06T12:00:00Z","payload_json":"{\"number\":\"26-0042\"}"}]}' \
https://api-sandbox.safesignals.io/v1/departments/dept-sandbox-belle-valley/events
{"accepted_count":1,"rejected_count":0,"rejected":[]}
Rows Muster rejects come back with a per-row reason explaining
why — most commonly, the event kind's capability isn't in your
producer_capabilities. See the
capability list.
7. Subscribe to live events (Layer 3)
Open a WebSocket to the subscription endpoint:
$ websocat wss://api-sandbox.safesignals.io/v1/departments/dept-sandbox-belle-valley/incidents/inc-1/subscribe \
-H "Authorization: Bearer <your JWT>"
{"type":"event","event_id":"e-1","kind":"IncidentCreated",…}
{"type":"event","event_id":"e-2","kind":"UnitCheckedIn",…}
{"type":"snapshot_end"}
{"type":"event","event_id":"e-3","kind":"AssignmentMade",…} ← live tail
Full lifecycle in the subscription guide.
8. Next steps
- Read the authentication guide — how the two-step onboarding works, revocation, key rotation.
- Skim the reference — every REST endpoint.
- Flip your JWT from sandbox to production when you're ready.
Common errors
| Symptom | Cause | Fix |
|---|---|---|
401 missingBearer |
No Authorization header | Add Authorization: Bearer <jwt> |
401 invalidJwt |
JWT signature verify failed | Check that your JWT was issued by Safe Signals |
401 wrongAudience |
JWT's aud isn't api.safesignals.io |
Request a fresh JWT — the audience claim is set at issuance |
| 403 | JWT good but department not authorized | Ask the customer chief to click "Authorize" in their admin console |
| 429 | Rate limited | Read the Retry-After header + back off |
| 5xx | Server side | Retry with exponential backoff |