Docs

API & data reference

The external REST API, outgoing webhook payload formats, and exactly what a config export/import does and doesn't carry. Written for the person scripting against Islandr, not for clicking through the Admin Console.

External automation API

REST API

A separate, versioned facade under /api/external/v1/ — deliberately not the same endpoints the Admin Console's own UI calls. The internal API is free to reshape itself as the UI evolves; this one is a stability-committed contract for scripts, CI pipelines, and infrastructure-as-code.

Authentication

API keys, not session cookies. Create one in the Admin Console under API Keys — the raw key is shown exactly once at creation, never again. Send it as a bearer token:

terminal
curl -H "Authorization: Bearer islandr_<your-key>" \
     https://your-islandr-host/api/external/v1/peers

A key acts with the same privileges as the admin who created it — v1 has no per-key scoping yet. Revoking a key is immediate and permanent (revoked keys are kept, not deleted, for the audit trail).

Endpoints (v1)

MethodPathNotes
GET/peersEvery peer, newest first.
POST/peersCreate a client peer (pass userId as a query parameter) or a site gateway peer (omit userId, set "type": "site" in the body). The response includes the generated private key material exactly once.
GET/usersEvery user.
GET/sitesEvery site.
GET/resourcesEvery resource, across all sites.
GET/rolesEvery role.

Deliberately small on day one — read-mostly access to what automation actually needs, not a mirror of every internal endpoint. Each additional facade endpoint is an incremental, reviewable addition to the same pattern.

OpenAPI spec

