# Client exports

All exports on this page are called from **client** scripts:

```lua title="client.lua"
local dispatch = exports['koja-dispatcher']
```

| Export | Returns | Yields |
|---|---|---|
| `IsOnShift()` | `boolean` | no |
| `IsCadOpen()` | `boolean` | no |
| `SendAlert(data)` | `id, route` or `false, error` | yes |
| `Call911(message)` | `true, route` or `false, error` | yes |

`SendAlert` and `Call911` send a request to the server and wait up to 10 seconds for the answer. Call them from a thread, a command or an event handler, never from the main chunk of a file.

:::hint{type="info" title="Prefer the server"}
When your script already has a server side, raise alerts there with [`CreateCall`](/new/dispatcher/api/server-exports) or [`CreateIncident`](/new/dispatcher/api/server-exports). Server exports are not rate limited, can set the category and do not depend on `api.clientAlerts`.
:::

---

## `IsOnShift()`

**Returns** `true` when the local player is on shift as a dispatcher (at a station or with the remote CAD).

```lua title="client.lua"
if exports['koja-dispatcher']:IsOnShift() then
    return -- do not open my phone while the player is dispatching
end
```

## `IsCadOpen()`

**Returns** `true` when the local player is on shift **and** the CAD has focus (mouse and keyboard captured by the dispatcher UI).

```lua title="client.lua"
CreateThread(function()
    while true do
        Wait(500)
        local busy = exports['koja-dispatcher']:IsCadOpen()
        exports['my-hud']:SetVisible(not busy)
    end
end)
```

---

## `SendAlert(data)`

Raises a 911 alert from a client script, for example a robbery or a phone app that only runs on the client. It goes through the [call router](/new/dispatcher/api/overview) like `/911`.

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | no | Text of the call. The server removes control characters and keeps 300 characters. |
| `caller` | string | no | Caller name (max 64). Default `Anonymous`. |
| `coords` | vector3 / table | no | Position of the alert. Defaults to the player's ped. Clamped, see below. |
| `label` | string | no | Place label (max 96). Default: the street name at `coords`, read on the client. |
| `need` | table | no | Units needed, `{ police = 1 }`. |
| `priority` | number | no | `1` to `3`. |

**Returns** `id, route` (`route` is `'call'`, `'incident'` or `'mdt'`), or `false, error`.

**Errors**

| Code | Meaning |
|---|---|
| `disabled` | `api.clientAlerts` is off (the default). |
| `invalid` | `data` is not a table, or the player has no ped. |
| `no_dispatchers` | Nobody on shift and `mode.noDispatcher` is `reject` (or `mdt` without koja-mdt). |
| `cancelled` | A `callCreate` or `incidentCreate` hook blocked it. A `callCreate` hook's own reason is returned instead when it gave one. |
| `no_response` | The server did not answer within 10 s, or the request was rate limited. |

```lua title="client.lua"
RegisterCommand('reportcarjack', function()
    local id, route = exports['koja-dispatcher']:SendAlert({
        message = 'Someone is breaking into a car',
        caller = 'Anonymous',
        coords = GetEntityCoords(PlayerPedId()),
        need = { police = 1 },
        priority = 2,
    })
    if not id then
        print('alert failed: ' .. tostring(route))
    elseif route == 'call' then
        print('a dispatcher will answer the call')
    end
end, false)
```

:::hint{type="warning" title="Security: SendAlert is off by default"}
Any client can call a client export, so cheaters could use `SendAlert` to spam fake alerts. The server protects it:

- It only works when the admin setting **`api.clientAlerts`** is on (`/dispatchadmin`, default off, `Config.Api.clientAlerts`).
- It is **rate limited** per player: a burst of 3, then one every 5 seconds (`Config.Security.limits['api:alert'] = { 3, 0.2 }`). Requests over the limit get `no_response`.
- The position is **checked against the player's ped**: coordinates more than **300 m** away (2D) are replaced by the player's own position, so an alert cannot be placed across the map.
- The caller is not linked to the player: no follow-up messages or replies reach them, and an `auto` fallback incident does not follow them. The call's origin is `client:<serverId>`.
- The category cannot be set from the client: the dispatcher picks it.

Turn it on only when a client-only resource needs it. For anything with a server side, call `CreateCall` there and validate the player first.
:::

---

## `Call911(message)`

The same as typing `/911 <message>`, for phone apps and other client UIs. The call comes from the player's own position, and the dispatcher's answers, questions and replies come back to the player as notifications (or as phone messages when a supported phone is running).

| Param | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes | At least 3 characters. The server removes control characters and keeps 250. |

**Returns** `true, route`, or `false, error`.

| `route` | Meaning |
|---|---|
| `'call'` | Rings at the dispatcher console. |
| `'incident'` | Nobody on shift, `mode.noDispatcher = 'auto'`: an incident was created and units are on the way. |
| `'mdt'` | Sent to koja-mdt. |
| `'update'` | Added to the player's open call (within `calls.playerReplyWindow`) or to the incident that follows them. |

**Errors** `invalid` (message missing or shorter than 3 characters), `no_dispatchers`, `cancelled` or a `callCreate` hook's reason, `no_response` (timeout or rate limit: a burst of 2, then one every 20 seconds).

```lua title="client.lua"
RegisterNUICallback('phone:call911', function(data, cb)
    local ok, route = exports['koja-dispatcher']:Call911(data.text)
    cb({ ok = ok, route = route })
end)
```

`Call911` does not depend on `api.clientAlerts`: it is the same path as the `/911` command, which every player can already use.
