XonSight
Home Guide Staff login

API reference

Xon Sight's public API is a small set of inbound webhooks - a FiveM resource (or any bridge script) POSTs to these to push evidence, a live roster, alerts, and non-bodycam feed pins into a portal. There's no public read API: everything you can query back (evidence lists, cases, etc.) lives behind a logged-in session and isn't meant for third-party integration.

Authentication

Every endpoint below accepts either header - checked in this order:

X-XonSight-Token: <your community's API token>

Per-community (get one from /admin/communities, admin-only). Routes this request's reads/writes to that community's own database. An unrecognized token is always a hard 401 - it never falls back to the header below.

X-XonSight-Key: <shared deployment secret>

The original single shared secret (XONSIGHT_WEBHOOK_KEY in the portal's .env) - still fully supported for any resource not yet updated to use a community token. If the portal was deployed with no key configured, this header is optional (matches Xon Sight's own documented "optional key" behavior) - not recommended for anything internet-facing. A wrong or missing key when one is configured returns 401.

POST /api/xonsight/evidence

Documented Xon Sight contract. One call per recorded video part.

Submits one piece of evidence. The portal never receives video bytes - only a link, so videoUrl must already be reachable over https from wherever the portal runs. Calling again with the same id updates that item in place (used for retries) rather than creating a duplicate.

{
  "id": 1000042,                              // integer, unique per item - re-posting the same id updates it
  "submitterIdentifier": "char1:a1b2c3...",   // in-game identifier, opaque to the portal
  "submitterName": "Officer Doe",
  "caption": "Officer Doe · LSPD · part 1 · 21:08",
  "videoUrl": "https://your-cdn.example.com/clip.webm",
  "createdAt": "2026-10-19T21:08:28Z",         // ISO 8601, UTC
  "deviceSerial": "X00012345"                 // optional extension - fills the Metadata tab's device serial
}

Required: id, videoUrl. Everything else defaults to blank if omitted.

200 OK
{ "ok": true, "id": 6 }
400
{ "error": "id and videoUrl are required" }

POST /api/xonsight/recorders/sync

Extension - not every Xon Sight deployment implements this.

Pushes the full current roster of who is actively recording, for the Live page's map and sidebar. This is a replace, not an append: any recorder id missing from the array is immediately removed from /live - send an empty array (or call this on an interval) once nobody is recording, don't just stop calling it.

{
  "recorders": [
    {
      "id": "bc-12-1792440000",        // stable per recording session
      "name": "Officer Doe",
      "job": "LSPD · Captain",
      "status": "recording",          // "recording" | "buffering" - omit for "recording"
      "startedAt": 1792440000,        // unix seconds
      "lastFrame": {                  // position/heading sample - omit while none has arrived yet
        "x": 215.4, "y": -800.1, "z": 29.2,
        "heading": 134.0, "pitch": 0.0, "fov": 50.0
      }
    }
  ],
  "syncedAt": 1792440005
}

lastFrame is a position/orientation sample, never a video frame or stream - Xon Sight has no live video feed to give a website. The Live page draws it on a schematic grid of in-game world coordinates, not a real map.

{ "ok": true, "count": 1 }

POST /api/xonsight/alerts

Extension - not every Xon Sight deployment implements this.

Raises a flashing banner on the Live page for an emergency trigger (weapon drawn, shots fired, a panic button - whatever you wire up). Fire-and-forget: there's no roster concept here, each call creates one alert a staff member acknowledges from the portal.

{
  "recorderId": "bc-12-1792440000",
  "recorderName": "Officer Doe",
  "type": "weapon_drawn",      // free-form slug - shown as-is on the banner ("weapon drawn")
  "message": "Weapon drawn"    // optional human-readable detail
}

Required: recorderId, type.

{ "ok": true, "id": 1 }

"type": "shots_fired" specifically also raises a persistent, acknowledge-required banner on every page of the portal (not just /live) for every logged-in supervisor or admin account - set a user's role to supervisor from /admin/users to opt them into this. Every other alert type still only shows as the normal toast notification + the /live page banner, same as before. This is polling-based (checked every ~4s, or immediately on the next real-time push), not a push notification to a device - nothing here sends an SMS/text message.

POST /api/xonsight/external-feeds/sync

Extension - not every Xon Sight deployment implements this.

An Axon Fusus-style "single pane of glass" layer: pins non-bodycam assets (fixed cameras, sensors, drones) on the Live map. Same full-replace semantics as recorders/sync - anything missing from the array is removed.

{
  "feeds": [
    {
      "id": "cam-1",
      "label": "Vinewood Blvd Cam",
      "kind": "camera",          // "camera" | "sensor" | "drone" | "other"
      "status": "online",        // "online" | "offline"
      "position": { "x": 215.0, "y": -800.0 }
    }
  ]
}
{ "ok": true, "count": 1 }

Errors

StatusMeaning
400Missing a required field - see each endpoint above.
401X-XonSight-Key missing or wrong, and the portal has a key configured.
200Always {"ok": true, ...} on success - none of these endpoints return partial success.

What's coming

Per-community tokens and isolated databases are live for the webhook endpoints above (/admin/communities). Still ahead: reaching a community by its own custom domain or an OPS subdomain instead of only by token (blocked on wildcard DNS actually resolving), and the browser-facing app (dashboard, evidence viewer, cases, assistant) becoming tenant-aware too - today it only ever shows the control-plane database's data, regardless of any community token used against the webhooks.