The full machine-readable spec (hand-written, not runtime-generated — it never drifts from an annotation processor's idea of the API, and adds nothing to the native-image build) is served directly by any running instance, no authentication required:

terminal
curl https://your-islandr-host/api/openapi.yml

Feed it to openapi-generator, Postman, or any OpenAPI 3.0.3-compatible client generator. It's also linked from the API Keys page in the Admin Console.

Turning the facade off

An admin who never intends to use external automation can disable the whole facade — every /api/external/v1/* route then returns 404 (not 401/403, so it doesn't even confirm the surface exists) regardless of an otherwise-valid key. Toggle in Settings → External automation API.

Outgoing notifications

Webhooks

Configured per-URL in the Admin Console under Webhooks, each with its own filter of which event types it receives. Delivery: up to 3 attempts with backoff (2s, 8s) — a receiver that's down for a minute still gets the event once it's back. Two payload formats, chosen per webhook.

Event types

KeyFired when…
peer.connectedA peer establishes a handshake after being offline.
peer.disconnectedA previously connected peer goes stale/silent.
peer.enabledA peer is enabled — by an admin or the Peer-Scheduler.
peer.disabledA peer is disabled — by an admin or the Peer-Scheduler.
acl.grant_createdA resource access grant is created.
acl.grant_revokedA resource access grant is revoked.
discovery.scan_completedA network discovery scan finishes.
acme.cert_renewedThe ACME/Let's Encrypt certificate renews successfully.
acme.cert_renewal_failedAn ACME renewal attempt fails.

Format: generic (default)

Islandr's own JSON envelope, HMAC-SHA256-signed so the receiver can verify authenticity:

POST body
{
  "event": "peer.connected",
  "timestamp": "2026-08-23T07:12:04.331Z",
  "actor": "admin",
  "target": "Peer:office-laptop",
  "data": { "peerId": "3f1c…" }
}

Sent with two headers:

HeaderValue
X-Islandr-Signaturesha256=<hex hmac> — HMAC-SHA256 of the raw request body, keyed with the webhook's own secret (shown once at creation, rotatable afterwards).
X-Islandr-DeliveryA random UUID, unique per delivery attempt — use it to deduplicate retries on the receiving end.
Verify the signature by computing HMAC-SHA256(secret, raw_body) yourself and comparing hex strings — constant-time compare, don't just == it.

Format: Gotify

Renders Gotify's native push shape instead, and posts straight to its /message endpoint — no separate receiver needed on your side if you already run a Gotify server:

POST body
{
  "title": "islandr — Peer connected",
  "message": "**Peer:office-laptop**\n- peerId: `3f1c…`",
  "priority": 5,
  "extras": { "client::display": { "contentType": "text/markdown" } }
}

Priority is 8 (not 5) for acme.cert_renewal_failed and peer.disconnected — high enough to break through Do-Not-Disturb if the Gotify client is configured that way. No X-Islandr-Signature header is sent for this format — Gotify has no signature-verification story, and its own app token (configured as the webhook's "secret") already authenticates the request via a ?token= query parameter.

Extra auth header (either format)

If the receiving end — or a reverse proxy in front of it — needs its own auth header (Authorization, X-API-Key, anything), configure one optional name/value pair per webhook. It's sent on every delivery alongside whatever the format above already sends, never as a replacement for it.

Backup & migration

Config export/import

A versioned JSON snapshot of the instance's configuration — for backups and for moving an install to new hardware. Downloaded/uploaded from the Admin Console's Settings page. New fields are added over time; an older export missing a field imports with a sensible default rather than failing.

The envelope carries a version (the format), an appVersion (which Islandr wrote the file, informational only) and exportedAt. Older files import into newer Islandr; a file written by a newer Islandr is refused rather than imported, because importing it would silently drop the fields that build doesn't know about and restore a configuration quietly different from the one backed up. The refusal happens before anything is touched, so a rejected import leaves the running configuration intact.

Included

  • All global settings (WireGuard subnet/MTU/keepalive, tunnel mode, DNS resolver config, hub map location, feature toggles like selfServicePeerCreation and the external API's own enable/disable switch, …) — except the handful called out below as host-specific.
  • Users, roles, role memberships.
  • Peers — including their Peer-Scheduler state (validUntil, enabledSource, and any recurring weekly enable/disable window).
  • Sites, resources, resource ports, port groups and their members — including a port's exclusive-capacity configuration (how many people may hold it at once, the ceiling on a single reservation, and whether requests are auto-approved).
  • Every permanent ACL grant — role-based, type-based, and direct user/site grants — plus their per-port links.
  • The two fixed OIDC providers' configuration (Microsoft 365 and Google Workspace: client ID, client secret, tenant/allowed domains).
  • Custom OIDC providers added via the generic OIDC flow (Auth0, Okta, Keycloak, or any other issuer) — including discovered endpoints, so a restored instance doesn't need network access to the IdP just to log in again immediately.
  • External-API keys. Only the SHA-256 hash and non-secret prefix ever exist — same as at rest — but that's still enough: an already-issued key keeps authenticating after a restore, since verification only ever needs the hash, never the raw value. Revoked keys stay revoked.
  • Private key material, only when the export is explicitly requested with includePrivateKeys=true — off by default.

Not included — and why

  1. Ad-hoc temporary direct grants (an access grant created with an expiry). These are meant to expire on their own; restoring a backup should never resurrect one that may already be stale by the time the backup is applied. Only permanent direct grants travel with an export.
  2. Live port reservations. Who currently holds an exclusive port, and until when. Same reasoning as the temporary grants above: a reservation is a running session with an end time, and a restore should start with nobody holding anything rather than reinstating somebody's half-finished slot from whenever the backup was taken. The ports' capacity configuration does travel; only the claims on them don't.
  3. Webhooks (URLs, secrets, event filters) — not yet part of the export at all.
  4. Sessions. Ephemeral by nature; nothing to restore.
  5. TLS/ACME certificate state (certificate, private key, ACME account, pending challenges) — tied to this specific host's domain and network position, not portable to another machine.
  6. Google Workspace directory-import credentials (the service account JSON) — a host-specific secret, re-entered per install rather than carried in a backup file.
  7. The hub's own WireGuard identity (server public key, public endpoint) — deliberately kept as the target host's own values on import, not overwritten by the source host's identity.
The webhooks item is a known gap, not a permanent decision — tracked for a future release. Everything else above is either genuinely host-specific (won't ever be exported) or deliberately excluded on purpose (temporary grants, live reservations, sessions).

Get started

One binary, one command — home in under a minute.

copied