EARLY ACCESS Muster is being validated by pilot departments. Schedule a walk-through →

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. Contact integrations@safesignals.io to 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