Sal's Kewl Korner Documentation / Sal's Kewl CAD Discord

Sal's Kewl CAD — owner's manual

A hosted CAD and records system for FiveM roleplay communities. Dispatchers work in a real browser with pop-out windows across as many monitors as they have; officers, deputies, medics and firefighters work from an in-game terminal or the same browser app. This manual covers setting the community up, running a shift, every setting on the admin side, the game bridge, and the API for people writing their own scripts.

documents resource version 2.1.0 · bridge 2.2.0 ↓ Download as PDF

Overview

Sal's Kewl CAD is a hosted computer aided dispatch and records management system for FiveM roleplay communities. It is two pieces that are bought and run as one thing:

  • The web platform — a browser application at your own address, yourcommunity.cad.salskewlscripts.com. It holds every call, every unit, every person, vehicle, report, citation, warrant and case your community has. This is the system of record, and it runs on our infrastructure rather than yours.
  • The game bridge (sals_kewlcadbridge) — one resource that drops into your server's resources/ folder. It reports what happens in game and carries dispatch's answers back. It stores nothing and decides nothing.

Four groups of people use it, and each gets a different screen:

WhoWhere they workWhat they get
Dispatchers A browser, at /cad The dispatch workspace: a docking layout of panels that can be tabbed, split, floated or popped out into their own browser windows across as many monitors as they have.
Field units
officers, deputies, troopers, medics, firefighters
In game (F6 or /mdt), or the same browser app at /mdt The terminal: their calls, their status, lookups, reports, the fireground. A report started in a patrol car is finishable on a laptop, because it is the same form and the same draft.
Players In game (/character), or /portal The citizen portal: their characters, vehicles, licenses, tickets to pay, court dates and job applications.
You, the community owner /admin The owner panel: branding, agencies, personnel, roles, the game server link, integrations, the realism toggles, the audit log and billing.
The dispatch workspace
The dispatch workspace. Pending queue and unit board on the left, the call being worked in the middle, the map and alerts on the right — and every one of those is a panel that can be dragged somewhere else or popped out to its own monitor.

What makes this different from a CAD you self-host

Three things, and they are the reasons the architecture is the way it is.

It does not go down when your server does. The platform is the system of record. Your dispatchers keep working while the game server restarts, a wipe never takes the records with it, and the call history survives you moving hosts.

Pop-out windows are real browser windows. A dispatcher with three monitors opens the map on one, the unit board on another and the call they are working on the third. These are genuine windows at genuine URLs, so they land where the operating system puts them and survive the first window navigating away. Five windows still share one connection to the platform — see Realtime rooms & gap replay.

Your server opens no ports. The bridge dials out over HTTPS. Nothing connects in, so there is no firewall change, no port forward and nothing for somebody to find with a port scanner.

What this manual covers

Everything, in roughly the order you will need it. Sections 1 to 10 are the admin side: what the platform is and how to set your community up. Sections 11 to 13 are the game bridge. Sections 14 to 32 are daily use, for dispatchers, field units and players. Sections 33 to 37 are for people writing their own scripts against it. The rest is reference and troubleshooting.

If you are setting up for the first time, read Setting up your community and Linking your game server, in that order, and skip the rest until something stops making sense. Most communities are running a shift within fifteen minutes of their first sign-in.

How it fits together

Worth understanding before you put it on a production server, because it explains most of the behavior you will meet later.

The platform is the system of record

Everything is stored on the platform. The bridge holds nothing — no database, no save file, nothing to back up. When a unit goes on duty in game, the bridge sends "this player went on duty" and the platform decides what that means: which unit they stepped into, whether their role allows it, what their status becomes, and who gets told.

That split is strict and it is deliberate. The bridge never decides, never caches an answer it was not given, and never accepts a client's word for anything that matters. An officer's game client cannot tell the server it has a permission; it can only ask, and the platform answers.

The five parts of the platform

PartWhat it does
API Every domain rule and every write. If something was created, changed or deleted, this did it.
Gateway The realtime connections — browsers, in-game terminals and game servers. A separate process on purpose: a dispatcher with five pop-out windows on a slow connection must not be able to hold a database transaction open, and the API must be able to restart without every board in every community going blank.
Worker Background jobs: Discord role sync, data import and export, and the commands that have to survive an outage.
Web app The dispatch workspace, the terminal, the citizen portal and the owner panel. One bundle, four surfaces — which is why a report started in the car opens on a laptop as the same draft.
Database PostgreSQL with PostGIS. Every table that holds community data carries a tenant id, and the database itself refuses to show one community's rows to another — see Security & isolation.

How a call gets from the game to a dispatcher's screen

Take an officer typing /ts at a traffic stop. The whole path, in order:

  1. The bridge reads the plate of the vehicle in front of the car and sends call.create.v1 up its outbound connection, signed.
  2. The gateway checks the signature and hands the event to the API. The gateway deliberately knows nothing about what the event means.
  3. The API finds which CAD account those game identifiers belong to, which unit that member is in, and whether their role allows creating a call. It assigns a call number from a database sequence, works out the nearest postal, decides which agencies it routes to, attaches the officer, and writes the whole thing in one transaction.
  4. The API publishes the new call to the calls room.
  5. Every browser subscribed to that room — every dispatcher, and every in-game terminal showing the board — receives it and the queue redraws. No refresh, no polling.
  6. The platform sends an acknowledgement back down to the bridge, which raises sals_kewlcad:eventResult inside FiveM so your other resources can react.

A dispatcher clicking + Call in the browser takes the same path from step 3 onward. That is the point: there is exactly one set of rules about numbering, routing, timelines and who gets told, rather than one set for the game and a second for the web that slowly drift apart.

What happens when the link drops

Nothing breaks, and this is worth knowing because it will happen.

  • In game: status changes, notes and calls queue up in memory and replay in order when the connection returns. The HUD says CAD offline so units know why nobody is answering rather than assuming dispatch is ignoring them. QueueDepth() tells a script how far behind it is.
  • In the browser: dispatchers keep working. The platform never needed your game server to be up. Commands headed for the game that must survive an outage — a fine, a sentence, a license suspension — go through a job queue with retries. Commands that are only useful immediately, like an assignment notification, are dropped deliberately: one that arrives twenty minutes later is worse than one that never arrives.
  • On reconnect: a browser tells the gateway the last sequence number it saw for each room and is sent what it missed. Past 500 events it is told to resync — refetch from the API — rather than being replayed every frame, which is both cheaper and leaves it in a known state either way.

Where the game is the truth, and where the CAD is

Both systems hold some of the same facts. The split is fixed, and the platform enforces it:

The game decidesThe CAD decides
Character names, dates of birth, gender Everything a player fills in about themselves: address, phone, emergency contact, medical details, appearance
Jobs and grades Which agency and role those map to
Which vehicles a character owns, and their plates Whether a vehicle is stolen, flagged or impounded
Whether a license exists Whether that license is valid, suspended or revoked
Where a unit physically is What its status is, what call it is on, and who it is

So a name that is wrong in the CAD is fixed in your character creator, not in the CAD; and a suspension handed down in the CAD reaches the character in game on its own. See Licenses & the DMV.

Requirements & plans

What you need

  • A subscription. Every plan starts with a 14-day free trial and everything carries over if you subscribe — the trial is the real product with smaller limits, not a demo.
  • A FiveM server with Lua 5.4. Any artifacts from the last couple of years. The bridge's manifest sets lua54 'yes'.
  • A browser, for the dispatchers. Any current one. Pop-out windows work everywhere; nothing here needs an extension or a desktop app.
  • Nothing else. No framework is required and the bridge lists no dependencies at all — see Linking your game server.

Frameworks

Works with qb-core, qbx_core, es_extended, ND Core or standalone. Detection happens at runtime, and there is nothing to configure. On standalone there is no character data to sync, so your people live in the CAD and are typed in or created through the citizen portal instead.

The plans and what they limit

All four plans include the whole CAD. The differences are capacity and a short list of features, not a crippled dispatch screen.

LimitTrialCommunityProNetwork
Game servers11325
Agencies310100500
Members251505005,000
Attachment storage250 MB5 GB25 GB250 GB
Bridge events per minute, per server6003,00010,00060,000
REST API calls per minute603001,2006,000
Outbound webhooks—320100
Audit history you can read back14 days90 days1 year3 years
Trial length14 days14 days14 days30 days
FeatureTrialCommunityProNetwork
Pop-out windowsyesyesyesyes
Discord login and role sync—yesyesyes
Sal's Kewl script integrations—yesyesyes
Build your own report forms——yesyes
Your own domain——yesyes
Public REST API and webhooks——yesyes
Response-time and activity dashboards——yesyes
White label———yes
Dedicated database———yes
Priority support with an agreed response time———yes

Who each is meant for: Community is a typical roleplay server. Pro is a large server, or a community running more than one. Network is for multi-server networks.

Your current usage against each limit is on the first page of the owner panel, and a figure turns amber past 85% so you find out before a member cannot be invited rather than after. Hitting a limit refuses the one action; it never stops dispatch.

Billing

Subscriptions, cards, invoices and tax are handled by Stripe, and the Billing page in the owner panel sends you to Stripe's own customer portal for anything to do with payment details. The only thing the platform keeps is which plan you are on.

That matters during an outage: if Stripe is unreachable, your limits stay exactly as they were. Nothing about dispatch depends on a payment processor being up.

Setting up your community

The Getting set up checklist on the first page of the owner panel is the short version, and it ticks itself off as you go. There are four items.

1. Sign in

Your community has its own address — yourcommunity.cad.salskewlscripts.com — and the sign-in page shows your community's name and branding before anybody has authenticated. That is deliberate: landing on a generic login when you typed your own community's address feels like the wrong website.

The sign-in page
Sign-in. The page says nothing about whether a given email address exists, on purpose — that is how an attacker enumerates your members.

2. Pick your agencies

Agencies come from templates with ranks, units, callsign conventions and roles already filled in, so you are never looking at an empty screen. Three starter bundles:

BundleWhat it creates
Los Santos county starter City police, county sheriff, state police, fire, EMS, tow, courts and the civilian portal, all sharing one dispatch. The one most communities want.
Small server starter One police agency, EMS, and the civilian portal. Nothing else.
Everything on Every template, including federal, corrections, DMV, park rangers and DOT.

Pick a bundle or choose agencies individually; either way every name, rank, color and callsign is yours to rename afterwards. See Agencies, ranks & units.

Add the Civilian Services agency even if you think you will not use it. It is what the citizen portal hangs off, and it costs nothing — it has no units and no callsigns.

3. Link your game server

One resource, one key, and your server dials out so you open no ports. The whole procedure is Linking your game server.

4. Invite your staff

Either invite people by email from Personnel, or map your Discord roles to agencies and roles and let that do it — see People, invitations & signing in.

5. Turn on two-factor for yourself

Required for owners, and the checklist will keep nagging until it is done. It is not bureaucracy: an owner account holds every permission inside the community, and somebody who takes one over can read every record in it. Authenticator app, from your profile.

What you get out of the box

Provisioning a community writes a working configuration, not an empty shell. Every one of these is editable afterwards and nothing reads the templates again once your rows exist:

  • Agencies with ranks, starting units, callsign patterns and a color used on the map, the roster and unit chips.
  • Roles — a member role, a supervisor role and a command role per agency, each with a sensible permission bundle. See Roles & permissions.
  • Status codes — plain words by default, ten-codes if you prefer them. See the appendix.
  • Call types matched to the agencies you turned on: a fire department gets structure fires and alarm activations, a sheriff's office gets warrant service.
  • Response plans — first, second and third alarm cards for fire, and response levels for EMS. See Run cards & response plans.
  • Report forms for every report type, deliberately short.
  • A penal code with charge classes, fines and jail time.
  • Alert rules so a robbery script plugged in on day one produces sensible dispatch behavior with no configuration. See Integrations & alert rules.

Branding

On the Overview page: your community's display name, logo, favicon, accent color, default theme and a line of text on the login page. On Pro and above you can serve the whole thing from your own hostname; on Network the branding replaces ours throughout.

The accent color is used for emphasis across the whole app, so pick one that reads against both the dark and light themes — the theme is each person's own choice and your default is only a default.

Agencies, ranks & units

An agency is a department: a police department, a sheriff's office, a fire department, a court. It owns ranks, roles, units and stations, and it has a department type that decides what the platform offers it.

Department types

The type is not cosmetic. It decides which record types the agency can reach, which report forms it gets, whether run cards and station alerting appear, and whether its members can read criminal history or medical history.

TypeWhat it means
Dispatch / CommunicationsRuns the board. Not a field agency.
City PoliceLaw enforcement.
County SheriffLaw enforcement. Usually also the jail and warrant service.
State Police / Highway PatrolLaw enforcement.
Federal AgencyLaw enforcement. Can start hidden from other agencies' maps.
Game Wardens / ParksLaw enforcement.
Coast Guard / MarineLaw enforcement.
Air OperationsLaw enforcement.
FireReads patient medical details, gets run cards, station alerting and the NFIRS-style fire report.
EMSReads patient medical details, writes patient care reports, sees the hospital board.
Tow / RoadsideTow rotation, job queue, impound intake and release.
CourtsThe docket, case files, warrant signing and sentencing.
Corrections / JailBooking queue, cell roster and sentence timers.
Licensing / DMVIssues and suspends licenses, registers vehicles.
DOT / Public WorksRoad closures and highway incidents; can draw on the map.
CivilianThe self-service portal your players use.
Business DispatchA civilian business that takes job calls.

The eight law-enforcement types are the ones whose members can read criminal history. Fire and EMS are the two that read patient medical details. Neither gets the other's — a lookup run by a medic shows blood type and allergies and says plainly that the criminal half is not theirs to see, rather than pretending it is empty.

The shipped agency templates

TemplateShortCallsign patternStarting units
County CommunicationsDISPATCHDISPATCH-1Two dispatch positions
Los Santos Police DepartmentLSPD1-ADAM-12Two patrol, watch commander, K9, SWAT element, detectives
Blaine County Sheriff's OfficeBCSOS-213Two patrol, patrol sergeant, K9
San Andreas State PoliceSASPTROOP 2-14Two patrol, motor unit, K9, air support
Federal Investigation BureauFIBFIB-7Detectives, hostage rescue
Los Santos Fire DepartmentLSFDENGINE 7Engine, ladder, rescue, brush, battalion
San Andreas EMSEMSMEDIC 41Two medic units, field supervisor, air rescue
Kewl Towing & RecoveryTOWTOW-3Two tow trucks
San Andreas Superior CourtCOURTJUDGE-1Courtroom 1
Bolingbroke PenitentiaryDOCDOC-12Booking
Department of Motor VehiclesDMVDMV-1Counter 1
San Andreas Park RangersSAPRRANGER-4Patrol, marine
San Andreas DOTDOTDOT-2Closure crew
Civilian ServicesCIV—None

Ranks

Each template ships a rank ladder, lowest to highest — LSPD runs Cadet through Chief of Police, the fire department Probationary Firefighter through Fire Chief, EMS EMT Trainee through EMS Chief. Ranks sort the roster and appear next to a member's name on the unit board and in reports.

A rank is not a permission. Promoting somebody to Sergeant does not let them approve reports; giving them the Supervisor role does. Keeping those separate is what lets a community have a Lieutenant who is not an admin and a quiet Corporal who is.

Units

A unit is a seat, not a person. ENGINE 7 exists whether or not anybody is in it, which is what lets four different firefighters step into it across a week and have the statistics still mean something. Who is in the seat right now is tracked separately, and the unit board shows the crew when you hover the callsign.

Every unit has a kind, which drives its icon on the map, whether a run card can call for it, and how the recommendation engine ranks it:

GroupKinds
Law enforcementpatrol, k9, supervisor, swat, detective, traffic
Fireengine, ladder, rescue, brush, tanker, battalion
Medicalmedic, ambulance
Othertow, air, marine, dispatch, other

Units also carry a capacity — seats — so an engine reads as a crew of four and a patrol car as one or two.

