API Reference

Lua exports, REST API, and Socket.IO reference for Tommy's Radio.

Lua ExportsREST APISocket.IO

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, or nil
local info = exports['tRadio']:getChannelInfo("154.755")
if info then
    print("Speakers: " .. #info.speakers)
    print("Listeners: " .. #info.listeners)
end

getAllChannels()

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)
end

Alerts & Panic

getChannelAlert(frequency)

Get the active alert on a channel.

  • Parameters: frequency (string/number)
  • Returns: table — alert config object (see Alert Field Reference), or nil
local alert = exports['tRadio']:getChannelAlert("154.755")
if alert then print("Alert: " .. alert.name) end

setAlertOnChannel(frequency, enabled, alertIndexOrName)

Activate or clear an alert on a channel.

  • Parameters:

    • frequency (string/number)
    • enabled (boolean|nil) — true to activate, false to deactivate, nil to toggle
    • alertIndexOrName (number|string|nil) — index into Config.alerts, alert name (case-insensitive), or nil for 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) end

setChannelPanic(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: string or nil

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 controls

hasRadioAccess(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, "")  -- clear

getCallsign(serverId)

Get a player's custom callsign.

  • Parameters: serverId (number)
  • Returns: string or nil

Audio Control

playToneOnChannel(frequency, tone)

Play a tone to all users on a channel.

  • Parameters: frequency (string/number), tone (string) — tone name from tones.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) — default true. false opens without NUI focus.
exports['tRadio']:openRadio()       -- open and focus
exports['tRadio']:openRadio(false)  -- open without focus

closeRadio()

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 -1 if 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 } or nil

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()       -- toggle

isRadioOpen()

  • 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 from tones.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 nil or false on failure
  • Client exports return -1 or nil when 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"
}
  • nacId must match dispatchNacId in config.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_ID

REST API vs Socket.IO

REST APISocket.IO
Use caseTriggering actions (broadcasts, alerts, tones)Full dispatcher presence and real-time monitoring
Visible in panelNoYes
Real-time updatesNoYes (voice, panic, user updates)
Voice communicationNoYes

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 text
  • frequency (optional): target frequency, omit for all users
  • type (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

EventPayloadDescription
setDispatchSessionsessionId (string)Link socket to auth session
updateUserInfo{ name, nacId }Set dispatcher identity
setSpeakerChannelfrequency (number)Join a channel to speak
addListeningChannelfrequency (number)Start scanning a channel
removeListeningChannelfrequency (number)Stop scanning a channel
setTalkingtrue/falsePTT state
heartbeatDate.now()Keep-alive

Listen Events

EventPayloadDescription
connectConnected to server
heartbeat_responseserverTimeServer 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

StatusBodyCause
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:7777 with your actual server IP and port
  • Replace changeme and 141 with your authToken and dispatchNacId
  • 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

On this page

Need help?

Ask on Discord