API Reference
Lua exports, REST API, and Socket.IO reference for Tommy's Radio.
Server Exports
Channel Information
getSpeakersInChannel(frequency)
Get all users connected to transmit on a channel.
- Parameters:
frequency(string/number) - Returns:
table— array of server IDs
local speakers = exports['tRadio']:getSpeakersInChannel("154.755")getListenersInChannel(frequency)
Get all users listening (scanning) a channel without transmit capability.
- Parameters:
frequency(string/number) - Returns:
table— array of server IDs
local listeners = exports['tRadio']:getListenersInChannel("154.755")getAllUsersInChannel(frequency)
Get all users on a channel (speakers + listeners).
- Parameters:
frequency(string/number) - Returns:
table— array of server IDs
local allUsers = exports['tRadio']:getAllUsersInChannel("154.755")getActiveTalkersInChannel(frequency)
Get users actively transmitting right now (PTT pressed).
- Parameters:
frequency(string/number) - Returns:
table— array of server IDs
local talkers = exports['tRadio']:getActiveTalkersInChannel("154.755")getActiveChannels()
Get all channels with at least one active user.
- Returns:
table— array of frequency strings
local channels = exports['tRadio']:getActiveChannels()getChannelInfo(frequency)
Get detailed information about a channel.
- Parameters:
frequency(string/number) - Returns:
table—{ speakers, listeners, activeTalkers }arrays, ornil
local info = exports['tRadio']:getChannelInfo("154.755")
if info then
print("Speakers: " .. #info.speakers)
print("Listeners: " .. #info.listeners)
endgetAllChannels()
Get the complete channel list from config with zones, names, types, etc.
- Returns:
table— array of all configured channels
local channels = exports['tRadio']:getAllChannels()
for _, ch in ipairs(channels) do
print(ch.name .. " - " .. ch.frequency)
endAlerts & Panic
getChannelAlert(frequency)
Get the active alert on a channel.
- Parameters:
frequency(string/number) - Returns:
table— alert config object (see Alert Field Reference), ornil
local alert = exports['tRadio']:getChannelAlert("154.755")
if alert then print("Alert: " .. alert.name) endsetAlertOnChannel(frequency, enabled, alertIndexOrName)
Activate or clear an alert on a channel.
-
Parameters:
frequency(string/number)enabled(boolean|nil) —trueto activate,falseto deactivate,nilto togglealertIndexOrName(number|string|nil) — index intoConfig.alerts, alert name (case-insensitive), ornilfor the first alert
-
Returns:
boolean
-- Activate first alert
exports['tRadio']:setAlertOnChannel("154.755", true)
-- Activate by name
exports['tRadio']:setAlertOnChannel("154.755", true, "SIGNAL 100")
-- Toggle
exports['tRadio']:setAlertOnChannel("154.755", nil, "SIGNAL 100")
-- Deactivate
exports['tRadio']:setAlertOnChannel("154.755", false)setChannelSignal(frequency, enabled)
Legacy alias for setAlertOnChannel. Prefer setAlertOnChannel for new code.
- Parameters:
frequency(string/number),enabled(boolean) - Returns:
boolean
getChannelPanic(frequency)
Get all users with panic active on a channel.
- Parameters:
frequency(string/number) - Returns:
table—{ [serverId] = true, ... }
local panics = exports['tRadio']:getChannelPanic("154.755")
for id, _ in pairs(panics) do print("Panic: " .. id) endsetChannelPanic(frequency, serverId, enabled)
Set or clear panic for a user on a channel.
- Parameters:
frequency(string/number),serverId(number),enabled(boolean) - Returns:
boolean
exports['tRadio']:setChannelPanic("154.755", 1, true)getUserPanicState(serverId, frequency)
Check if a user has panic active.
- Parameters:
serverId(number),frequency(string/number) - Returns:
boolean
User Management
setUserChannel(serverId, frequency)
Move a user to a channel (handles trunked channels automatically).
- Parameters:
serverId(number),frequency(string/number) - Returns:
boolean
exports['tRadio']:setUserChannel(1, "155.475")disconnectUser(serverId)
Disconnect a user from the radio system.
- Parameters:
serverId(number) - Returns:
boolean
isUserTalking(serverId, frequency)
Check if a user is actively transmitting.
- Parameters:
serverId(number),frequency(string/number) - Returns:
boolean
User Information
getPlayerName(serverId)
Get a player's display name.
- Parameters:
serverId(number) - Returns:
string
getUserNacId(serverId)
Get a player's effective NAC ID. Checks Config.getUserNacId first, then falls back to tRadio.nac.* ACE permissions.
- Parameters:
serverId(number) - Returns:
stringornil
getUserInfo(serverId)
Get both name and effective NAC ID.
- Parameters:
serverId(number) - Returns:
table—{ name, nacId }
getAcePermissions(serverId)
Get a player's resolved ACE permissions. Returns the per-frequency and per-zone permissions granted via tRadio.* ACE entries. See ACE Permissions for setup.
- Parameters:
serverId(number) - Returns:
table—{ connect, scan, gps, zones, dispatch }
local perms = exports['tRadio']:getAcePermissions(1)
-- perms.connect = { 154.755, 154.815 } -- frequencies player can transmit on
-- perms.scan = { 856.1125 } -- frequencies player can scan
-- perms.gps = { 154.755 } -- frequencies player can see GPS for
-- perms.zones = { 1, 2 } -- zone indices (1-based) player can access
-- perms.dispatch = true -- tlib.admin grants SGN/CT/TK radio controlshasRadioAccess(serverId)
Check if a user has radio access (via radioAccessCheck).
- Parameters:
serverId(number) - Returns:
boolean
refreshNacId(serverId)
Refresh a single player's NAC ID cache. Call after job changes.
- Parameters:
serverId(number) - Returns:
boolean
RegisterNetEvent('QBCore:Server:OnJobUpdate', function(source)
exports['tRadio']:refreshNacId(source)
end)refreshPlayerInfo()
Refresh NAC ID and display name for all online players.
- Returns:
number— count of players refreshed
Callsigns
Requires useCallsignSystem = true in admin panel settings.
setCallsign(serverId, callsign)
Set a player's callsign. Pass empty string or nil to clear.
- Parameters:
serverId(number),callsign(string|nil) - Returns:
boolean
exports['tRadio']:setCallsign(1, "2L-319")
exports['tRadio']:setCallsign(1, "") -- cleargetCallsign(serverId)
Get a player's custom callsign.
- Parameters:
serverId(number) - Returns:
stringornil
Audio Control
playToneOnChannel(frequency, tone)
Play a tone to all users on a channel.
- Parameters:
frequency(string/number),tone(string) — tone name fromtones.json
exports['tRadio']:playToneOnChannel("154.755", "BEEP")playToneOnSource(serverId, tone)
Play a tone to a specific user only.
- Parameters:
serverId(number),tone(string)
exports['tRadio']:playToneOnSource(1, "BEEP")Client Exports
Radio Control
openRadio(shouldFocus)
Open the radio UI.
- Parameters:
shouldFocus(boolean, optional) — defaulttrue.falseopens without NUI focus.
exports['tRadio']:openRadio() -- open and focus
exports['tRadio']:openRadio(false) -- open without focuscloseRadio()
Close the radio UI.
setCurrentChannel(channel)
Set your main speaking channel.
- Parameters:
channel(string/number)
getCurrentFrequency()
Get your current frequency.
- Returns:
string/number— frequency, or-1if not connected
getCurrentChannel()
Get full information about your current channel.
- Returns:
table— channel object
Listening Channels
addListeningChannel(channel)
Add a channel to your scan list.
- Parameters:
channel(string/number)
removeListeningChannel(channel)
Remove a channel from your scan list.
- Parameters:
channel(string/number)
getListeningChannels()
Get all channels you're scanning.
- Returns:
table— array of frequencies
Transmission
startTransmitting()
Start PTT transmission programmatically.
stopTransmitting()
Stop PTT transmission.
isTransmitting()
Check if you're currently transmitting.
- Returns:
boolean
getActiveTalker()
Get who's currently talking on your frequency.
- Returns:
table—{ serverId, name, frequency }ornil
setTalking(talking)
Set talking state for UI feedback.
- Parameters:
talking(boolean)
Radio State
isConnected()
- Returns:
boolean— whether connected to a channel
isPowerOn()
- Returns:
boolean
setPower(state)
Set radio power. Omit to toggle.
- Parameters:
state(boolean, optional)
exports['tRadio']:setPower(true) -- on
exports['tRadio']:setPower(false) -- off
exports['tRadio']:setPower() -- toggleisRadioOpen()
- Returns:
boolean— whether the radio UI is displayed
isRadioFocused()
- Returns:
boolean— whether the radio UI has NUI focus
Audio & Volume
setVolume(volume) / getVolume()
Voice volume (0.0-1.0).
setToneVolume(volume) / getToneVolume()
SFX/tone volume (0.0-1.0).
set3DVolume(volume) / get3DVolume()
3D audio volume (0.0-1.0).
playTone(tone)
Play a tone locally.
- Parameters:
tone(string) — tone name fromtones.json
Appearance
setRadioLayout(layout) / getRadioLayout()
Radio model name (e.g., "ATX-8000").
setRadioTheme(theme) / getRadioTheme()
Theme name (e.g., "Dark", "Auto").
setAnimationId(animId) / getAnimationId()
Animation index.
Settings
setEarbudsEnabled(enabled) / getEarbudsEnabled()
Earbuds mode (disables 3D audio).
setGPSEnabled(enabled) / getGPSEnabled()
GPS tracking.
Alerts & Panic (Client)
triggerAlertOnChannel(frequency, enabled)
Trigger or clear an alert (sends request to server).
- Parameters:
frequency(string/number),enabled(boolean)
panicButton(frequency, enabled)
Activate or clear panic.
- Parameters:
frequency(string/number),enabled(boolean)
User Information (Client)
getCurrentName()
- Returns:
string— your display name
getCurrentNacId()
- Returns:
string— your NAC ID
refreshMyNacId()
Force refresh your NAC ID from the server.
RegisterNetEvent('QBCore:Client:OnJobUpdate', function()
exports['tRadio']:refreshMyNacId()
end)getMyCallsign() / setMyCallsign(callsign)
Get/set your callsign (requires callsign system enabled). Pass empty string to clear.
getZone()
- Returns:
string— current zone name
System
getBatteryLevel()
- Returns:
number— battery percentage (0-100)
getSignalStrength()
- Returns:
number— signal strength value
getConnectionDiagnostics()
- Returns:
table— diagnostic information for troubleshooting
Notes
- Frequencies accept strings (
"154.755") or numbers (154.755) - Volume values use 0.0-1.0 range (not 0-100)
- Server exports return
nilorfalseon failure - Client exports return
-1ornilwhen disconnected
Web API
Tommy's Radio includes a REST API and Socket.IO server for external integrations — dispatch panels, bots, and automation systems.
Authentication
Step 1: Get a Session
POST /radio/dispatch/auth
{
"nacId": "141",
"callsign": "Dispatcher-01"
}Response:
{
"success": true,
"sessionId": "dispatch_1234567890_abc123",
"authToken": "changeme"
}nacIdmust matchdispatchNacIdinconfig.lua- Sessions expire after 24 hours
Step 2: Authenticate Requests
All protected endpoints require these headers:
Authorization: Bearer YOUR_AUTH_TOKEN
x-session-id: YOUR_SESSION_IDREST API vs Socket.IO
| REST API | Socket.IO | |
|---|---|---|
| Use case | Triggering actions (broadcasts, alerts, tones) | Full dispatcher presence and real-time monitoring |
| Visible in panel | No | Yes |
| Real-time updates | No | Yes (voice, panic, user updates) |
| Voice communication | No | Yes |
Most integrations only need the REST API.
Public Endpoints
Health Check
GET /api/health
{
"status": "ok",
"service": "tRadio",
"timestamp": "2024-01-01T00:00:00.000Z",
"version": "5.0"
}Protected Endpoints
All endpoints below require authentication headers.
Get System Status
GET /api/status
{
"userCount": 5,
"channelCount": 3,
"channels": {
"460.125": {
"frequency": 460.125,
"speakers": [1, 2, 3],
"listeners": [4, 5],
"activeTalkers": [2]
}
},
"users": {
"1": {
"name": "John Doe",
"nacId": "PD001",
"currentChannel": 460.125,
"listening": [460.125, 460.150],
"isTalking": false
}
},
"panicStatus": {},
"activeAlerts": {},
"version": "5.0"
}Get Dispatch Status
GET /radio/dispatch/status
Returns channels, users, panic status, active alerts, and server timestamp.
Get Configuration
GET /radio/dispatch/config
Returns server config including zones, alerts, and audio settings.
Get Available Tones
GET /radio/dispatch/tones
{
"tones": [
{ "name": "BEEP", "file": "beep.wav" },
{ "name": "PANIC", "file": "panic.wav" }
]
}Trigger Broadcast
POST /api/trigger-broadcast
{
"message": "All units respond to 123 Main St",
"frequency": 460.125,
"type": "Emergency"
}message(required): broadcast textfrequency(optional): target frequency, omit for all userstype(optional): alert type label
Dispatch Broadcast
POST /radio/dispatch/broadcast
{
"message": "All units, be advised...",
"frequency": 460.125,
"type": "General",
"tone": "alert_a"
}tone(optional): tone to play with the broadcast
Play Tone on Channel
POST /api/play-tone
{
"frequency": 460.125,
"tone": "BEEP"
}Dispatch Tone
POST /radio/dispatch/tone
{
"frequency": 460.125,
"tone": "BEEP"
}Trigger Alert
POST /radio/dispatch/alert/trigger
{
"frequency": 460.125,
"alertType": "SIGNAL 100",
"alertConfig": {
"name": "SIGNAL 100",
"color": "#d19d00",
"isPersistent": true,
"tones": {
"activate": "ALERT_A",
"repeat": "BEEP",
"deactivate": "ALERT_B"
},
"repeatInterval": 15000,
"repeatShowBanner": true,
"deactivateLabel": "RESUME",
"deactivateColor": "#126300"
}
}The alertConfig object must be the full entry from Config.alerts.
Clear Alert
POST /radio/dispatch/alert/clear
{ "frequency": 460.125 }One-Shot Alert
POST /radio/dispatch/alert/oneshot
Non-persistent alert that plays once and disappears.
{
"frequency": 460.125,
"alertConfig": {
"name": "Ping",
"color": "#0049d1",
"isPersistent": false,
"tones": { "activate": "ALERT_B" }
}
}Tone-only variant (no visual banner):
{
"frequency": 460.125,
"alertConfig": {
"name": "Boop",
"toneOnly": true,
"tone": "BONK"
}
}Switch User Channel
POST /radio/dispatch/switchChannel
{
"serverId": 1,
"frequency": 460.150,
"oldFrequency": 460.125
}Send Alert to User
POST /dispatch/user/alert
{
"userId": 1,
"message": "Respond to 123 Main St",
"frequency": 460.125
}Disconnect User
POST /dispatch/user/disconnect
{ "userId": 1 }Set Player Callsign
POST /dispatch/user/set-player-callsign
Requires useCallsignSystem = true.
{
"userId": 1,
"callsign": "2L-319"
}Omit callsign or send empty string to clear.
Update Dispatch Callsign
POST /dispatch/user/update-callsign
{
"callsign": "Dispatcher-02",
"userId": -5000
}Dispatch user IDs are negative. If userId is omitted, updates the first dispatch user found.
Socket.IO
Connect via Socket.IO to appear as a dispatcher in the panel and receive real-time updates.
Connection
const io = require('socket.io-client');
const dispatchUserId = -(1000 + Math.floor(Math.random() * 10000));
const socket = io('http://YOUR_IP:7777', {
auth: {
authToken: 'changeme',
serverId: dispatchUserId // Must be negative
}
});Emit Events
| Event | Payload | Description |
|---|---|---|
setDispatchSession | sessionId (string) | Link socket to auth session |
updateUserInfo | { name, nacId } | Set dispatcher identity |
setSpeakerChannel | frequency (number) | Join a channel to speak |
addListeningChannel | frequency (number) | Start scanning a channel |
removeListeningChannel | frequency (number) | Stop scanning a channel |
setTalking | true/false | PTT state |
heartbeat | Date.now() | Keep-alive |
Listen Events
| Event | Payload | Description |
|---|---|---|
connect | — | Connected to server |
heartbeat_response | serverTime | Server timestamp |
voice | { serverId, channel, voiceData, isTalking } | Voice data |
panicAlert | { frequency, serverId, playerName } | Panic activation |
serverTone | { tone, frequency } | Tone played on channel |
transmissionEffect | { serverId, effectType, distance } | Background SFX |
Web API Examples
cURL
# Authenticate
curl -X POST http://192.0.2.100:7777/radio/dispatch/auth \
-H "Content-Type: application/json" \
-d '{"nacId": "141", "callsign": "API-Test"}'
# Get status
curl http://192.0.2.100:7777/api/status \
-H "Authorization: Bearer changeme" \
-H "x-session-id: dispatch_123_abc"
# Trigger broadcast
curl -X POST http://192.0.2.100:7777/api/trigger-broadcast \
-H "Authorization: Bearer changeme" \
-H "x-session-id: dispatch_123_abc" \
-H "Content-Type: application/json" \
-d '{"message": "Test alert", "frequency": 460.125}'Node.js
const axios = require('axios');
const io = require('socket.io-client');
const API_URL = 'http://192.0.2.100:7777';
const AUTH_TOKEN = 'changeme';
async function main() {
// Authenticate
const { data } = await axios.post(`${API_URL}/radio/dispatch/auth`, {
nacId: '141', callsign: 'API-Bot'
});
const headers = {
'Authorization': `Bearer ${data.authToken}`,
'x-session-id': data.sessionId,
'Content-Type': 'application/json'
};
// Connect Socket.IO
const userId = -(1000 + Math.floor(Math.random() * 10000));
const socket = io(API_URL, {
auth: { authToken: AUTH_TOKEN, serverId: userId }
});
socket.on('connect', () => {
socket.emit('setDispatchSession', data.sessionId);
socket.emit('updateUserInfo', { name: 'API-Bot', nacId: 'API' });
});
// Get status
const status = await axios.get(`${API_URL}/api/status`, { headers });
console.log('Users online:', status.data.userCount);
// Broadcast
await axios.post(`${API_URL}/api/trigger-broadcast`, {
message: 'Test broadcast', frequency: 460.125
}, { headers });
}
main();Python
import requests
import socketio
import random
API_URL = 'http://192.0.2.100:7777'
# Authenticate
auth = requests.post(f'{API_URL}/radio/dispatch/auth', json={
'nacId': '141', 'callsign': 'API-Bot'
}).json()
headers = {
'Authorization': f'Bearer {auth["authToken"]}',
'x-session-id': auth['sessionId'],
}
# Connect Socket.IO
sio = socketio.Client()
user_id = -(1000 + random.randint(0, 10000))
@sio.on('connect')
def on_connect():
sio.emit('setDispatchSession', auth['sessionId'])
sio.emit('updateUserInfo', {'name': 'API-Bot', 'nacId': 'API'})
sio.connect(API_URL, auth={'authToken': auth['authToken'], 'serverId': user_id})
# Get status
status = requests.get(f'{API_URL}/api/status', headers=headers)
print(f"Users online: {status.json()['userCount']}")Web API Error Responses
| Status | Body | Cause |
|---|---|---|
| 400 | { "error": "..." } | Missing or invalid parameters |
| 401 | { "error": "Authentication required" } | Missing or invalid auth headers |
| 404 | { "error": "Channel not found" } | Invalid frequency |
| 500 | { "error": "..." } | Server error |
Web API Notes
- Replace
192.0.2.100:7777with your actual server IP and port - Replace
changemeand141with yourauthTokenanddispatchNacId - Frequencies are decimal numbers (e.g.,
460.125) - Dispatcher IDs must be negative; player IDs are positive
- Sessions expire after 24 hours
- CORS is enabled for all origins