Dispatch-kind units are deliberately excluded from the "dispatch nearest unit" suggestions. A dispatcher is on duty and available, and without that exclusion they would show up in the list looking like the nearest resource, which is the kind of suggestion that makes people stop trusting the whole menu.

Jurisdictions and stations

Two optional pieces of geography, both drawn on the map:

  • Jurisdictions are city, county and state areas. With jurisdiction routing on, a call goes to whoever covers the spot it happened. They nest, so a state trooper's reach covers the counties and cities inside the state. Off — and it ships off — every call lands in one shared queue, which is how most servers play.
  • Stations are firehouses and posts, with a footprint on the map and an apparatus roster. They matter for station alerting and for run cards, where a station's due order is what the department decided in advance. See Station alerting.

Postals

If your community uses a postal map, upload it and every call gets a readable postal attached automatically — the nearest one to where it happened. Dispatchers read postals on the radio far more often than street names, so this is worth the five minutes.

People, invitations & signing in

A member is a person in your community. They hold one or more memberships — one per agency they belong to — and each membership carries a rank, a callsign, a badge number and one or more roles.

That shape is why a player can be an LSPD officer on Tuesday and a volunteer firefighter on Thursday without two accounts, and why the permission engine always needs to know which agency they are working as right now.

The active agency

Somebody in more than one agency picks which they are working as when they come on duty. That choice drives every agency-scoped permission check for the session: a deputy's supervisor role in the sheriff's office does not reach into the fire department's reports, even while they are standing in both.

On a server with jobAgencyMap configured, the framework job a player is wearing picks the agency for them and /duty needs no menu at all.

Ways to sign in

Four, and they all end in exactly the same place — a session row and a short access token — so adding a fifth later changes nothing downstream:

MethodHow it works
Password Email and password. Two-factor is available to everyone and required for owners.
Magic link A one-time link by email. Useful for people who will sign in three times a year.
Discord OAuth. One Discord application serves every community, and a player who signs in this way is matched to the Discord ID your game server already reports — so nobody types a code. Community plan and above, with Discord sync on. Can also drive roles — see below.
A code from the game A player types /cadlink, the bridge shows them a six-character code, and they enter it on the website. This is how a game account is tied to a CAD account, and it is done once per player.

Most players never do any of this. A player gets a CAD account the first time they go on duty, open the in-game terminal, or open the citizen portal. The platform creates it in the agency their framework job is mapped to, with that agency's default role.

The map is yours — Owner panel, Player accounts, job to agency. The agency is never taken from anything the game sends, only from that map, and an unmapped job gets a civilian account rather than a guess. Your plan's member limit still applies.

/cadlink and the code box stay as the fallback, for somebody attaching an account they already have. If you would rather approve everyone by hand, turn off the autoEnroll setting and nothing is created automatically.

Inviting people

Personnel in the owner panel. An invitation names the agency, the role and optionally a rank and callsign, and arrives as a link that creates the account on first use. Invitations can be revoked before they are accepted.

If you are standing a community up with twenty people at once, create the invitations in a batch and post the links in your staff channel — each is single-use and tied to the email it was issued for.

Discord role sync

Community plan and above. Map a Discord role to an agency and a CAD role, and membership follows the Discord role: somebody promoted in Discord is promoted in the CAD, and somebody who leaves the role loses the access. The sync runs as a background job, so it is a minute or two behind rather than instant.

It is worth being deliberate about which direction is the truth. If Discord drives roles, a change made in the CAD will be reverted on the next pass; most communities find it less confusing to let Discord own membership and the CAD own everything else.

Sessions and sign-out

Everyone can see their own open sessions and sign one out — the forgotten browser at a friend's house, the terminal on a server they no longer play. In-game terminal sessions are separate and short-lived by design: an in-game browser is a different risk from a real one, so the token the terminal gets expires in hours rather than weeks.

Two-factor

Authenticator app, from anybody's profile. Required for community owners, and worth requiring of anybody who can read criminal history or approve reports. Turning it on does not log anyone out.

Roles & permissions

This is the model the whole platform is built on, and ten minutes here saves a lot of confusion later.

A permission is three things

Every permission is written resource:action@scope:

report:approve@agency
   │        │       └─ scope — how far the grant reaches
   │        └───────── action — what may be done
   └────────────────── resource — what it is done to

Scope never says what is allowed; only how far. That is the whole trick. There are four, narrow to wide:

ScopeReaches
@selfOnly rows this person owns or created. Their own reports, their own layout, their own character.
@agencyRows belonging to an agency they are in, plus rows belonging to no agency at all.
@jurisdictionRows inside a jurisdiction they reach — which includes everything nested inside it, so a state grant covers the counties and cities.
@tenantEverything in the community.

So report:update@self lets somebody edit their own report; report:update@agency lets a supervisor edit anyone's in their department; report:update@tenant reaches the whole community.

How a decision is made

In order, and the first match wins:

  1. Community owner? Owners and co-owners hold everything inside their own community, period.
  2. Any of their roles? A member's effective permissions are the union of their roles in the agency they are working as. A role granted through an agency applies only to that agency's rows, even if the permission string says @tenant — otherwise a deputy's role in one agency would reach into another.
  3. A mutual-aid grant? One agency can lend another specific rights, optionally only for the duration of one call and optionally with an expiry. Only relevant once you turn on private agencies.
  4. Otherwise denied, and the denial is logged with the reason — so support can answer "why can't my sergeant approve reports" without a database session.

The browser runs the same decision code to decide what to gray out, but that copy is only ever a hint. The server answer is the one that counts, and it is checked again on every single request. Nothing can be reached by guessing a URL.

The roles you start with

Most agencies get three, following a deliberate "simple first" rule: a member who can do the job, a supervisor who can approve and override, and a command role that configures the agency. Anything more granular is yours to build.

Agency typeRoles
DispatchDispatcher · Lead Dispatcher · Communications Manager
City PoliceOfficer · Supervisor · Command
SheriffDeputy · Supervisor · Command
State PoliceTrooper · Supervisor · Command
FederalAgent · Supervisor · Command
Game WardensWarden · Supervisor · Command
Marine / Air OpsOfficer / Pilot · Supervisor · Command
FireFirefighter · Supervisor · Command
EMSMedic · Supervisor · Command
TowDriver · Yard Manager
CourtsClerk of Court · Attorney · Judge
CorrectionsCorrections Officer · Jail Administrator
DMVDMV Clerk
DOTDOT Worker
CivilianCivilian
BusinessEmployee

What each layer adds

Roles are built out of bundles, which is the quickest way to understand what a given role can actually do:

BundleWhat it grants
CAD base
every on-duty role
See calls and units, read and write call notes, see the map, send and read messages, see BOLOs, manage their own layout and time clock, search, see the roster, and set their own status.
Field base
every field unit
CAD base, plus: create and update calls, put themselves on a call, read and write their own reports, submit them, and read people and vehicles.
Law enforcement Create and edit people and vehicles, read criminal history, firearms, licenses and businesses, raise flags, write citations and arrests, log evidence and impounds, request warrants, create and broadcast BOLOs, read the penal code and case files.
Fire Read patient medical details, write fire incident reports with their NFIRS coding, see station alerting, the hospital board, and the run card they are working on.
EMS Read and update patient medical details, write and submit patient care reports, see the hospital board, station alerting and run cards.
Dispatch Create, update, delete, assign, dispatch, merge and close calls; force any unit's status; draw on the map; broadcast messages; tone out stations; apply a run card and strike the next alarm; manage the signs and the radio panel; create and broadcast BOLOs; run lookups; manage the tow rotation; see the hospital board and statistics.
Supervisor extra Approve and edit anybody's reports in the agency, assign and dispatch units, merge and close calls, force statuses, manage members, record discipline, read agency statistics and time clocks, push a layout as the agency default, and strike an alarm.
Command extra Everything that configures the agency: ranks, roles, units, status codes, report forms, run cards and response plans, certifications, applications, and the agency's own audit log.

The split that keeps coming up: manage versus strike

Two permissions on run cards, and the difference is the clearest illustration of how the model is meant to be used:

  • run_card:strike — escalate a working fire to a second alarm. A dispatcher's decision, made in thirty seconds on the radio. Dispatchers and supervisors hold it.
  • run_card:manage — edit what a second alarm means. A chief's decision, made once in an office. Only command roles hold it.

When you build your own roles, look for that shape: the thing done in the moment and the thing decided in advance are usually two permissions, not one.

Building your own roles

Roles in the owner panel. The editor renders straight from the catalog in the next section, grouped the same way, so everything that can be granted is visible and a typo is impossible — an unknown resource or action is refused at save time rather than silently denying forever.

Three things worth doing:

  • Start from a shipped role and copy it rather than from empty. The bundles above took a long time to get right.
  • Be careful with @tenant. It is the right scope for dispatch and wrong for almost everything else. A supervisor who should see their own department's reports wants @agency.
  • Grant view and list together. They are separate on purpose — view is one row, list is the index — but a role with one and not the other is almost always a mistake.

The permission catalog

Everything that can be granted. The role editor renders from this same list, so nothing exists that is not here. Actions marked on a resource are the only ones that may be granted on it; anything else is refused when you save.

Computer aided dispatch

ResourceActionsWhat it covers
callview, list, create, update, delete, assign, dispatch, merge, closeCalls for service.
call_noteview, create, update, deleteNotes on the call timeline.
unitview, list, create, update, delete, force_statusUnits. force_status is a dispatcher or supervisor overriding somebody else's status.
boloview, list, create, update, delete, broadcastBOLOs. broadcast pushes it into the game.
messageview, create, broadcastMessages between units and dispatch.
mapviewThe live map.
map_drawcreate, update, deleteDrawing closures, perimeters and boxes on the map.
station_alertingview, tone_outThe Stations panel, and setting a house off.
signsview, manageThe electronic signs in the game.
radioview, manageThe radio activity panel.
layoutview, create, update, delete, manageWorkspace layouts. manage is pushing one as an agency default.
tow_rotationview, manageThe tow rotation list.
hospital_boardview, manageHospital destinations and diversion status.
response_planview, list, create, update, deleteWhat rolls on a given kind of call.
run_cardview, manage, strikeSee the split described in the previous section.

Records management

ResourceActionsWhat it covers
personview, list, create, update, delete, exportThe master name index.
person_medicalview, updateBlood type, allergies, conditions, DNR. Fire and EMS.
person_criminalviewCriminal history. Law enforcement and the courts.
vehicleview, list, create, update, deleteThe vehicle index.
firearmview, list, create, update, deleteRegistered firearms, by serial.
businessview, list, create, update, deleteRegistered businesses.
licenseview, list, create, update, revoke, issueDriver, CDL, weapon, hunting and the rest.
flagview, create, update, deletePerson flags: armed and dangerous, officer safety, medical alert.
reportview, list, create, update, delete, submit, approve, seal, unseal, exportEvery report type except the two below.
fire_reportview, list, create, update, delete, approveThe fire incident report and its NFIRS coding. Its own resource because statistics are built on it.
pcrview, list, create, update, submit, approvePatient care reports. Its own resource for the same reason, and agency-private by default.
evidenceview, list, create, update, deleteEvidence and chain of custody.
citationview, list, create, update, deleteCitations and warnings.
arrestview, list, create, update, deleteArrest records.
impoundview, list, create, update, deleteImpounds and release.
searchviewThe cross-index search box.
statsview, exportThe statistics pages.

Judicial

ResourceActionsWhat it covers
caseview, list, create, update, close, seal, unsealCase files.
hearingview, list, create, update, deleteThe docket.
warrantview, list, create, update, sign, revokecreate is requesting one; sign is a judge granting it.
sentenceview, create, updateSentencing, which starts the jail clock in game.
penal_codeview, list, create, update, delete, import, exportYour charge list, fines and jail time.
bookingview, list, create, updateThe jail: booking, cell roster, release.

Personnel

ResourceActionsWhat it covers
memberview, list, create, update, deleteMemberships: rank, callsign, badge, roles.
rosterviewThe roster panel.
rankview, create, update, deleteThe rank ladder.
certificationview, create, update, deleteTraining records: K9, SWAT, paramedic, FTO.
disciplineview, create, update, deleteDisciplinary records. Agency-private by default.
applicationview, list, update, approveJob applications from the citizen portal.
timeclockview, updateHours on duty.

Administration

ResourceActionsWhat it covers
agencyview, list, create, update, delete, manageThe agencies themselves.
jurisdictionview, list, create, update, deleteThe drawn city, county and state areas.
stationview, list, create, update, deleteFirehouses and posts.
roleview, list, create, update, deleteThe role editor itself.
status_codeview, create, update, deleteYour status set.
call_typeview, create, update, deleteYour call types.
form_definitionview, create, update, deleteReport forms. Pro and above.
game_serverview, create, update, deleteLinked FiveM servers.
api_keyview, create, revoke, rotateBridge keys and public API keys.
integrationview, manageThe connected-products page.
alert_ruleview, create, update, deleteWhat happens to an incoming third-party alert.
webhookview, create, update, deleteOutbound webhooks. Pro and above.
tenant_settingsview, updateThe realism toggles and everything else on the settings pages.
auditview, exportThe audit log.
billingview, managePlan and payment.
data_exportexport, importBulk import and export.

Wildcards

Resource and action may both be *. A community owner holds exactly one permission — *:*@tenant — and that is the only place a wildcard is used by default. You can use them in your own roles; be aware that call:*@tenant includes delete.

Record visibility

Permissions answer "may this member approve a report". Visibility answers a different question: "should this member see another agency's reports at all". The two are separate, and both have to pass.

The default is shared

Out of the box almost everything is visible across your whole community: calls, BOLOs, warrants, people, vehicles, firearms, businesses, incident reports, arrests, citations, evidence, impounds, cases, bookings, shift logs and fire pre-plans.

That is how most roleplay communities actually play. A deputy runs a plate and expects to see what the city PD wrote about it, and a server where they cannot is a server where people stop using the records system.

What starts private

Four record types, each for a real reason:

TypeWhy
Patient care reportsMedical records. Only the EMS agency that wrote one reads it.
Personnel filesA department's own staff records.
Internal affairsThe point of an internal investigation is that the subject's own shift does not read it.
Use of forceReviewed inside the department before it goes anywhere.

Changing it

Turn on Private agency records under Realism, and each record type above gets a shared / agency-private switch. Agency-private means the row is visible only to members of the agency that owns it — and to anyone holding a mutual-aid grant from that agency.

Think about this before switching record types to private rather than after. A report written while a type was shared does not become unreadable, but your community's habits form fast, and a sheriff's office that has spent a month unable to read city arrests will have started keeping its own notes somewhere else.

Mutual aid

One agency lending another specific rights, for a limited time or for the duration of a single call. It only matters once private agencies are on, and it is the right answer to "the fire department needs to read this one police report" rather than making the whole type shared.

A call-scoped grant is exactly that: rights on one call and nothing else, and it expires with the grant.

Undercover units

Separate from all of the above, and on by default: units marked plain-clothes are hidden from everyone except dispatch on the map. Federal agencies can also start hidden from other agencies' maps entirely.

The realism toggles

One page in the owner panel, and the most important thing to say about it is this: everything here ships off except the three that make a new community work. A CAD with every option turned on is harder to use, not more realistic.

The realism settings page
Realism. Each switch says what turning it on will feel like for the people using the CAD, rather than describing itself.

Dispatch and calls

SettingDefaultWhat it does
Route calls by jurisdictionOff Draw city, county and state areas on the map and calls go to whoever covers that spot. Off, every call lands in one shared queue, which is how most servers play. Turn it on when you have more law enforcement agencies than dispatchers and the queue has become noise.
Station alertingOff Tone out fire and EMS stations and see who acknowledges. Adds the Stations panel and can alert automatically when a call is assigned.
Show ten-codesOff Puts 10-8 and 10-97 next to the plain words. The statuses themselves do not change — this is labels only.
Tow rotationOff Tow jobs go to whichever company is next in line rather than whoever grabs it first.

Records and paperwork

