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

Authentication guide — Muster public API

1. TL;DR

  • Every authenticated call sends Authorization: Bearer <jwt>.
  • The JWT is issued by Safe Signals at integration-registration time. Contact integrations@safesignals.io to register.
  • The JWT identifies your integration, not a specific department. Per-department authorization is a separate step the customer chief performs in their admin console.
  • Sandbox JWTs verify with the same substrate but authorize against fake departments at api-sandbox.safesignals.io.

2. JWT anatomy

Muster license JWTs are RS256-signed. Header:

{"alg":"RS256","typ":"JWT"}
              

Payload (excerpt — see proto/muster/v1/ for the authoritative field list):

{
                "iss": "licensing.safesignals.io",
                "aud": "api.safesignals.io",
                "sub": "integration:eso-records-v1.2",
                "iat": 1735689600,
                "exp": 1893456000,
                "jti": "jwt-uuid-here",
                "tier": "tier2",
                "features": ["api_read_v1"],
                "department": {"id":"integration_root","name":"ESO Records","country":"US"}
              }
              

Verify the signature against the public keys returned by GET /v1/trust/keys (see below).

3. The two-step onboarding

Step 1 — Muster-issued integration credential (once per integrator)

Contact integrations@safesignals.io. Include:

  • Legal integrator name (e.g., "ESO Solutions LLC").
  • Integration display name (e.g., "ESO Records Integration v1.2").
  • Producer capabilities you'll write to Muster (e.g., ingest.incident_created, ingest.unit_checked_in).
  • Consumer capabilities you'll read from Muster (e.g., read.incident_events, read.neris_projection).
  • Contact email for incident notifications.

Safe Signals issues you one integration-scoped JWT. It expires after 1 year and is renewed manually — Safe Signals will contact your integration email 30 days before expiry to coordinate the rotation.

Step 2 — Chief-issued per-department authorization

For each customer department where you want to consume data, the department's chief goes to admin console → Integrations → finds your integration in the marketplace → clicks "Authorize" → confirms which capabilities to grant.

Muster stores an authorized_integrations doc at departments/{deptId}/authorized_integrations/{integrationId}. Your JWT's Layer 1 list/get calls only resolve for authorized departments. Chief can revoke at any time.

How the gate works. On every request whose URL targets a department other than the one your JWT is scoped to (e.g., your integrator JWT is scoped to integrator-root and you're reading /v1/departments/dept-a/…), the server:

  1. Resolves your JWT's sub claim to your registered integration id via the integrations registry.
  2. Reads departments/{deptId}/authorized_integrations/{integrationId}.
  3. Missing → 403 with the message Integration "{id}" is not authorized for department "{dept}". The customer chief must click "Authorize" on this integration in their admin console → Integrations tab.
  4. revoked_at field set → 403 with the revocation timestamp.
  5. Present and not revoked → the request proceeds to the normal Layer 1 read path.

Gate lookups are cached with a 1-minute TTL, so new authorizations take effect within a minute of the chief clicking "Authorize". Revocations take effect on the same TTL.

Operator surface. Chiefs manage per-department authorizations from the admin console's Integrations page — "Authorized integrations" panel. Under the hood this hits three endpoints on admin.safesignals.io:

  • GET /v1/admin/authorized-integrations?department_id=… — list every integration currently authorized (or revoked) for the department.
  • POST /v1/admin/authorized-integrations — grant. Body: {"department_id": "…", "integration_id": "…"}. Idempotent: re-authorizing an active integration is a no-op; re-authorizing a revoked one clears the revocation.
  • DELETE /v1/admin/authorized-integrations/{integrationId} — revoke. Body: {"department_id": "…"}. Preserves the original authorized_at for audit; sets revoked_at and revoked_by_uid. Re-revoking is a no-op.

All three require chief / admin role in the target department (cross-department escalation is refused). Every grant + revoke is audit-logged (action: integrationAuthorized / integrationRevoked).

Customer-scoped JWTs bypass the gate. Muster's own mobile app, admin console, and firefighter portal use JWTs scoped directly to a specific customer department (department.id matches the URL). Those requests never invoke the gate — the same-dept check is the entire enforcement.

Rule 25 is preserved end-to-end: each department decides which integrator sees their data.

4. Auth failure modes

Status auth_rejection_code What it means
401 missingBearer No / malformed Authorization header
401 invalidJwt JWT signature or structural validation failed
401 wrongAudience aud claim isn't api.safesignals.io
401 revoked JTI is on the revocation blocklist
401 featureNotAvailable JWT valid but tier lacks api_read_v1
403 (no auth_rejection_code; response body's reason) Department not authorized for your integration

5. Verifying event signatures

Every event has an Ed25519 signature from the originating device. To independently verify an event you retrieved from Layer 1 or Layer 3:

  1. GET /v1/trust/keys — returns Muster's current + historical Ed25519 signing keys (which Muster uses to sign its own artifacts — archives, license JWTs, etc.).
  2. Per-event device signatures are exposed as payload_json.signature when the client requests it via ?include_signatures=1 (Layer 1 read). Compare against the event's origin_device public key (surface in the admin console under Devices → Public Keys — accessible via Layer 1 only where the customer has authorized this).

6. Rotating your JWT

Contact integrations@safesignals.io when:

  • Your integration's private key is suspected compromised.
  • Your JWT expires (Safe Signals sends a renewal email 30 days before expiry).
  • You want to declare additional producer / consumer capabilities and need a new capability set.

Rotation is manual — no self-service in Phase 7. When your new JWT lands, discard the old one in your codebase. Safe Signals does NOT retire the old JWT immediately unless you request revocation; overlap avoids race conditions on long-lived callers.

7. Muster key rotation

Muster's issuer key rotates on a documented schedule (roughly annually or on suspected compromise). Older JWTs remain verifiable via the historical public keys from GET /v1/trust/keys. Your caller code should:

  1. Fetch /v1/trust/keys on startup.
  2. Cache the response for up to 1 hour.
  3. On JWT verification failure, refetch (the key may have rotated since your last fetch).

8. Sandbox vs. production

Sandbox Production
Base URL api-sandbox.safesignals.io api.safesignals.io
Data Fake departments, fake incidents Real customer data
JWTs Separately-issued sandbox JWT Production JWT
Rate limits Higher (integration testing tolerated) Per your tier
SLA Best-effort See status.safesignals.io/api
Signing keys Sandbox-signed Production-signed

Sandbox JWTs never work against production and vice versa — they use different signing keys.