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.
{ "ok": true, "id": 6 }
{ "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
| Status | Meaning |
|---|---|
| 400 | Missing a required field - see each endpoint above. |
| 401 | X-XonSight-Key missing or wrong, and the portal has a key configured. |
| 200 | Always {"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.