SettingDefaultWhat it does
Supervisor review of reportsOff Reports go to a supervisor before they lock, and can be returned with a note. Good for communities that train people; friction for ones that do not.
Detailed fire and EMS codingOff Adds the NFIRS-style coding sections to fire reports and the fuller vitals layout to patient care reports. Worth it if your fire department cares about its statistics; a lot of extra fields if it does not.
Private agency recordsOff Lets you make record types visible only to the agency that wrote them. See Record visibility.

Courts and corrections

SettingDefaultWhat it does
Court workflowOff Cases, the docket, pleas, hearings and sentencing. A sentence tells the game to start the jail clock. Warrants work either way — those are law enforcement, not court.

Civilians

SettingDefaultWhat it does
Civilian portalOn Players manage their own characters, vehicles and tickets, and see their court dates.
Let civilians register their own vehicles and firearms On Off, only the DMV and law enforcement can create those records.

Map and history

SettingDefaultWhat it does
Keep position history for playback On Lets a supervisor replay unit movement on a closed call. Off, positions are live only and nothing is stored — which is also a few hundred fewer database rows a minute on a busy server.

The other settings pages

Not on the Realism page, but worth knowing they exist:

SettingDefaultWhat it does
Dispatch modeSharedOne dispatch for the whole community, or one per agency.
Unit recommendationOnSuggest the nearest available unit of the right kind.
Automatic tone-out on assignmentOffTone the station when a fire or EMS call is assigned, without a dispatcher pressing anything.
Close a call when the last unit clearsOffUseful on a server with no dedicated dispatchers.
Status timersOnThe board complains when a unit sits too long in a timed status.
Audible cuesAll onNew call, panic, priority escalation and tone-out. Each dispatcher can override for themselves.
Map styleSatelliteAlso road and atlas.
Live position interval2 sHow often positions are pushed to browsers, 1–10 seconds.
Position history retention7 days0–90 days.
Show postalsOnPostal numbers on the map.
Show breadcrumbsOffA trail behind each moving unit.
Hide plain-clothes unitsOnFrom everyone but dispatch.
Characters come from the frameworkOnIdentity fields are read-only in the CAD when the game owns them.
Characters per player31–20.
Online ticket paymentOnPlayers pay citations from the portal.
9-1-1 from the portalOffMost communities want a 9-1-1 call to come from somebody actually in the world.
Businesses need approvalOnA player registering a business waits for a yes.
Time zoneAmerica/Los_AngelesEvery timestamp a human reads.

Retention

How long things are kept, and all four are yours to set:

WhatDefaultRange
Audit log365 days0 to 3,650, capped by your plan
Closed calls30 days1 to 365
Messages30 days1 to 365
Unit positions7 days0 to 90

Closed-call retention trims the call, not the paperwork. Reports, citations, arrests and cases are permanent records and are never trimmed by this setting. Shortening it is how you keep a busy server's board fast without throwing away anything that matters.

Linking your game server

One resource, two secrets, one restart.

1. Get your key pair

In the owner panel, Game servers → Link a game server. Name the server something you will recognize, pick its framework if you want the label to be right, and the platform issues two values:

  • an API key, beginning skc_live_, which says which community and which server is talking;
  • a signing secret, shown once, which proves a message was not altered on the way.
The game servers page
Game servers. The Push config button sends your current status codes, call types and command names down to the bridge without a restart.

Both values are secrets, and the signing secret is shown once. Together they authenticate your entire community to the platform — treat them like a database password. Never commit them to a repository and never paste them in Discord. If one leaks, rotate the pair from this same page; the old pair stops working immediately.

2. Install the resource

Drop the sals_kewlcadbridge folder into your server's resources/ directory and add it to server.cfg:

ensure sals_kewlcadbridge

3. Paste the two values

Open config.lua and set them:

Config.ApiKey        = 'skc_live_...'   -- Game servers → API key
Config.SigningSecret = '...'            -- shown once, next to the key

4. Restart and read the console

You should see a short banner:

[sals_kewlcadbridge] ----------------------------------------
[sals_kewlcadbridge] Sal's Kewl CAD bridge 2.1.0
[sals_kewlcadbridge] framework: qb-core
[sals_kewlcadbridge] ----------------------------------------

If either value is still the placeholder, the bridge says so and stops there rather than starting in a half-working state. For anything else, set Config.Debug.Enabled = true and restart to see the detail — it reports the framework it detected, whether it connected, and every rejected message with the reason.

5. Have your people link their accounts

Each player types /cadlink once and enters the six-character code on the website. Until they do, the platform does not know who they are and they will not appear as a unit. This includes civilians, who need it for /character.

What is sent, and what is not

Worth being able to answer when a player asks:

SentNot sent
On-duty unit callsigns, statuses and positions Off-duty positions, unless you deliberately enable it
Calls and call notes Chat, of any kind
Lookups that are run, and by whom Anything about players who never go on duty, beyond a 9-1-1 they placed themselves
Panic and backup requests Any file, screenshot or voice from your server
Characters, owned vehicles and licenses, if sync is on Anything from a framework table you did not point it at

Config.TrackOffDuty ships false and should stay that way. Players consider off-duty tracking surveillance, and dispatch has no use for it.

How the connection works

  • Outbound only. The bridge opens a WebSocket out to the gateway. Nothing connects in; there is nothing to forward and nothing to find on a port scan.
  • Every message is signed with HMAC-SHA256 over the body, a timestamp and a nonce, using your signing secret. The three are signed together, so a captured signature cannot be reused with a fresh timestamp. A replayed or altered message is rejected.
  • FiveM ships no crypto for Lua, so SHA-256 and HMAC are implemented in pure Lua in the resource and verified against the FIPS 180-4 and RFC 4231 test vectors on every build. A drift there would reject every request from your server with an error nobody could diagnose.
  • HTTPS fallback. Some hosts block long-lived sockets. If the WebSocket cannot connect, the bridge falls back to plain HTTPS with the same authentication and the same handlers — you lose latency and nothing else. Controlled by Config.AllowHttpFallback, which ships on.
  • An outage is survivable. Messages queue while disconnected and replay in order. QueueDepth() tells a script how far behind it is.

Pushing configuration without a restart

When you add a status code, rename a command or add a call type, press Push config on the Game servers page. The platform sends the current set down to the bridge, which picks it up live. Restarting the resource does the same thing; the button exists so you do not have to.

More than one server

Link each one separately — each gets its own key pair and appears on its own row, with its own player count and last-seen time. Calls, units and records are shared across all of them, because they are all one community. Plan limits cap how many you may link.

Bridge configuration reference

Everything in config.lua, commented in plain language in the file itself. This is the same list with more context.

The link

SettingDefaultWhat it does
Config.ApiKeyplaceholderYour community's API key. Required.
Config.SigningSecretplaceholderSigns every message. Required.
Config.Gatewaywss://cad.salskewlscripts.com/wsOnly change if support asks, or if you are self-hosting.
Config.Apihttps://cad.salskewlscripts.comSame.
Config.AllowHttpFallbacktrueFall back to plain HTTPS when the WebSocket cannot connect.

Intervals

SettingDefaultWhat it does
Config.Intervals.Location3000 msHow often on-duty positions go up. Lower is smoother on the dispatcher's map and more traffic; the platform caps it anyway.
Config.Intervals.Heartbeat5000 msLiveness ping, so the CAD knows the server is alive.
Config.Intervals.ReconnectMin2000 msShortest wait before retrying a dropped connection.
Config.Intervals.ReconnectMax30000 msLongest wait. It backs off between the two.
Config.TrackOffDutyfalseOff-duty position tracking. Leave it off.

Commands

Every command is renameable. The left side is what the bridge calls it internally and must not change; the right side is what your players type. Set one to false to remove it entirely.

Config.Commands = {
    duty = 'duty', status = 'status', available = '10-8',
    enroute = 'enr', onscene = 'os', clear = 'clr', busy = 'busy',
    trafficstop = 'ts', attach = 'attach', detach = 'detach', note = 'note',
    backup = 'backup', panic = 'panic',
    plate = 'plate', name = 'name', serial = 'serial',
    bolo = 'bolo', cite = 'cite', arrest = 'arrest',
    tow = 'tow', ems = 'ems', fire = 'fire',
    transport = 'transport', athosp = 'athosp', incident = 'incident',
    mdt = 'mdt', character = 'character', cadlink = 'cadlink',
    emergency911 = '911', nonemergency = '311',
}

You can also rename commands from the platform side, in tenant settings, which is the better place if you run more than one server and want them consistent.

Which jobs may use duty commands

Config.EmergencyJobs = {
    'police', 'sheriff', 'state', 'bcso', 'sasp', 'lspd',
    'ambulance', 'ems', 'fire', 'fd', 'doc', 'tow',
}

Leave it empty to allow anyone. This only keeps the command list tidy for civilians — the platform checks permissions properly regardless, so this is not a security control and is not meant to be one.

The in-game terminal

SettingDefaultWhat it does
Config.Mdt.KeyF6Opens the terminal. Players can rebind it in FiveM's own settings.
Config.Mdt.RequirePropfalseRequires a laptop or tablet prop in hand, with the animation.
Config.Mdt.AllowOnFoottrueWhether the terminal works outside a vehicle.
Config.Mdt.DimBackgroundtrueFade the game behind it, so it reads as a screen.
Config.Mdt.Chrome'tablet'tablet draws a device the character is holding; fullscreen fills the screen and reads more like a menu. The platform's own setting wins when one is set there.

The HUD

SettingDefaultWhat it does
Config.Hud.EnabledtrueThe status HUD.
Config.Hud.Position'top-right'Or top-left, bottom-left, bottom-right.
Config.Hud.ShowPendingCounttrueHow many calls are waiting in the unit's area.
Config.Hud.WaypointOnAssigntrueSet a waypoint automatically when dispatch assigns something.

Framework sync

Characters created in game appear in the CAD without anyone typing them twice. A character is sent when its player loads in, when their job changes, and in a slow pass over everyone online every few minutes.

SettingDefaultWhat it does
Config.Sync.EnabledtrueThe whole feature. Standalone servers have nothing to sync.
Config.Sync.IntervalMinutes15Minutes between full passes over everyone online.
Config.Sync.ApplyLicensestrueApply the CAD's suspensions, revocations and reinstatements to the character in game. With this off the CAD still records them but the game copy does not move.
Config.Sync.Vehicles.EnabledtrueRead owned vehicles from the framework's table.
Config.Sync.Vehicles.Tableframework defaultplayer_vehicles for qb/qbx, owned_vehicles for ESX, nd_vehicles for ND. Set only if your fork differs.
Config.Sync.Vehicles.OwnerColumnframework defaulte.g. citizenid.
Config.Sync.Vehicles.PlateColumnframework defaulte.g. plate.
Config.Sync.Vehicles.ModelColumnframework defaultnil if your table has no model column.
Config.Sync.Vehicles.PropsColumnframework defaultThe JSON blob, read for a color name.
Config.Sync.Licenses.EnabledtrueSync licenses.
Config.Sync.Licenses.Tableuser_licensesWhere ESX keeps them. qb and qbx keep them in player metadata and need nothing here.
Config.Sync.LicenseMapESX namesYour framework's license names to the CAD's, for anything not already mapped.

Vehicle reads go through oxmysql, mysql-async or ghmattimysql, whichever is running — no framework exposes owned vehicles through an export, so this is a plain SELECT against their own table. It is read-only; the bridge never writes to your framework's tables. A wrong table or column name costs one console warning and nothing else: after three failed reads in a row it stops trying and says so.

The CAD license types are driver, cdl, motorcycle, weapon, hunting, fishing, pilot, boating and business. ESX's drive, drive_bike, drive_truck and weapon are mapped already.

Integrations

All detected at runtime with GetResourceState. Nothing here is a hard dependency, and a missing resource just means that feature stays off.

SettingWhat it connects
SalsKewlDispatchYour 9-1-1 call takers send worked calls into the CAD, with the transcript and the answers.
SalsKewlRadioChannel activity and the emergency button reach the CAD's radio panel.
SalsKewlStationAlertThe CAD's tone-outs set the firehouse off, the station's call board reads the CAD, and the watch-desk reset reports back. Configured in that resource.
SalsKewlSignageAmber alerts, BOLOs and closures pushed from the CAD onto the highway boards, and radar readings sent up as plate reads. Configured in that resource, under Config.Cad.
Shims.ps_dispatchForward ps-dispatch events, so an existing robbery script reaches the CAD without editing it.
Shims.cd_dispatchSame for cd_dispatch.
Shims.core_dispatchSame for core_dispatch.

The live call board

The platform can push its whole board — every open call, who is on it, the roster — into your server, for anything that displays it in game: a station TV, a wall in the dispatch office.

SettingDefaultWhat it does
Config.Board.Enabled'auto' 'auto' sends it while something on this server reads it — Station Alert or Sal's Kewl Dispatch is running, a resource called WantBoard(), or one of the board exports was read in the last two minutes. true always; false never, and station boards on this server stay empty.
Config.Board.AutoFor Station Alert, Dispatch Resources that read the board without having to say so. Add your own, or have it call WantBoard() at start.

Sensitive calls are never on the board. A board on a wall is a public place.

Nights ERS

Bridges night_ers callouts to the CAD, and does nothing at all if that resource is not running. See Nights ERS callouts for what it does and the one rule that shapes it.

SettingDefaultWhat it does
Config.Ers.EnabledtrueMaster switch. Harmless left on without night_ers installed.
Config.Ers.OfferTimeoutMs5000How long the CAD shows an unanswered offer. Match it to your ERS offer timeout; ERS still decides when its own offer lapses.
Config.Ers.TrafficStopstrueWhether an ERS traffic stop becomes a call as it starts.
Config.Ers.PursuitstrueWhether a pursuit does. Pursuits come in as priority 1.

Debug and locale

SettingDefaultWhat it does
Config.Debug.EnabledfalseVerbose logging. Ships off; leave it off in production — it prints every message in both directions, which is a lot on a busy server.
Config.Debug.VerbosefalsePrint outbound events as well as inbound commands.
Config.Locale'en'Reads locales/.

Street commands

Everything a unit would otherwise open a menu for. Names below are the defaults and every one is renameable in Config.Commands.

The important thing about these is that they are not a lesser interface. A /ts typed in a patrol car and a dispatcher clicking "new traffic stop" produce the same event and go through the same rules — there is one set of behavior, not two.

Duty and status

CommandDoes
/dutyGo on or off duty, choosing a unit. With a job-to-agency map configured, no picker appears.
/10-8Available.
/enrEn route to your assigned call.
/osOn scene.
/clrClear, with an optional disposition.
/busyBusy, unavailable for assignment.
/status <code>Any other status your community defines.

Calls

CommandDoes
/tsTraffic stop on the vehicle in front of you. The plate is read for you.
/attach /detachPut yourself on a call, or clear from it.
/note <text>Add a note to your current call.
/backup [urgency]Request backup at your location.
/panicEmergency alert. Goes to every dispatcher immediately, at priority 1.
/911 <text>Emergency call. Any player, on duty or not.
/311 <text>Non-emergency call.

Lookups

Each requires the matching permission in the CAD. The answer comes back as a few lines in chat — the danger first, then the identifying facts, then a pointer to the terminal for the rest. The common case is a plate read at a stop light, and opening a full-screen interface to answer that is worse than four lines of text.

CommandReturns
/plate <plate>Vehicle, owner, registration, flags.
/name <first> <last>Person, warrants, licenses, flags.
/serial <serial>Firearm by serial number.

Actions

CommandDoes
/bolo <text>Create a BOLO.
/cite /arrestStart a citation or an arrest in the terminal, prefilled.
/incidentStart an incident report where you are, prefilled.
/tow /ems /fireRequest a tow, EMS or fire to your location.
/transport /athospPatient transport, and arrival at hospital.
/mdtOpen the terminal.
/characterYour own record. Any player.
/cadlink <code>Link this game account to your CAD account. One time, per player.

What "prefilled" means

/cite, /arrest and /incident open the form already filled in with what the game knows and the browser cannot:

  • the nearest player within four meters, resolved to their CAD person record;
  • the plate of the vehicle the officer is looking at, or sitting in;
  • the street, with coordinates.

If nobody is close, the form opens empty and says so rather than guessing.

The in-game terminal

Opened with F6 or /mdt. It is the same application a dispatcher's browser loads, which is the whole point: a unit writes a report in the car and finishes it on a laptop as the same draft, against the same form and the same permissions.

The in-game terminal
The terminal. A home screen of large tiles rather than a row of tabs, because tabs are a desktop idiom that needs a mouse and reading, and a tile is a target you can hit in a moving vehicle.

It is a device, not a web page

That distinction drives every choice in it:

  • A status bar across the top with the callsign, the clock and the link state — the three things somebody glances up to check.
  • A home screen of tiles, big enough to hit on a touchscreen in a moving car.
  • One level of navigation, with a back arrow that always goes home. Anything deeper gets lost while driving.
  • A status strip that is always visible: your status, your call, and the quick statuses as buttons.

The tiles

TileWhat it is
CallsWhat is waiting and what you are on. Attach, detach, note, close.
LookupOne box that works out what you typed — a plate, a person, an address, a phone number. The dangerous part of the answer comes first.
ReportsYour drafts and your submitted reports, and the form to write a new one.
BOLOsWhat is currently out.
My shiftYour time on duty, your calls, your paperwork.
AssignmentThe fireground tab. Fire and EMS only — see below.
DispatchOnly if your community turned it on — see below.
CommandsThe command reference, as its own screen rather than a wall of text under every tab.

Tiles are gated three ways and all three have to pass: the member's role has to grant it, the community has to allow it in game, and — for the fireground — the member has to actually be on a fire or EMS agency. A tile nobody can use is worse than a missing one, because it teaches people the terminal is unreliable.

What the terminal knows that the browser does not

A card showing where the unit is and what it is driving, with two buttons that save a lot of typing:

  • Run plate ahead — the plate of the vehicle in front, without anybody reading it off the screen and typing it in.
  • Cite here — opens a citation with the street and coordinates already filled.

The fireground tab

Built around one idea: a firefighter on a call should never have to type a time or remember a number.

It shows the whole assignment, not just this unit's part — because the first question on any working fire is "what else is coming and how far out are they" — and a row of buttons that stamp the times. Pressing On scene here is what produces the travel time that ends up in the department's annual report, and it is the only way that number is ever right.

Dispatch from the car

Off unless the community switches it on, and most do not. The point of a dispatcher is that somebody is not driving, and giving every officer the board is how a community stops needing dispatchers at all.

But a small server running with nobody in the chair needs an officer to be able to take a call and send somebody, and telling them to alt-tab to a browser is worse. So it is a setting, off by default.

It is deliberately a subset. Creating a call, assigning a unit and striking a run card are things you can do at a stop light. Merging calls, editing call types and reassigning across agencies need a screen and a keyboard, and they stay in the web app.

The setting only decides whether the terminal offers these. It never grants a permission — every one of them is still checked server-side against the member's role. Switching it on widens what the terminal shows; it does not widen what anybody may do.

Terminal sessions

The terminal is handed a short-lived token rather than a password, and it expires in hours rather than weeks. An in-game browser is a different risk from a real one. When it expires the terminal says Terminal session ended and reopening it mints a new one.

The citizen portal

What a player sees about themselves. In game with /character, or in a browser at /portal. Written to be read by somebody who has never seen a CAD and does not want to.

The citizen portal
The citizen portal. Everything behind it is scoped to the signed-in user twice over, so the worst case of a permission bug is still your own records.

The five tabs

TabWhat is there
My characters Each character, with the details a medic who finds them unconscious would want: address, phone, emergency contact, blood type, allergies, conditions and a DNR flag. No framework tracks any of that, which is why it is here.
Vehicles What they own, its plate and registration, and whether it is currently flagged or impounded.
Tickets Citations they owe, with the charge, the fine and a pay button if your community allows online payment.
Court Hearings they are due at, with the date and the case. Only when court workflow is on.
Apply for a job Applications to whichever agencies are accepting them. A command role reviews these under Personnel.

What a player may change, and what they may not

On a server that syncs characters from the game, the identity fields — name, date of birth, gender — are shown but locked, with a line saying where to change them. That is the same split described in How it fits together: the game owns identity, the CAD owns everything else. Everything that is theirs is editable here.

Why the medical block matters

It is the single most-used part of this portal on servers that roleplay EMS. A medic running a name on an unconscious patient gets blood type, allergies, conditions and the DNR flag — and the only way any of that is ever there is if the player filled it in themselves. Tell your players it exists.

Registering things

If your community allows it, players can register their own vehicles and firearms from here. Turn it off under Realism and only the DMV and law enforcement can create those records, which is the right answer for a community that roleplays the DMV as an actual counter you queue at.

9-1-1 from the portal

Off by default. Most communities want a 9-1-1 call to come from somebody actually in the world, not from a web page. Turn it on if yours disagrees.

The dispatch workspace

At /cad. A docking layout where every panel can be tabbed, split, floated or popped out into its own browser window.

The workspace with its controls
The toolbar: the command line on the left, quick actions beside it, then theme, add-panel, pop-out and reset.

The layout you start with

Queue and units on the left, the call being worked in the middle, the map and alerts on the right, with Lookup and Messages tabbed behind the call and BOLOs behind alerts.

The unit board gets the most vertical space of anything on screen, and that is a considered choice rather than an accident: the pending queue is usually four or five rows, while the roster is every unit in every agency and a dispatcher reads it constantly. A queue that scrolls is an annoyance; a unit board that scrolls means somebody gets missed.

Every panel

PanelWhat it shows
PendingCalls waiting for a unit. Priority first, then oldest first.
ActiveCalls being worked: dispatched, active, held.
CallEverything about the one call you are working.
ClosedRecently closed, with dispositions.
UnitsThe status board, grouped by agency.
RosterEveryone in the community, not just who is on duty.
MapLive positions, calls, drawings and the heat map.
LookupOne search box across people, vehicles, plates, firearms, property and reports.
BOLOsWhat is currently out, and the form to issue one.
AlertsIncoming third-party alerts, panic and backup.
MessagesText between dispatch and units. Reaches every terminal and every dispatcher screen straight away.
StationsFire and EMS houses: tone out, see acknowledgements, edit a station.
SignsThe electronic signs in the game, and what they are showing.
HospitalsDestinations and diversion status.
TowThe tow queue and the rotation.
RadioChannel activity, and who is talking.

Pop-out windows

Pick a panel from Pop out… and it opens as a real browser window at a real URL — which is what makes it land on a second monitor, stay there, and survive the first window navigating away.

The map popped out to its own window
The map in its own window. Five windows are still one connection to the platform.

Every pop-out carries the session with it, and they share a single realtime connection: the first window to load elects itself leader, holds the socket, and rebroadcasts every frame to the others. Five windows do not mean five connections and do not mean five times the traffic.

Saving layouts

Your arrangement is saved as you change it — immediately in your own browser, and to the platform a couple of seconds later, so a resize or a pop-out survives a refresh and follows you to another machine.

  • Reset layout puts back the default arrangement.
  • A supervisor with layout:manage@agency can push a layout as the agency default, which is what a new dispatcher in that agency gets.
  • The order of preference is: your own saved layout, then the agency default, then the built-in one. A layout saved by an older build that fails to restore falls back rather than leaving you with an empty workspace.

Quick actions

Three things a dispatcher starts from scratch most often, sitting next to the command line rather than buried in a panel that may not even be on screen:

ActionShortcut
+ CallAlt + N
+ BOLOAlt + B
Signs—

Alt rather than Ctrl, because Ctrl + N is "new window" in every browser and cannot be reliably intercepted. Both are ignored while you are typing in a field, so an n in a narrative never opens a dialog on top of the one you are in.

Themes

Dark, light, system or high contrast, chosen per person from the toolbar and kept in that browser. Your community's default is only a default — a dispatcher working a night shift gets to decide for themselves.

The dispatcher command line

The box at the top left. Press / or Ctrl + K from anywhere to focus it.

This is the feature experienced dispatchers end up living in. A dispatcher who learns six of these stops touching the mouse, which is most of why people who have used real CAD software prefer it to web ones.

The commands

CommandDoesExample
u <callsign> <status>Set a unit's statusu 1-ADAM-12 ONS
a <callsign> <call#>Assign a unit to a calla S-213 26-000119
d <callsign>Clear a unit from its calld S-213
c <type> [location]Start a new call of that typec TS Route 68
n <call#> <text>Add a note to a calln 119 driver detained
x <call#> [disposition]Close a callx 119 citation issued
f <callsign>Follow a unit on the mapf TROOP 2-14
?Show the list?

It forgives abbreviation

Dispatchers abbreviate, so the command line does too:

  • 1a12 finds 1-ADAM-12 — punctuation and case are ignored on a second pass.
  • 119 finds 26-000119 — a call number matches on its ending as well as in full.

It tells you what went wrong

A bad status code does not fail silently; it answers with the codes your community actually uses. A call number that matches nothing open says so. Successes clear the box; failures leave what you typed there so you can fix it rather than retype it.

Every command resolves to exactly the same API call a click would make, so nothing here can do something the interface cannot — and nothing here skips a permission check. A dispatcher without call:close@tenant gets the same refusal from x as from the button.

Working a call

Where calls come from

SourceHow
A dispatcher+ Call, Alt+N, c <type>, or right-clicking the map where it happened.
A unit/ts, /tow, /ems, /fire, /incident — or the terminal.
A player/911 or /311, with the caller attached.
Panic or backup/panic and /backup, as priority alerts.
A third-party scriptAn alert through the rules engine — a robbery script, an alarm, an ALPR hit. See Integrations & alert rules.
Sal's Kewl DispatchA worked 9-1-1 call dumped in whole, with the transcript, the call taker's answers and the times.
Your own scriptCreateCall or the REST API.

Every one of them lands in the same place and follows the same rules about numbering, routing, the timeline and who gets told.

The pending queue

Sorted the way a dispatcher thinks: priority first, then oldest first. A P1 that came in ten seconds ago still outranks a P4 from an hour ago.

A call that has been waiting too long for its priority turns amber and then red. The thresholds are per priority, because "too long" means something different for a shots-fired call than for a parking complaint:

PriorityAmber afterRed after
P1 — life at risk30 s1 min
P2 — urgent1 min3 min
P3 — prompt3 min7 min
P4 — routine7 min15 min
P5 — when convenient30 min1 hour

Each row shows the call number, the title, the service code, the location and postal, the caller if there is one, and the units already on it. Two separate ages are shown: how long since the call came in and how long since a unit was actually dispatched. A call can be four minutes old and thirty seconds dispatched, and those two numbers mean very different things to a supervisor.

Assigning units

Four ways, and they all do the same thing:

  • Drag a unit from the status board onto the call. This is how dispatchers who have used a real CAD expect it to work.
  • Right-click the call and pick from the list — available units only, nearest first when the call has coordinates, with the distance shown.
  • The command line: a 1-ADAM-12 119.
  • Recommend on the call panel, which suggests the nearest available units of the right kind.

Assigning does three things at once: it attaches the unit, moves it to en route, and tells the game — the officer gets a notification and a waypoint without typing anything.

The call panel

A call open in the call panel
One call on one surface: the details, the units, the timeline and the note box. A panel in the layout rather than a modal, so opening a call never hides the boards.

It stays live: a note typed in a patrol car appears here without a refresh, because the panel subscribes to that call's own room.

On it you will find the priority (changeable from a dropdown), the status, the source, the agencies it routed to, the units attached and the units that have cleared, the full timeline, and a box to add a note. Depending on your permissions and the call: Recommend, Run card, Tone out and Close.

Call statuses

StatusMeans
pendingWaiting. Nothing has been sent.
dispatchedUnits assigned, nobody on scene yet.
activeSomebody is on scene.
heldParked deliberately — waiting on something, not forgotten.
closedDone, with a disposition.

Merging duplicate calls

Several 9-1-1 calls about the same crash is the normal case, not an edge one. Merging moves the units, the notes and the timeline across rather than asking anyone to retype anything, and leaves a pointer behind so the old call number still resolves when somebody reads it off a report later.

When a new call is created close to a recent one of the same kind, the platform offers the merge rather than creating it blind.

Closing

Close with a disposition — the short answer to "what happened" that makes the closed list worth reading. If your community turned on Close a call when the last unit clears, it closes itself when everyone detaches, which is the right setting for a server with no dedicated dispatchers.

Playback

With position history on, a supervisor can replay unit movement on a closed call — where everybody was, minute by minute. This is the honest answer to "did anyone actually go" and is worth the storage on a community that runs internal reviews.

Units & status

The unit status board
The unit board, grouped by agency and colored by status. The column that earns its place is the time in status.

Reading the board

Units are grouped by agency, with the agency color down the left, and sorted within the group by how committed they are — panic first, then on scene, en route, transporting, at hospital, busy, available, out of service, off duty. The thing needing attention is always at the top.

Each row shows:

  • the callsign, with a crew count if more than one person is in it — hover for who, with ranks and badge numbers;
  • the status, as your community's own code, in the status color;
  • what it is on: the call number, service code and location when committed, or who is in it when not;
  • two timers — see below;
  • icons for lights and siren, and for plain clothes.

The two timers

The first is time in the current status. The second, when the unit is on a call, is time on that call. They are different numbers and both matter: going en route, arriving and then waiting resets the status timer three times, and the question "how long has this unit been tied up" is answered by neither of the first two.

A unit that sits past its status's timer is marked overdue and the figure turns amber. The defaults are 10 minutes en route, 30 on scene, 20 busy or transporting, 45 at hospital — all editable per status code.

Changing status

WhoHow
The unit itself/10-8, /enr, /os, /clr, or the quick-status buttons on the terminal.
A dispatcher or supervisorThe dropdown on the row, the right-click menu, or u <callsign> <code>. Needs unit:force_status.
The platformAssigning a unit moves it to en route automatically.

Every status change is recorded in an append-only log, and that log is what the response-time statistics are computed from. It is also what stamps the fireground milestones — see Run cards & response plans.

Which statuses a unit can be put into

Four states only mean anything while the unit is assigned to a call, because each one stamps a milestone onto that call: en_route, on_scene, transporting and at_hospital. A unit sitting in quarters with nothing assigned cannot be put on scene — of what? — so those four are greyed out in the menu with the reason beside them, and refused by the platform if something asks anyway. The refusal names the unit and says what to do instead, because it is read mid-shift.

The others are about the unit itself and are always available: available, busy, out of service, off duty. Panic cannot be set by hand at all. It comes from the unit — the panic button, or the terminal — and it is cleared by putting them back in service. A dispatcher who could raise one by hand could manufacture the one alert nobody is allowed to ignore.

It is not a ladder. A crew that arrives and has to back out to the street goes on scene → en route, and a unit that clears the hospital and returns to the same call goes back again. The only question asked is whether the status makes sense right now, never whether it follows the one before. A CAD that insists dispatch work only moves forward gets told whatever the software will accept, and then the times mean nothing.

A street command refused this way says so in game: the bridge acknowledges /os the moment it is typed, so when the platform disagrees it pushes the reason back to that player rather than leaving the acknowledgement standing.

The right-click menu

On any unit row: open the call it is on, clear it from that call, set a status from your community's own quick codes, show it on the map, or copy the crew's names. Everything there was already possible and took two or three clicks through a detail panel.

The same menu is on the units listed inside a call, which is where you are actually looking when a crew says they have arrived.

Status codes and states

Your community defines its own status codes — the labels, the colors, the timers and which appear as quick buttons. Every code maps onto one of nine underlying states, which is what keeps the boards, timers and recommendation working whatever you call things:

StateMeansDispatchable?
off_dutyNot workingNo
availableIn service, freeYes
en_routeTraveling to a callNo — committed
on_sceneArrivedNo — committed
busyUnavailable for assignmentNo
transportingMoving a patient or a prisonerNo — committed
at_hospitalAt the destinationNo — committed
out_of_serviceMechanical, meal, trainingNo
panicEmergencyNo — committed

Only available is offered for a new assignment. So a community that renames 10-8 to In service and maps it to available keeps every piece of automatic behavior.

The status log

Every change is written down: what it went from and to, how long the unit spent in the previous status, which call it was on if any, where the change came from — the board, the terminal, the game, the API — and whether dispatch forced it rather than the unit choosing. That last one is the question actually being asked whenever somebody looks a shift up: the crew cleared early, or dispatch cleared them?

On a call, each change also lands on that call's own timeline, which is what you read when you open the call. The log is the other half — the transitions that belong to no call at all: went available at the station, out of service for fuel, off duty mid-shift. A supervisor reconstructing a night needs those gaps as much as the calls.

EndpointWhat it answers
GET /v1/logs/unit-statusThe log for a window, newest first. Filter with unitId, callId, or offCallOnly=true for the transitions no call will ever show. Defaults to the last 24 hours.
GET /v1/units/:id/status-logOne unit's own.

The roster

A separate panel from the status board, because they answer different questions — "who can I send" versus "who is actually playing" — and mixing them makes both worse. The roster shows everyone in the community with their agency, rank, callsign, whether they are on duty, and their hours over the last seven days.

The map

Drawn on a canvas in game coordinates, which is worth one sentence of explanation: San Andreas is not on Earth. Its coordinates are a flat local system, the jurisdiction polygons are stored in that system, and a conventional slippy-map library would mean projecting everything twice to arrive back where it started. Canvas also redraws two hundred moving units without breaking a sweat.

The basemap

Units, calls and zones are drawn over a tiled map of San Andreas. The tiles are FiveNet's livemap-tiles — Apache-2.0 and community-drawn, cut from the Virus_City postal map and the DLK HD atlas, and deliberately not Rockstar's own imagery, which is what makes them something we can host for you. Sal's Kewl Dispatch already draws the same set, so a community running both sees one map in two places rather than two that disagree.

There is nothing to configure and nothing to upload. If you self-host and want your own cut of the atlas, or no basemap at all, the web app reads three settings at build time:

SettingWhat it does
VITE_MAP_TILESA {z}/{x}/{y} tile URL. Set it to off for the plain grid.
VITE_MAP_TRANSFORMWorld coordinates to map pixels, as sx,ox,sy,oy. The default is the transform any 16384px gdal2tiles cut of the standard atlas produces — change it only together with the tiles, or every unit ends up in the sea.
VITE_MAP_ZOOMMinimum and maximum zoom, as min,max.

What is on it

  • Units, colored by agency and shaped by kind, moving live.
  • Calls, colored by priority.
  • Hospitals, stations and hydrants, on a toggle.
  • Jurisdiction boundaries, on a toggle.
  • Postals, if your community uploaded a postal map.
  • Drawings — closures, perimeters, staging areas — put there by anybody with map_draw.
  • A heat map of where calls have been over the last thirty days, for supervisors deciding where to post people.

What you can do on it

ActionHow
Select a callClick it. Calls win over units under the cursor, because a click usually means "that call".
Create a call where you clickedRight-click.
Assign a unitDrag it from the status board onto the map.
Follow a unitf <callsign>, or click it on the board.
Fit everythingThe fit button.

How it frames itself

The first frame that has data fits the camera around what is actually happening, rather than opening on the whole island with every unit as a dot in one corner. After that the camera is yours.

Privacy on the map

Units marked plain-clothes are hidden from everyone except dispatch, on by default. Federal agencies can start hidden from other agencies' maps entirely. Off-duty players are never on it at all.

There is no satellite tile layer behind the map yet — it draws in game coordinates with no imagery underneath. Tiles layer in behind everything described here without changing any of it when that lands.

BOLOs, alerts & messages

BOLOs

Issue one from + BOLO, Alt + B, the BOLOs panel, or /bolo in game. A BOLO has a subject — person, vehicle, plate, property or other — a title, a body, an optional plate, and the agencies it is for.

Broadcasting one pushes it into the game: it reaches every terminal and shows in the notification system of every unit it is addressed to. A BOLO with a plate on it also joins the hotlist, so an ALPR read of that plate comes back as a hit.

BOLOs are canceled rather than deleted, so the record of what was out and when survives.

Alerts

The Alerts panel is the feed of things that happened which are not yet calls: panic buttons, backup requests, alarm activations, ALPR hits, toll violations, radio emergencies and anything a third-party script sent in.

What becomes a call and what stays an alert is your community's decision, set per alert type in the rules engine — see Integrations & alert rules. A dispatcher can dismiss an alert, or act on it.

Panic

/panic, the radio emergency button, or a phone script. It goes to every dispatcher immediately, creates a priority 1 call with the officer's position, and the unit's status goes to panic — which puts it at the very top of the board, above everything else, with its own color.

This is the one thing in the system that is deliberately impossible to miss.

Messages

Text between dispatch and units, on channels. It reaches every terminal and every dispatcher screen straight away. A dispatcher with message:broadcast@tenant can send to everyone at once.

Messages are kept for 30 days by default, which is adjustable. They are not a record of anything — use a call note for that, because call notes live on the call's timeline and survive forever.

The lookup panel

The lookup panel
One box. It works out what you typed, and puts the dangerous part of the answer first.

Type a plate, a name, a serial, an address or a phone number and the panel works out what you meant. The answer leads with what would hurt somebody — warrants, armed and dangerous, officer safety flags — and then gives the identifying facts.

What the answer contains depends on who is asking. Law enforcement sees criminal history; fire and EMS see blood type, allergies, conditions and DNR; neither sees the other's half, and the panel says which half is missing rather than pretending it is empty. That sentence is the difference between a medic trusting the system and a medic assuming it is broken.

Run cards & response plans

Three different things, kept as three different fields on purpose, because conflating them is how fire statistics become meaningless:

  • A caller reports a structure fire. That is the call type.
  • Three engines, a ladder, a rescue, a chief and a medic roll. That is the response.
  • It turns out to be a cooking fire confined to the container. That is the incident type, coded afterward.

Levels are cumulative

A second alarm is everything in the first plus everything in the second, which is how a real escalation works — you do not cancel the first-alarm assignment when you strike the second. The platform follows that: raising a call from a first alarm to a second sends only the second alarm's apparatus, because the first alarm's units are already on the call and are skipped. Striking the same level twice sends nothing the second time.

The plans you start with

PlanFirst alarmCovers
Residential structure box 2 engines, 1 ladder, 1 rescue, 1 battalion, 1 medic Structure fire. Two engines put water on it and open it up; the rescue is the crew that goes in for anybody still inside, and the medic stands by for them as much as for the occupants. Second and third alarms add lines, a second ladder, a tanker, a division supervisor and rehab.
Commercial structure box 3 engines, 2 ladders, 1 rescue, 2 battalion, 1 medic A bigger building needs more water and more people before the first line is even charged. Heavier at every level.
Vehicle fire 1 engine Unless it is against a building, in which case it gets a box instead.
Brush & wildland 2 brush, 1 engine, 1 tanker Brush units first because they can leave the road, an engine for structure protection, and a tanker because there is rarely a hydrant where this happens. The extended attack adds air attack.
Alarm activation 1 engine, non-emergent Nine out of ten of these are burnt toast or a contractor — and the tenth is why somebody still goes. The upgrade level is a full box.
Vehicle extrication 1 rescue, 1 engine, 1 medic A rescue for the tools, an engine for the hose line standing by over the fuel, and a medic for the person in the car.
Hazardous materials 1 engine, 1 battalion Nobody commits until somebody has identified the product. The full hazmat level adds an entry team, decon and responder rehab.
EMS — single unit 1 medic The great majority of medical calls, and it needs nothing else.
EMS — cardiac arrest 1 medic, 1 engine, 1 supervisor An arrest needs hands more than it needs vehicles: enough people to rotate compressions without stopping. The engine company is there to be those hands.
EMS — multiple casualties 3 medics, 1 engine, 1 supervisor More patients than one crew can triage. Every level adds transport capacity, and command exists from the first minute because the hard part is sorting rather than treating.
EMS — interfacility transfer 1 ambulance, non-emergent Scheduled and routine, and it should never tone a station out.

Every one of these is a starting point. Every department argues about its own run cards; the point of shipping defaults is that a new community is not staring at an empty screen on its first structure fire.

Response modes

Each level is emergent (lights and siren) or non-emergent (normal traffic), and the mode goes down to the game with the assignment.

How a plan is matched to a call

In order, most specific first. The first one that matches wins, and the dispatcher is told which it was:

  1. A drawn box — a response area somebody drew on the map.
  2. The station district the call falls in.
  3. The call type's own plan.
  4. The old flat run-card count on the call type, if a plan was never configured.

Nothing matching is the normal answer for a police call — run cards are a fire and EMS idea and most calls do not have one.

Using it

Run card on the call panel shows what will be sent before it is sent, with a line saying how the plan was matched. A dispatcher committing six pieces of apparatus on a button press wants to have read the list first — and "why did this call get the commercial box" needs an answer.

Press Strike on a level and it fills that level's requirements with real apparatus.

How apparatus is picked

Due order first. A station's list is what the department decided in advance, and second-guessing it with a distance calculation is how you end up sending the wrong engine to a street that borders two districts. Only when the due list is exhausted — or was never configured — does it fall back to the nearest available unit of the right kind.

Shortfalls

If the plan asks for three engines and two are available, you are told so, loudly, naming what was short and by how much. That is deliberate: asking for three engines and getting two is the single most important thing to know on a working fire, and exactly the sort of thing a quieter interface would swallow into a notification nobody reads. The message says what it means — nothing else of that type is on duty and free, so mutual aid or a move-up is the next call.

The times, and why they are automatic

As each piece of apparatus changes status, the platform stamps a milestone: dispatched, en route, on scene, transporting, at hospital, available. Turnout and travel times are computed from those.

Nobody types any of it. A time a dispatcher has to remember to record is a time that does not get recorded, and the numbers a department is judged on are only ever right if they are automatic. First write wins for every stamp: a crew that goes en route, arrives, then goes en route again to the hospital has one turnout time, not two.

Who can do what

PermissionWho holds itWhat it is
run_card:viewEveryone on the firegroundSee the card you are running on.
run_card:strikeDispatchers, supervisors, commandEscalate a working fire to the next alarm.
run_card:manageCommand onlyEdit what an alarm level means.

Station alerting

Behind the Station alerting realism toggle. With it on, the Stations panel appears and the platform can set a firehouse off.

The stations panel
The Stations panel: each house, its apparatus, and whether it has been acknowledged.

Setting a house off

Three ways:

  • From a call — Tone out on the call panel tones the stations whose agencies are on it.
  • From the Stations panel — pick a station, pick the call it is for, or run it as a drill with no call at all.
  • Automatically, when a fire or EMS call is assigned, if you turned on automatic tone-out on assignment.
Toning out a station
The house lights up, the tones drop and the board shows the run. A drill does the same with no call.

A station whose alerting is switched off is refused rather than silently skipped — somebody pressed a button and is waiting for something to happen, and silence is the worst possible answer.

Acknowledgement

When somebody hits the reset at the watch desk in game, that comes back up and the panel shows the house as acknowledged, who hit it, which units are responding and against which call. A station that has not acknowledged stays visibly un-acknowledged, which is the whole reason to have a panel rather than just a button.

Setting stations up

Editing a station
Name, slug, tone set and the alerting switch — everything the game side keys on.

A station has a name, a slug, a tone set, an apparatus roster and an on/off switch for alerting.

The slug must match the station id in Sal's Kewl Station Alert. Without one, the game cannot match the station and the tone-out goes nowhere. The panel warns you when a station has no slug.

With Sal's Kewl Station Alert

If you run that resource, the two halves fit together with nothing to configure on the bridge side:

  • the CAD's tone-outs set the house off — lights, tones and the run on the board;
  • the station's call board reads the CAD's live board, so the wall screen shows what dispatch sees;
  • the watch-desk reset reports back as an acknowledgement.

Stations and apparatus created in game appear here on their own. See that resource's own manual for the game-side half.

Without it

Station alerting still works — the Stations panel, the tone-out record and the acknowledgement state all live on the platform. What you lose is the physical house lighting up, which is the part that happens in the world.

Records

A full page rather than a dock panel, at /cad/records, because looking somebody up properly is a sit-down job and a records system squeezed into a 320-pixel panel is a records system that gets abandoned.

The records page
The master indexes and the report list.

The indexes

IndexHolds
People The master name index. Identity, address, phone, physical description, licenses, flags, criminal history, medical details, known vehicles and associated reports.
Vehicles Plate, model, color, owner, registration, insurance, and whether it is stolen, flagged or impounded.
Firearms Serial number, type, registered owner, and whether it has been reported stolen or used in an incident.
Businesses Registered businesses, owners and addresses.
Property & evidence Evidence items with a chain of custody — every hand it has passed through, and when.
Reports Everything written, by type and status.

Where records come from

Mostly, nobody types them. On a framework server, characters, their owned vehicles and their licenses arrive from the game on their own — when a player loads in, when their job changes, and in a slow pass every few minutes. A dealership sale reaches the CAD as a change of owner on the plate.

The rest is created by officers as they work: a person who has never been in the system gets a record the first time somebody writes a report naming them.

On a fresh community, lookups legitimately return nothing. The records are in the CAD, not in the game, and the CAD has not met anybody yet. Import what you have from Settings → Import, or let records accumulate for a week. This surprises people often enough to be worth telling your staff up front.

Flags

Flags are what change how a name search reads, and they are the reason the lookup puts danger first:

FlagMeans
armed_dangerousArmed and dangerous.
officer_safetyA caution without a specific weapon.
felony_convictionPrior felony.
mental_healthKnown mental health history.
medical_alertA condition responders need to know about.
missingReported missing.
gangKnown affiliation.
probation / paroleUnder supervision.
do_not_detainUsually an undercover officer or a protected witness. Read it before you act on anything else.
customWhatever your community needs.

Search

One box across every index. It will take a plate, a partial name, a serial, a phone number, an address or a report number and work out which it is. The lookup panel in the workspace and the terminal's lookup tile are the same search, shaped for smaller screens.

The medical and criminal split

Worth repeating because it is the behavior people ask about most:

  • Law enforcement — the eight LEO department types — read criminal history.
  • Fire and EMS read blood type, allergies, conditions and DNR.
  • Neither gets the other's half by default, and the interface says which half is missing rather than showing an empty box.

Both are ordinary permissions — person_criminal:view and person_medical:view — so a community that wants its fire investigators to read criminal history grants it.

Reports & the form engine

The report types

TypeWritten by
IncidentAnyone
ArrestLaw enforcement
CitationLaw enforcement
WarningLaw enforcement
Field interviewLaw enforcement
Use of forceLaw enforcement. Agency-private by default.
PursuitLaw enforcement
EvidenceLaw enforcement, corrections
ImpoundLaw enforcement, tow
Warrant requestLaw enforcement
Fire incidentFire. Its own permission; statistics are built on it.
Fire inspectionFire
Fire pre-planFire
Apparatus checkFire, EMS
Patient care reportEMS. Its own permission, agency-private by default.
Tow jobTow
Shift logAnyone
SupplementAdded to an existing report

The life of a report

StatusMeans
draftBeing written. Only the author sees it.
submittedHanded in. With approvals off, it goes straight to approved.
returnedA supervisor sent it back with a note.
approvedAccepted.
lockedNo longer editable. Changes go in a supplement.
amendedA supplement was added.
sealedA judge sealed it. See Courts.

With Supervisor review off — the default — a submitted report is approved and locked. Turn it on and reports wait for a supervisor, who can approve or return with a note.

Forms are data, not code

A report is a form definition — a versioned schema of sections and fields — plus the values. The form drives the sections, the fields, their widths, and which of them get copied into indexed columns so search can find them.

That means a community that adds a field to its arrest report sees it on the next load, in the browser and in the in-game terminal, with no release from us. Building your own forms is a Pro feature; editing the shipped ones is available to everybody.

Field types

TypeWhat it is
text, textarea, number, currencyThe plain ones.
select, multiselect, checkboxChoices.
date, datetime, timeWhen.
personLinks to a person record, with lookup.
vehicleLinks to a vehicle record, with plate lookup.
chargesThe charge picker, which totals fines and jail time from your penal code.
locationA map point plus street and postal.
unitUnit picker from the current roster.
vitalsRepeating vitals rows, for a patient care report.
attachmentsFile upload.
signatureA typed name plus a timestamp. It is not a legal signature and says so.
nfirs_codeThe NFIRS code picker, shown only with detailed fire coding on.

Fields can repeat, can show conditionally on another field's value, and can be marked to copy into an indexed column — person, vehicle, location, when it happened, the narrative, the charges or an amount — which is what makes a report findable by search afterwards.

Short forms are deliberate

The shipped forms have only the fields most people actually fill in, and every extra section is marked optional so you turn it on on purpose. A ten-section arrest report is how a records system stops being used; it is better to have a hundred complete short reports than twenty complete long ones and eighty abandoned drafts.

Where reports are written

Anywhere. The browser, the in-game terminal, or started in one and finished in the other — it is the same draft against the same form. /cite, /arrest and /incident open prefilled from what the game knows.

Attachments

Photographs, bodycam links and files, against your plan's storage allowance. Uploads are scanned where a scanner is configured; your community's allowance is on the owner panel's first page.

Sealing

A judge can seal a report, which takes it out of every ordinary view and search. Unsealing is also a judicial action, and both are recorded in the audit log with who and when.

Citations, fines & payment

Writing one

/cite in game opens the form with the nearest person, the plate in front of the car and the street already filled in. From the browser, Records → new citation.

The charge picker reads your community's penal code and totals the fine and the jail time as you add charges, so an officer is never adding up numbers.

The penal code

Your charge list, editable under Settings, with a class, a fine and jail time per charge. The five classes:

ClassTypically
infractionA fine, no jail. Most traffic.
citationA written citation with a court date.
misdemeanorFine and short jail.
felonySerious. Usually a case file.
ordinanceLocal rules.

The code can be imported and exported, which is the easy way to move a penal code between a test community and a live one, or to adopt one your community already wrote.

Billing it to the player

Bill on a citation sends a fine.issue.v1 down to the game, which charges the character through your framework. This is one of the commands that survives an outage: it goes through a job queue with retries rather than being dropped, because a fine that silently vanishes is a fine the player never pays and nobody can explain.

A fine can be issued against an offline character and lands when they next load in.

Paying it

Two ways:

  • From the citizen portal, if your community allows online payment. The player sees what they owe and pays it.
  • In game, through whatever your framework or a payment script does. The result comes back as payment.v1 and the citation is marked paid.

Court dates

A citation can carry a court date, which puts it on the docket when court workflow is on and shows in the player's Court tab. With court workflow off, the date is just a field — useful, but nothing schedules itself.

Licenses & the DMV

The license types

driver, cdl, motorcycle, weapon, hunting, fishing, pilot, boating and business.

The statuses

StatusMeans
validIn good standing.
suspendedTemporarily withdrawn, optionally with a date it lifts.
revokedTaken away. Needs reissuing, not reinstating.
expiredLapsed.
noneNever held one.

Who decides what

This is the clearest case of the split described in How it fits together:

  • The game decides whether a license exists. A character who passed the driving test has one.
  • The CAD decides whether it is in good standing. A suspension is a CAD action.

The framework keeps a copy so in-game scripts — the vehicle dealer, a traffic stop check — can read it without a round trip. Every change the CAD makes goes down to the game and is applied to the character, in qb metadata, esx_license or ND's licenses, depending on your framework.

Suspensions reach the game, even offline

Suspend a license while the player is offline and it lands the next time they load in. The platform re-sends any standing suspension whenever the game copy disagrees with it, which is what makes that work rather than depending on a message arriving at the right moment.

If you would rather the CAD recorded suspensions without touching the game, set Config.Sync.ApplyLicenses = false.

Reacting to it in your own scripts

Listen for sals_kewlcad:licenseChanged(data, applied). The second argument says whether the bridge already changed the character's license in the framework, so a DMV script or a vehicle shop can do its own thing without double-applying.

Points, if you want them

The DMV agency template exists for communities that roleplay the counter: DMV clerks can issue, suspend, revoke and reinstate, register vehicles, and create people. Give the role to whoever staffs it and law enforcement keeps only license:view, which is usually what a community wants — an officer sees the status and the DMV changes it.

Courts, warrants & the jail

Mostly behind the Court workflow toggle, because most communities do not roleplay court and a module they will not use is clutter. For the ones that do, this is where the chain ends: a stop becomes an arrest, the arrest becomes a case, the case becomes a sentence, and the sentence starts a timer in the game.

The courts page
The docket, case files, warrants and the jail.

Warrants work either way

Warrants are law enforcement, not court, and they work whether or not court workflow is on. The lifecycle:

StatusMeans
requestedAn officer asked for one, with probable cause.
signedA judge granted it.
activeOut and servable.
servedExecuted.
recalledWithdrawn.
expiredTimed out.
deniedA judge said no.

Four types: arrest, search, bench and failure_to_appear. Requesting and signing are deliberately different permissions — officers request, judges sign.

An active warrant shows at the top of that person's lookup, in game and in the browser, and puts their known plates on the hotlist so an ALPR read comes back as a hit.

Cases

StatusMeans
openFiled.
arraignmentFirst appearance.
pending_trialScheduled.
in_trialBeing heard.
adjudicatedDecided.
dismissedThrown out.
appealedUnder appeal.
closedFinished.
sealedOut of ordinary view and search.

Pleas are not_guilty, guilty, no_contest or none. Verdicts are guilty, not_guilty, dismissed, mistrial or plea.

The docket

Scheduled hearings, with the case, the date and who is due. It is also what a player sees in the Court tab of their portal, so scheduling a hearing is how you tell somebody to turn up.

Sentencing

A sentence carries jail time, a fine and community service. Handing one down sends sentence.start.v1 to the game, which starts the jail clock on that character. Like a fine, this is one of the commands that survives an outage.

The jail

The corrections agency gets the booking queue, the cell roster and sentence timers. Booking statuses are booked, held, sentenced, released and escaped.

The three court roles

RoleCan
Clerk of Court File cases, schedule hearings, keep the docket, read citations, arrests, warrants and reports.
Attorney District attorney or public defender: read case files, criminal history, evidence and reports, update cases and file for hearings.
Judge Everything above, plus: sign and revoke warrants, rule on cases, hand down sentences, revoke licenses, seal and unseal cases and reports, and edit the penal code.

Sealing is a judicial power and nothing else in the platform grants it — which is the point. A sealed record is one the police cannot quietly unseal.

Tow & impound

Tow jobs

A tow is requested with /tow from an officer at a scene, created by a dispatcher, or raised by a script. It lands in the tow queue, which drivers see in their terminal and dispatch sees in the Tow panel.

StatusMeans
queuedWaiting for a company.
offeredOffered to whoever is next on the rotation.
acceptedTaken.
on_sceneDriver has arrived.
completeDone.
canceledCalled off.

First come, or rotation

Two models, chosen with the Tow rotation realism toggle:

  • Off (default) — a job sits in the queue and whichever driver grabs it first gets it. Simple, and fine on a server with one tow company.
  • On — jobs are offered to whichever company is next in line, in order, the way a real rotation list works. Worth turning on the moment you have two companies competing, because otherwise the one with the fastest clicker gets everything.

The rotation itself is managed by anyone with tow_rotation:manage@tenant — a yard manager or a dispatcher.

Impounds

An impound records the vehicle, where it was taken from, why, the release fee, the lot and space, an inventory and condition photographs.

StatusMeans
impoundedIn the lot.
holdInvestigative hold — it is not going anywhere until somebody says so.
releasedReturned to the owner.
auctionedDisposed of.

Impounding a vehicle sends impound.flag.v1 to the game, so the vehicle is flagged there too and your garage script can refuse to hand it over. Releasing it sends the opposite.

The two tow roles

RoleCan
DriverTake tow jobs, create impounds, write reports and invoices.
Yard ManagerRun the rotation, release vehicles, manage drivers, and configure the agency.

Hospitals & EMS

The hospital board

Destinations with their current diversion status, so a medic transporting is sent somewhere that can take the patient:

StatusMeans
openTaking everything.
limitedTaking some things. Call first.
closedOn diversion. Go somewhere else.

Anyone with hospital_board:manage sets the status. Fire, EMS and dispatch read it.

Transport

/transport puts a unit in the transporting state; /athosp marks arrival at hospital. Both stamp milestones, which is where the transport and at-hospital times in the fire and EMS statistics come from.

Patient care reports

The PCR is its own resource with its own permissions, and it is agency-private by default — only the EMS agency that wrote one reads it. That is the one default we would recommend leaving alone.

With Detailed fire and EMS coding on, the PCR gets the fuller vitals layout: repeating vitals rows with times, rather than a single set.

The medical record a medic actually reads

Blood type, allergies, conditions and the DNR flag, from the person's record. Players fill those in themselves in the citizen portal, and no framework tracks any of it — so the quality of this depends entirely on whether your players know the portal exists. It is worth a pinned message.

EMS roles

Medic, Supervisor and Command. A medic reads and updates patient medical details, writes and submits patient care reports, and sees the hospital board, station alerting and run cards. Supervisors approve PCRs.

Signs & highway boards

The electronic signs in the game — overhead gantries, roadside boards, variable message signs — as reported by the sign resource running on your server.

The signs panel
Every sign the game has reported, and what it is currently showing.

It is a mirror, not a second source of truth

The game decides what a sign looks like; the CAD only asks it to show something. The sign resource sends its whole inventory up whenever a board is placed, moved, deleted or changes what it says, and that replaces the previous set wholesale. So the panel is always showing what is actually out there.

Putting a message up

Writing a message to the signs
Pick a kind, write the message, choose which boards.

Five kinds, and the sign resource decides what each looks like because the CAD does not know what a gantry looks like:

KindFor
amber_alertA missing child or a vehicle to look for.
boloA BOLO pushed to the highway.
closureA road closure.
noticeAnything else.
clearTake it down.

A message can carry an expiry, so a closure takes itself down.

Modes

Setting a named mode on the signs
A named mode is a preset the sign resource owns.

Instead of a message you can set a named mode — a preset defined on the game side. The panel tells you when a board is being held by a mode, by the CAD, or by another resource, so two systems fighting over one sign is visible rather than mysterious.

With Sal's Kewl Signage

Amber alerts, BOLOs and closures push from the CAD onto the boards, and radar readings come back up as plate reads which can become ALPR hits. Configured in that resource under Config.Cad; nothing to set on the bridge side.

From your own script

Report your signs with ReportSigns(signs, modes) and the CAD's panel draws them. See Exports & events for scripts.

Statistics

Three questions, which is all a community actually asks: how fast are we getting there, what are we getting called to, and who is playing.

The statistics page
Response times, call mix and activity, over 24 hours, 7, 30 or 90 days.

Response times

Three intervals, each measured from the append-only milestones on the call rather than from anything a client reported — which is why they are worth putting on a screen at all:

IntervalFromTo
To dispatchThe call arrivingA unit being assigned
TurnoutAssignmentEn route
TravelEn routeOn scene

What we get called to

Call volume by type, by priority, by agency and per day. Useful for the obvious reason and for one less obvious one: it shows which of your call types nobody ever uses, which is usually a sign the list is too long.

Activity

Who was on duty, for how long, over the period. Also on the Roster panel as a seven-day figure per member.

Fire and EMS statistics

A separate page, because the questions are different. A police commander asks how many calls and how long to arrive. A fire chief asks about turnout time, per station and per apparatus, because those are the numbers that justify a budget or lose one.

Everything on it is read from the response milestones stamped automatically as each piece changes status. None of it is typed by anyone, which is the only reason it is worth reading.

Why the 90th percentile

The page leads with the 90th percentile rather than the average, and quotes it against NFPA 1710:

BenchmarkNFPA 1710
Turnout, fire suppression80 seconds
Turnout, EMS60 seconds
Travel, first arriving engine4 minutes

All three are stated at the 90th percentile in the standard, and that is the point: an average hides the bad night that everyone remembers. A department that is slow and consistent reads as slow, which is the honest answer.

The fire page also shows

  • By station and by apparatus — where the slow numbers actually are.
  • Escalation — how often a first alarm was not enough, which is the number that tells a chief their run cards are wrong.
  • What we were actually called to — incident types as coded afterward, against the call types as reported. The gap between those two is interesting on its own.
  • Property loss, where it was recorded.

Exporting

Anyone with stats:export can take the underlying rows out. Advanced dashboards are a Pro feature; the basic figures are on every plan.

Integrations & alert rules

The integrations page
Connected products, what happens to incoming alerts, and outbound webhooks.

Connected products

The other Sal's Kewl resources are detected at runtime and need no configuration on the bridge side. Put them on the same server and they find each other:

ProductWhat it adds
Sal's Kewl Dispatch Your 9-1-1 call takers work a call and dump it into the CAD whole: the caller, the location, the transcript, the answers they gave, the people and vehicles mentioned, and the times. The CAD answers with the call number it was given, so the call taker can read it back.
Sal's Kewl Radio Channel activity and the emergency button reach the Radio panel. A radio emergency becomes a priority 1 call.
Sal's Kewl Station Alert Tone-outs set the firehouse off, the station's wall board reads the CAD's live board, and the watch-desk reset comes back as an acknowledgement.
Sal's Kewl Signage Amber alerts, BOLOs and closures go out to the highway boards; radar readings come back as plate reads.

Shims for other dispatch resources

Three are forwarded without editing them: ps_dispatch, cd_dispatch and core_dispatch. If your server already runs a robbery or crime script that talks to one of those, its alerts reach the CAD the moment you switch the shim on.

The alert rules engine

Any script — ours or yours — can send an alert. What happens next is your community's decision, not ours, and that decision is a rule.

Three possible actions:

ActionWhat happens
Create a callIt becomes a call of the type and priority you chose, routed to the department types you named.
Tell dispatch onlyIt appears in the Alerts panel. A dispatcher decides whether it is worth a call.
Ignore itDropped.

The three knobs that keep the feed usable

KnobWhat it does
De-duplicate Merge a second alert of the same type within x meters and y seconds. Four people reporting the same crash is one call.
Cooldown Ignore further alerts of this type for a while, wherever they are. Right for speed cameras.
Reporting chance The chance an alert is reported at all, 0 to 1.

Reporting chance looks like a bug the first time you meet it, and it is not. "60% of drug sales get reported" is a roleplay balance setting. Not every crime in a city gets called in, and a server where every one does turns dispatch into a metronome. It sits next to the rest of the rule because it is the same kind of decision.

There is also a rate cap per minute per resource, which is what stops a misbehaving script from filling your queue.

The rules you start with

Chosen so a server that plugs in a robbery script and a shots-fired detector gets sensible dispatch behavior with no configuration at all.

Alert typeActionBecomesPDe-dupeChance
robbery_storeCallROB1120 m / 5 min—
robbery_bankCallROB1200 m / 10 min—
robbery_jewelryCallROB1150 m / 5 min—
burglary_houseCallBURG260 m / 5 min80%
alarm_businessCallALARM350 m / 10 min—
alarm_residentialCallALARM350 m / 10 min—
alarm_fireCallALARMFIRE350 m / 10 min—
shots_firedCallSHOTS1100 m / 1 min—
vehicle_theftCallGTA380 m / 3 min70%
carjackingCallGTA180 m / 2 min—
drug_saleNotify——2 min cooldown40%
fightCallDIST350 m / 3 min60%
medical_alertCallMEDB230 m / 2 min—
speed_cameraNotify——30 s cooldown—
alpr_hitNotify——60 s cooldown—
toll_violationNotify——60 s cooldown—
suspicious_personCallSUSP460 m / 5 min50%
phone_panicCallPANIC130 m / 1 min—
radio_emergencyCallPANIC130 s—

Every row is editable, and you can add rules for alert types of your own invention.

The plate hotlist

The bridge keeps a list of plates worth knowing about in memory, so an ALPR read can be checked without a round trip. It is derived from warrants, BOLOs and vehicle flags rather than being a second list that goes stale — so flagging a car makes it an ALPR hit immediately, and clearing the flag stops it.

IsPlateFlagged(plate) is the export; it returns whether and why.

Outbound webhooks

Pro and above. Send CAD events to your own bot or website — a Discord bot that posts new calls, a status page, a leaderboard. Configured on this page, with a per-plan cap on how many.

Nights ERS callouts

If you run Nights Emergency Response Simulator (night_ers) beside the bridge, its callouts reach your dispatch board and can be worked like anything else. It is detected at runtime: install it and the integration starts, remove it and nothing breaks, and it is never listed as a dependency.

The rule this is built around

An offered callout is never written to the CAD. The simulator offers far more work than a community answers, and a record of every refusal would bury the calls you actually ran underneath the ones nobody took.

So an offer appears on the board as an offer — with no call number — and is held only in memory, with a short life. If nobody takes it, it expires and there is nothing to clean up, because nothing was written: no call, no number, no entry in any history.

Accepting is what creates the call. From that moment it is an ordinary call: a number, a timeline, units, notes, and it closes like any other. There is no separate ERS code path and no reduced history, which is the point — "tracked as if it were a real call" is only true if it stops being a special case the moment it becomes one.

What reaches the board

In ERSIn the CAD
A callout is offeredAn offer on the board, no call number. Dispatch sees it, and so does the unit it was offered to.
Nobody takes itIt disappears. Nothing was saved.
A unit acceptsA real call, numbered and timed, with the unit on it. The people and vehicles ERS invented arrive with it, as the call's narrative and flags.
The unit arrivesThey go on scene, which stamps the call the same way an arrival always does.
The callout completes, or endsThe call closes, with a disposition that keeps the difference between the two.
A patient is delivered to hospitalA line on the call's timeline.
A traffic stop or pursuit startsA real call immediately — there is no offer step, because the unit is already out with it. Pursuits come in as priority 1.
A stop or pursuit endsA note on the call. The call is not closed: a stop that turns into an arrest is still being worked after ERS considers its scene finished.

Dispatch can push one

A dispatcher can send a callout offer to a particular unit, which is what makes the CAD in charge of the simulator rather than a log of what it already decided. The bridge answers that by calling ERS's own offer function for that player.

Setting it up

  1. Install night_ers as its authors describe.
  2. Update the bridge to 2.2.0 or later.
  3. Leave Config.Ers.Enabled at true, and set OfferTimeoutMs to match your ERS offer timeout.
  4. Restart both. The CAD's game server page lists night_ers among the detected resources once the bridge has said hello.

Turn stops or pursuits off with Config.Ers.TrafficStops and Config.Ers.Pursuits if you would rather those stayed off the board — see the configuration reference.

What it does not do

  • An expired offer leaves no trace. There is no list of callouts your community turned down, by design.
  • ERS decides when its own offer lapses. The CAD's timeout only controls how long the offer is shown, and it holds the detail slightly longer than that so a unit accepting a moment late still gets a call with everything on it.
  • Nothing about your own calls, units or records changes when ERS is installed. An ERS call is a call.

The public API & webhooks

Pro and above. A documented, versioned REST surface so third-party scripts and your own website can read from the CAD.

Keys

Public API keys come from the same place as bridge keys but have a different scope, deliberately: a bridge key cannot read the public API and an API key cannot send game events. One key, one job. Both can be rotated and revoked from the owner panel.

The endpoints

EndpointReturns
GET /v1/public/statusLive server status — units on duty, open calls. For your community's own website.
GET /v1/public/callsOpen calls. Sensitive calls are excluded.
GET /v1/public/unitsUnits on duty.
GET /v1/public/bolosActive BOLOs.
GET /v1/public/plate/:plateA plate check. What most third-party scripts actually want.
GET /v1/public/schemaThe developer reference: every event type and its shape.
GET /v1/public/schema/:typeOne event's shape, for wiring up an integration.

The schema endpoint is the documentation

It is published from the same definitions the gateway validates against, so the shapes it describes and the shapes that are accepted cannot drift apart. That is the failure mode which makes integrating with somebody else's CAD miserable, and it is worth the one endpoint to avoid.

Reads here, writes through the bridge

The public API is read-mostly on purpose. Writes go through the bridge endpoints, which already have signing, nonces and idempotency worked out — there is no reason to build a second way in with weaker guarantees.

If your script runs outside the game, or you would simply rather use HTTP, the bridge's own alert endpoint takes third-party alerts over plain HTTPS with the same authentication and the same rules engine.

Rate limits

Per minute, per plan: 60 on trial, 300 on Community, 1,200 on Pro and 6,000 on Network. Bridge events are counted separately and far more generously, because a busy server legitimately sends a lot.

Exports & events for scripts

The Lua surface the bridge gives your own resources. Call them as exports.sals_kewlcadbridge:Name(...), and wrap each in pcall — the bridge may be reconnecting.

Alerts and calls

-- Third-party alert. The one most integrations want.
exports.sals_kewlcadbridge:SendAlert({
    title = 'Silent alarm',
    kind = 'alarm',
    coords = GetEntityCoords(PlayerPedId()),
    priority = 2,
})

exports.sals_kewlcadbridge:CreateCall({
    typeCode = '10-90', location = '...', narrative = '...',
})
exports.sals_kewlcadbridge:AddCallNote(callNumber, 'text')

Units and duty

exports.sals_kewlcadbridge:IsOnDuty(source)          --> boolean
exports.sals_kewlcadbridge:GetOnDutyUnits()         --> table
exports.sals_kewlcadbridge:SetUnitStatus(source, '10-8')
exports.sals_kewlcadbridge:GoOnDuty(source, { unitId = '...' })
exports.sals_kewlcadbridge:GoOffDuty(source)
exports.sals_kewlcadbridge:Panic(source, 'note')
exports.sals_kewlcadbridge:RequestBackup(source, 'urgent', 'note')

SetUnitStatus takes a state — en_route, on_scene, available, busy, out_of_service — as well as a code, for resources whose verbs are states.

Lookups on a unit's behalf

-- Answered by event, exactly once, so a radio can read it back on the air.
local ref = exports.sals_kewlcadbridge:Lookup(source, 'plate', '46EEK572')

AddEventHandler('sals_kewlcad:lookupAnswered', function(ref, detail, err)
    -- detail = { lines = {...}, warnings = {...} }
    -- or err  = 'offline' | 'no answer' | reason
end)

Plate readers and ANPR

exports.sals_kewlcadbridge:IsPlateFlagged(plate)     --> boolean, reason
exports.sals_kewlcadbridge:PlateRead({
    plate = '...', coords = ..., camera = 'I-5 North',
})

Radio, tone-outs and tolls

exports.sals_kewlcadbridge:RadioActivity(channel, talking, source)
exports.sals_kewlcadbridge:RadioEmergency(source, channel)

-- stationId is the CAD's id, as it arrived on sals_kewlcad:toneOut.
-- `who` is optional: the player who hit the reset, or a name.
exports.sals_kewlcadbridge:AcknowledgeToneOut(stationId, respondingUnits, callNumber, who)

exports.sals_kewlcadbridge:TollPass({ plate = '...', plaza = '...', amount = 4.50 })

The live call board

For anything that shows the CAD inside the game: a station TV, a wall in the dispatch office. The platform pushes the board whenever it changes, only while something here wants it.

exports.sals_kewlcadbridge:ActiveCalls()
--> { { id, number, kind, typeCode, typeLabel, priority, status, title,
--      location, postal, coords, assigned = { 'E1', 'M7' }, primaryUnit,
--      agencies, createdAt, updatedAt }, ... }   (times in Unix ms)

exports.sals_kewlcadbridge:Roster()
--> { { id, callsign, label, kind, deptKind, agency, state, status,
--      statusLabel, statusColor, onDuty, available, callId, callNumber,
--      panic, src }, ... }

exports.sals_kewlcadbridge:BoardAge()   --> seconds since the board arrived
exports.sals_kewlcadbridge:BoardAt()    --> when it was built, Unix ms
exports.sals_kewlcadbridge:WantBoard()  --> keep it coming while my resource runs
exports.sals_kewlcadbridge:ReleaseBoard()

Or listen instead of polling:

AddEventHandler('sals_kewlcad:boardChanged', function(board) end)  -- { at, calls, units }
AddEventHandler('sals_kewlcad:boardStale', function(ageSeconds) end)  -- the link dropped

Sensitive calls are never on the board. Station Alert and Sal's Kewl Dispatch are detected and need no call to WantBoard().

Characters, vehicles and licenses

The bridge syncs these itself when a character loads and when a job changes. These are for the moments it cannot see.

-- After a character creator finishes, or a name change.
exports.sals_kewlcadbridge:SyncPlayer(source)

-- A dealership sale or a transfer. The plate is the key; sending it again
-- with a new owner records the sale.
exports.sals_kewlcadbridge:SyncVehicle({
    plate = 'ABC 123',
    model = 'sultan',              -- optional
    color = 'Metallic Black',      -- optional
    ownerCharacterId = citizenid,  -- or source = playerId
})
-- Or, as a server event:
TriggerEvent('sals_kewlcad:vehicleChanged', { plate = 'ABC 123', source = src })

exports.sals_kewlcadbridge:SyncAll()

-- Open the terminal or the citizen portal from your own script:
-- a tablet item, a phone app, a "my record" button.
exports.sals_kewlcadbridge:OpenMdt(source, '/mdt')
exports.sals_kewlcadbridge:OpenPortal(source)

Signs

-- The whole set, every time; the CAD's Signs panel draws it.
-- Sal's Kewl Signage does this itself.
exports.sals_kewlcadbridge:ReportSigns(signs, modes)

Migrating from another dispatch resource

exports.sals_kewlcadbridge:DumpToCad(package)
exports.sals_kewlcadbridge:UpdateDumpedCall(sourceCallId, update)

Push an existing call in and keep updating it by your own id, so a resource you are moving away from can run alongside the CAD during the changeover.

Health

exports.sals_kewlcadbridge:IsConnected()   --> boolean
exports.sals_kewlcadbridge:QueueDepth()    --> messages waiting

Events you can listen for

EventFires when
sals_kewlcad:callAssignedA call is assigned. Client and server, the latter with the player it landed on.
sals_kewlcad:lookupAnsweredA lookup you requested comes back. Exactly once.
sals_kewlcad:boardChangedThe live board changed.
sals_kewlcad:boardStaleThe link dropped and the board is now old.
sals_kewlcad:toneOutA station is toned out.
sals_kewlcad:licenseChangedA CAD license decision arrived. Second argument says whether the bridge already applied it.
sals_kewlcad:vehicleChangedA vehicle changed hands. Server event you raise.
sals_kewlcad:eventResultThe platform's answer to any event you sent.
sals_kewlcad:dumpAcceptedA dumped 9-1-1 call was accepted, with the CAD number it was given.
sals_kewlcad:linkChangedThe link to the platform went up or down.

These event names are part of the contract. Renaming one breaks whatever a community built on it, so they are treated the same way as the wire protocol — see the next section.

The event contract

Everything that crosses between your server and the platform. You do not need this to run a community; you need it to write something that talks to one.

The envelope

Every message, in both directions, carries the same wrapper:

{
  "v": 1,
  "id": "a unique id for this message, 8-64 characters",
  "type": "unit.status.v1",
  "ts": 1790304000000,
  "seq": 41,
  "data": { }
}

seq is optional on the way up and is what makes gap recovery possible: the bridge numbers its own messages, so the platform can ask for what it missed.

Versioning

Every type carries its version in its name — call.create.v1, never call.create. That is the entire versioning strategy, and it is deliberate:

  • Adding an optional field is not a new version. Old bridges keep working because the field is optional; new ones send it.
  • Removing a field, renaming one, or changing what a value means is a new version. call.create.v2 ships alongside v1, both are accepted, and v1 is removed only once no bridge in the wild still sends it.
  • An unknown type is refused, not ignored. A bridge sending something the platform does not recognize gets an error naming the type. Silently dropping it is how a community spends an evening wondering why nothing arrives.

The bridge reports its own version in bridge.hello.v1, so the platform knows what a given server is capable of before it sends anything down.

Game to CAD — 24 types

TypeWhat it reports
bridge.hello.v1Resource version, framework, detected integrations, server name, player count, last sequence seen.
bridge.heartbeat.v1Player count, on-duty count, queue depth, uptime, and whether anything wants the board.
unit.location.v1Up to 300 unit positions in one message.
unit.duty.v1Somebody went on or off duty, with the agency, job, grade, callsign and unit kind.
unit.status.v1A status change, by code or by state.
call.create.v1A new call: type, title, priority, location, narrative, source, caller, plate, agencies, whether to attach the sender, and a dedupe key.
call.note.v1A note on a call.
call.attach.v1A unit putting itself on a call.
call.detach.v1A unit clearing from one.
panic.v1Emergency, with coordinates, and whether it came from a radio.
backup.request.v1Backup, with urgency 1 to 3.
lookup.request.v1A lookup: plate, name, serial, VIN, phone or anything.
alert.generic.v1A third-party alert. The one most integrations use.
framework.sync.v1Characters, vehicles and licenses — up to 500, 1,000 and 1,000 respectively.
payment.v1Something was paid: a citation, an invoice, an impound fee, bail.
radio.activity.v1Somebody keyed up or stopped talking on a channel.
radio.emergency.v1The emergency button on a radio.
alerting.ack.v1A station acknowledged a tone-out.
toll.pass.v1A plate through a gantry, paid or not.
signs.inventory.v1Every sign on the server and the named modes available.
alpr.read.v1A plate read, with camera, speed and limit.
dispatch.dump.v1A worked 9-1-1 call: caller, location, narrative, answers, transcript, people and vehicles mentioned, call taker and times.
dispatch.update.v1An update to a dumped call, by your own id.
link.request.v1A player ran /cadlink.

CAD to game — 18 types

The bridge treats these as instructions, never as truth to store. It shows a notification, sets a waypoint, or calls a framework export.

TypeWhat it asks for
config.push.v1The current status codes, call types, command names, job-to-agency map, intervals and feature flags.
unit.assigned.v1You have a call: number, type, priority, location, narrative, and whether to set a waypoint.
unit.unassigned.v1Cleared, and whether to drop the waypoint.
unit.status.set.v1Your status is now this, and whether it was forced.
bolo.broadcast.v1A BOLO, to the agencies named.
alerting.toneout.v1Tone this station: station, call, type, location, units, tone, narrative.
fine.issue.v1Charge this character. Survives an outage.
sentence.start.v1Start the jail clock: jail seconds, fine, community service, facility. Survives an outage.
license.suspend.v1Suspend, revoke or reinstate a license, with a reason and an until date. Survives an outage.
impound.flag.v1This plate is impounded, or no longer is.
plate.hotlist.v1The plates worth knowing about — replace, add or remove, up to 2,000.
billboard.set.v1Put this on the signs, or a named mode, with an expiry.
board.snapshot.v1The whole live board: up to 200 calls and 500 units.
notify.v1Show somebody a message, at a level, optionally with a waypoint.
lookup.result.v1The answer to a lookup: lines, warnings, and a path to open in the terminal.
mdt.open.v1Open the terminal at this path with this session token, prefilled.
link.code.v1Here is the player's six-character link code and how long it lasts.
ack.v1Up to this sequence number was accepted; these were rejected, and why.

Why the board is a snapshot

Three decisions, each the cheaper one:

  • A snapshot, not a delta. The consumer compares it against the last one, so a lost message costs nothing and nobody ever has to reconcile a stream. Open calls are a small set and the whole board is a few kilobytes.
  • Debounced per community. A dispatcher assigning three units to a structure fire is one push, not three.
  • Only to servers that asked. The heartbeat carries a flag while something on that server wants it, so a server running nothing that reads the board never receives one.

Generated, not written

The full field-by-field reference for every type above is generated from the same definitions the bridge validates against and the gateway parses with. Hand-written event documentation goes stale in about two releases and nobody notices until somebody has already built an integration against the wrong shape.

Realtime rooms & gap replay

How a dispatcher's screen stays current without polling.

Rooms

A browser subscribes to rooms, and the permission on each is checked once, at subscribe time, rather than on every event. That is why events need no per-message filtering on the way out, which is most of why this is cheap.

RoomPermission needed
callscall:list@tenant
unitsunit:list@tenant
mapmap:view@tenant
bolosbolo:list@tenant
alertscall:list@tenant
call:<id>call:view@tenant
agency:<id>unit:list@agency
channel:<id>message:view@tenant
user:<id>layout:view@self
dockethearing:list@tenant
towtow_rotation:view@tenant
hospitalhospital_board:view@tenant
radioradio:view@tenant
stationsstation_alerting:view@tenant
signssigns:view@tenant
admintenant_settings:view@tenant

Every room name begins with the community's own id, and a subscribe to a room whose prefix is not the connection's own community is refused before the permission is even considered.

One socket, however many windows

Five pop-out windows do not mean five connections. The first window to load elects itself leader, holds the socket, and rebroadcasts every frame to the others. If the leader closes, whoever notices first takes over.

The trade is that switching which window is leader costs a reconnect, which is a blink. It is done this way rather than with a shared worker because shared workers are unavailable in several browsers people actually use, and the election is twenty lines of code.

Gap replay

On reconnect — a laptop waking from sleep, a dropped wifi — a client sends the last sequence number it saw for each room and receives what it missed. Past 500 events it is told to resync and refetch from the API instead of being sent every frame, which is both cheaper and leaves it in a known state either way.

This is why a dispatcher who closes the lid on a laptop and opens it twenty minutes later sees a correct board rather than a stale one or a blank one.

Data, retention & export

Your data is yours

Export in the owner panel takes everything out: people, vehicles, calls, reports, citations, cases and the rest. It runs as a background job and you are told when it is ready, because a community with four years of history is not a request that finishes in a browser timeout.

Importing

Import brings in what you already have — a spreadsheet of people from your old system, a penal code, a vehicle list. This is the right first move on a community that has been running for years without a CAD, because an empty records system is a records system nobody uses.

The penal code specifically can be imported and exported on its own, which is the easy way to move one between a test community and a live one.

What is kept, and for how long

DataDefaultNotes
Reports, citations, arrests, cases, warrants, people, vehiclesForeverThese are the records. Nothing trims them.
Closed calls30 daysAdjustable 1–365. Trimming a call never trims the paperwork written on it.
Messages30 daysAdjustable 1–365.
Unit positions7 daysAdjustable 0–90. Set 0 to keep none.
Audit log365 daysAdjustable, capped by your plan: 14 days on trial up to 3 years on Network.
AttachmentsForeverAgainst your plan's storage allowance.

The audit log

Every write is recorded with who did it, what it was, when, and where it came from — the web, the in-game terminal, a street command, the API, the system itself, or platform staff.

That last field is more useful than it sounds. "Who deleted this call" is a different question from "was it deleted from a patrol car or from a browser", and the second one is usually the one that explains what happened.

Filter it by person, by action, by date. Command roles see their own agency's; owners see everything.

If you stop subscribing

A community moves to read-only rather than being deleted. Your dispatchers can still open it, every record is still there and still exportable, and nothing can be written. Export before you go, and ask us if you need help getting it out.

Security & isolation

This section exists because you are trusting somebody else's infrastructure with your community's records, and you are entitled to know how that is held up.

The one guarantee

A bug must not leak data between communities. Everything else in this platform is a feature; that is the product. Three mechanisms hold it up, and all three are load-bearing.

1. The database refuses

Every table holding community data carries a community id, and row-level security policies are applied to all of them by a loop rather than a list — so a new table cannot be forgotten by somebody writing a migration in a hurry. The application connects as a role that is explicitly not allowed to bypass those policies, and every query runs inside a transaction with the community id set.

There is no second way to reach the database. A route gets one handle, and that handle is already scoped.

2. Foreign keys carry the community

A policy stops one community reading another's rows. It does not stop one writing a row of its own that points at one, because an ordinary foreign key only checks that the id exists — not whose it is.

So every cross-table reference in the schema — all 197 of them — is a composite that includes the community id. A row that pointed somewhere it should not simply cannot be written.

3. The platform refuses to start if either is wrong

On startup the API checks that its database role cannot bypass the policies, that no rows are visible with no community set, and that every foreign key is community-scoped. If any of those is false it refuses to serve.

A failed deploy is a much better outcome than a silent leak, and this is the check that makes that the default.

Tested, including the negative cases

All three are covered by tests that run against a real PostgreSQL rather than a mock — because the two things most worth testing here are database features, and a mock of either would pass while the real thing was broken. Every endpoint has a cross-community test: a valid session for one community, pointed at another community's row ids, must get a refusal and nothing else.

Authentication

  • Sessions are rows, with rotating refresh tokens and short-lived access tokens. Signing out one browser does not sign out the others.
  • Two-factor by authenticator app, available to everyone, required for owners.
  • The sign-in page says nothing about whether an email address exists, which is how an attacker would otherwise enumerate your members.
  • In-game terminal tokens are separate and expire in hours.

The bridge connection

  • Outbound only; your server opens no ports.
  • An API key identifies the server; an HMAC signature proves the body was not altered; a timestamp and a nonce stop a captured request being replayed. None of the three is sufficient alone, and the signature covers all three together.
  • A bridge key cannot read the public API and an API key cannot send game events.
  • Rotating a key closes the connection immediately, which is the behavior you want.

What platform staff can see

Said plainly, because you should know:

  • Support staff do not become members of your community. There is no staff account sitting in your roster.
  • When staff need to see what you see, they impersonate, which mints a real session with real roles and writes both names on every row it touches. There is no invisible mode.
  • Impersonation sessions appear in your own audit log, marked as such, with the staff member named.
  • Platform staff hold no shortcut around the permission engine — they act through a real subject with real roles, precisely so that the audit trail shows what was actually used.

Your own responsibilities

Three, and they are the ones that actually get communities into trouble:

  • Treat the API key and signing secret like a database password. Never in a public repository, never in Discord. Rotate immediately if either leaks.
  • Turn on two-factor for owners and for anybody who can read criminal history or approve reports.
  • Be deliberate about @tenant grants. Most mistakes in a role are a scope that is one step too wide.

Troubleshooting

The bridge will not start

"API key is still the placeholder" — paste the real values from Game servers into config.lua. The bridge stops deliberately rather than starting half-working.

For anything else, set Config.Debug.Enabled = true and restart. It reports the framework it detected, whether it connected, and every rejected message with the reason.

"invalid signature" in the console

Your signing secret does not match the one on the platform. Regenerate the pair under Settings → Game servers and paste both values again — the key and the secret are issued together and do not mix across generations.

The bridge connects but no units appear

Almost always this: players have to link their game account once with /cadlink, using the code from their CAD profile. Until they do, the platform does not know who they are. Have one person do it and watch them appear on the board to confirm.

Commands say somebody is not allowed

Permissions are checked by the platform, never by the resource. Check the member's role in the CAD, and check which agency they are working as — @agency grants do not reach another agency's rows. Config.EmergencyJobs only tidies the command list for civilians and is not a permission control.

Lookups return nothing

Expected on a fresh community. The records are in the CAD, not in the game, and the CAD has not met anybody yet. Import what you have from Settings → Import, or let records accumulate.

Characters appear but their vehicles do not

Owned vehicles are read from your framework's own table through oxmysql, mysql-async or ghmattimysql. If none of those is running, or your fork uses different table or column names, set them in Config.Sync.Vehicles.

After three failed reads in a row the bridge stops trying and prints one warning saying so — fix the config and restart the resource.

A player's name is wrong in the CAD

On a server that syncs from the framework, names, dates of birth and gender come from the game. Change them in the character creator or your framework's tools and they update on the next load. Everything else about a character is edited in the portal.

A license suspension did not reach the game

Check Config.Sync.ApplyLicenses is true. If the player was offline, it lands the next time they load in — the platform re-sends any standing suspension whenever the game copy disagrees, so it is not lost, only waiting.

Tone-outs go nowhere

The station's slug must match the station id in Sal's Kewl Station Alert. The Stations panel warns when a station has no slug. Also check the station's own alerting switch is on — a station with it off refuses a tone-out rather than swallowing it, and will tell you.

Signs are empty in the CAD

The sign resource reports its inventory up; the CAD does not discover signs on its own. Check the sign resource is running and that its CAD integration is configured on its side.

The station board in game is empty

Check Config.Board.Enabled. On 'auto' the board is sent only while something on the server wants it — if your display resource is not in Config.Board.AutoFor and does not call WantBoard(), nothing arrives. Add it to the list or set Enabled = true.

The run card sent fewer units than it asked for

That is the shortfall report, and it is working. Nothing else of that kind is on duty and free. Mutual aid or a move-up is the next call — the platform will not invent apparatus.

A dispatcher's layout is wrong after an update

Reset layout on the toolbar. A layout saved by an older build that cannot be restored falls back to the default rather than leaving an empty workspace, but a layout that restores badly is best reset.

Nothing happens at all

Set Config.Debug.Enabled = true, restart, and read the console. It reports the framework it detected, whether it connected, and every rejected message with the reason. If the connection is the problem, check whether your host blocks long-lived sockets and confirm Config.AllowHttpFallback is true.

Still stuck

Have these ready and support can usually answer in one message: your community slug, the bridge version from the console banner, whether debug is on, and the exact line from the console.

Appendix: status codes & call types

Plain words — what a new community gets

CodeLabelStateTimerQuick
OFFOff dutyoff_duty——
AVLAvailableavailable—yes
ENREn routeen_route10 minyes
ONSOn sceneon_scene30 minyes
BSYBusybusy20 minyes
TRNTransportingtransporting20 min—
HOSAt hospitalat_hospital45 min—
OOSOut of serviceout_of_service——
PANEMERGENCYpanic——

Ten codes — the alternative set

CodeLabelStateTimer
10-8In serviceavailable—
10-76En routeen_route10 min
10-97On sceneon_scene30 min
10-6Busybusy20 min
10-15Transportingtransporting20 min
10-23At hospitalat_hospital45 min
10-7Out of serviceout_of_service—
10-42Off dutyoff_duty—
10-33EMERGENCYpanic—

Fire and EMS add two more on top of whichever set you chose: STG staging and AVL-S available on scene.

Priorities

PriorityMeans
P1Life at risk. Everything drops for this.
P2Urgent. Crime in progress or serious injury.
P3Prompt. Respond when a unit clears.
P4Routine.
P5When convenient. Paperwork and follow-ups.

Law enforcement call types

CodeLabelPAlso goes to
TSTraffic stop4—
TCTraffic collision3EMS, fire
DUIImpaired driver3—
PURSPursuit13 patrol, 1 air
SHOTSShots fired13 patrol
ROBRobbery in progress13 patrol
BURGBurglary2—
ALARMAlarm3—
DISTDisturbance3—
DVDomestic disturbance2EMS
ASSLTAssault2EMS
GTAVehicle theft3—
SUSPSuspicious person or vehicle4—
WELFWelfare check4EMS
WARRWarrant service3—
BACKUPOfficer requesting backup12 patrol
PANICOFFICER EMERGENCY14 patrol, 1 supervisor, 1 medic; fire and EMS too
ASSISTAssist other agency3—
FOLLOWUPInvestigation follow-up5Sensitive — off the shared board

Fire call types

CodeLabelPSendsTones out
STRUCTStructure fire12 engine, 1 ladder, 1 rescue, 1 battalion, 1 medicyes
VEHFIREVehicle fire21 engineyes
BRUSHFIREBrush fire22 brush, 1 engine, 1 tankeryes
ALARMFIREFire alarm activation31 engineyes
EXTRICExtrication11 rescue, 1 engine, 1 medicyes
HAZMATHazardous materials12 engine, 1 battalionyes
PSAPublic service assist5——
INSPInspection5——

EMS call types

CodeLabelPSends
MEDAMedical — life threatening11 medic, 1 engine
MEDBMedical — urgent21 medic
MEDCMedical — routine41 medic
CARDIACCardiac arrest12 medic, 1 engine
GSWGunshot wound11 medic, 2 patrol
TRANSFERInterfacility transfer5—

Service call types

CodeLabelPGoes to
TOWTow requested4Tow
IMPOUNDImpound4Tow
CLOSURERoad closure4DOT
DEBRISDebris in roadway4DOT, state police
ANIMALAnimal call5Game wardens, police
MARINEMarine assist2Marine, EMS
OTHEROther4Anyone

Your community gets the call types matching the agencies you turned on, so a server with no fire department is not looking at a list of structure fire codes. All of them are editable, and you can add your own.

Appendix: fire & EMS coding

Only relevant with Detailed fire and EMS coding on.

What it adds

  • NFIRS-style coding sections on the fire incident report: incident type, property use, area of origin, heat source and cause of ignition.
  • The fuller vitals layout on patient care reports — repeating vitals rows with times rather than a single set.

The code picker

Type a number or a word. 111, cooking, alarm — all three work, because a firefighter who has coded a thousand building fires types 111 and one who has not types what they saw.

Why some codes carry a hint

The distinctions NFIRS makes are genuinely easy to get wrong, and the codes most often confused carry a one-line note in the picker:

These twoDiffer by
111 building fire
113 cooking fire confined to the container
Whether it stayed in the pot. A contained cooking fire is not a building fire, and coding it as one makes your fire loss statistics meaningless.
745 alarm system activation, no fire
733 smoke detector malfunction
Whether the detector was working correctly and had something to detect.

Coding a run wrong is worse than not coding it, because a wrong code is indistinguishable from a right one once it is in the statistics. If a firefighter is unsure, the honest answer is to leave it for somebody who knows.

The three fields that are not the same thing

Repeating the distinction from Run cards & response plans, because this is where it bites:

  • Call type — what was reported. "Structure fire."
  • Response — what rolled. "Residential box, first alarm."
  • Incident type — what it turned out to be. "113 — cooking fire, confined."

The gap between the first and the third is one of the more interesting numbers on the fire statistics page, and it only exists because they are three separate fields.

What the statistics are measured against

NFPA 1710, at the 90th percentile: 80 seconds turnout for fire suppression, 60 for EMS, four minutes travel for the first arriving engine. See Statistics for why the 90th percentile and not the average.

Support

Support, release news and coupon codes are on Discord, and by email at support@salskewlscripts.com.

Before you write in

Four things answer most questions in one message:

  • your community slug — the first part of your CAD address;
  • the bridge version from the console banner;
  • whether Config.Debug.Enabled was on;
  • the exact line from the console, rather than a description of it.

The rest of the catalog

Sal's Kewl CAD is designed to work with the other Sal's Kewl resources, and each has its own manual at salskewlkorner.com/documentation:

ResourceWhat it adds to the CAD
Sal's Kewl DispatchIn-game radio, a fire/EMS pager, and 9-1-1 call-taking that dumps worked calls straight into the CAD.
Sal's Kewl Station AlertThe firehouse: lights, tones, and a live CAD call board on the wall.
Sal's Kewl SignageHighway boards that show the CAD's amber alerts, BOLOs and closures.
Sal's Kewl Toll SystemToll gantries that report passes and violations to the CAD.

Each is sold separately and none is required — the CAD works on its own, and every integration is detected at runtime rather than depended on.

Keeping up to date

The bridge ships with its own README and CHANGELOG, and the CHANGELOG calls out breaking configuration changes explicitly. This manual is restamped with every release; the version it documents is on the front and in the pill at the top of the web copy.

The live copy, always current, is at salskewlkorner.com/documentation/sals-kewl-cad.html.