# API overview

koja-mdt exposes four integration surfaces:

| Surface | Side | Use it to |
|---|---|---|
| [Server exports](/new/mdt/api/server-exports) | server | Read and write MDT records from your own resources (dispatch, records, warrants, citations, custody, EMS, court…). |
| [Client exports](/new/mdt/api/client-exports) | client | Open the tablet, read the local officer state, send 911 calls, drive the bodycam. |
| [Hooks](/new/mdt/api/hooks) | server | Block, rewrite or react to MDT actions, whether they come from the tablet, the web portal or an export. |
| [Events](/new/mdt/api/events) | both | Listen to MDT activity with plain `AddEventHandler`. |

The [integrations](/new/mdt/api/integrations) page explains the `editable/` bridges for jails, fines, impounds and frameworks, and the [examples](/new/mdt/api/examples) page has complete recipes.

## Getting started

:::steps
### Start order

Start your resource **after** `koja-mdt` (and its dependencies `oxmysql` and `koja-lib`).

```cfg title="server.cfg"
ensure oxmysql
ensure koja-lib
ensure koja-mdt
ensure my-robberies
```

### Call an export

```lua title="server.lua"
local mdt = exports['koja-mdt']
local medics = mdt:CountOnDuty({ type = 'ems' })
```

### Handle errors

Most write exports return `result, errorCode`. Always check the first value.

```lua title="server.lua"
local warrant, err = exports['koja-mdt']:CreateWarrant({ type = 'arrest', name = 'John Doe', reason = 'Armed robbery' })
if not warrant then
    print('warrant failed: ' .. tostring(err))
end
```
:::

## Conventions

| Topic | Rule |
|---|---|
| Naming | Exports are PascalCase (`GetCitizen`, `CreateWarrant`). The only exceptions are the hook helpers `registerHook` and `removeHook`. |
| Input | Domain exports take one input table (`CreateIncident(data)`) or a primary key plus an optional change table (`UpdateBolo(id, changes, opts)`). |
| Output | `result` on success. `nil, errorCode` (or `false, errorCode`) on failure. Lookups return `nil` or an empty table instead of an error. |
| Validation | Every input is type-checked and clamped with the same `Guard` helpers the tablet uses (lengths, enums, ranges). Bad input never throws. |
| Side | Server exports are callable from server scripts only. Clients can never reach them. |
| Yielding | Exports that touch the database or the network yield. Call them from a thread, an event handler or a callback, never from the main chunk of a file. |
| Copies | Tables cross the resource boundary as copies. Changing a returned table does not change the MDT. |

## Sources and identifiers

| Term | Meaning |
|---|---|
| `src` / server id | The player's server id (a number). Exports that need a live player accept it. |
| `identifier` | The framework character id: `citizenid` on QBCore and Qbox, `identifier` on ESX. Records (citizens, warrants, citations, jail records) are keyed by it, so it works for offline players. |
| `who` | Parameters named `who` accept **either** a server id (number) or an identifier (string). A number is always treated as a server id. |
| `plate` | Trimmed and upper-cased by the MDT. `' abc 123 '` and `'ABC 123'` refer to the same vehicle. |
| `department` | A department id from `shared/departments.lua` (`lspd`, `bcso`, `sahp`, `doj`, `ems`, `lsfd`, …). |

## Acting identity

Write exports run through the same internal action handlers as the tablet (`Api.Invoke`), so permissions, audit logs, hooks, Discord webhooks and live sync behave exactly as if an officer did it. Who the action runs as is controlled by an options table, passed either inside the input table (`CreateIncident(data)`) or as the last argument (`opts`):

| Field | Type | Effect |
|---|---|---|
| `officer` | number | Run as this on-duty or off-duty MDT member (server id). Their name goes on the record and **their permissions are enforced**. If the player is not an MDT member the export returns `nil, 'not_authorized'`. |
| `department` | string | When no `officer` is given: the department that owns the record. |
| `author` | string | When no `officer` is given: the display name used on the record and in logs (default `System`). |

Without `officer`, the action runs as **System**: a virtual session with every permission and the `command` rank. Server resources are trusted, so System bypasses the permission checks but never the input validation, the hooks or the state rules (for example you still cannot pay a void citation).

:::hint{type="info" title="Default department"}
System actions use `department` when you pass one. Otherwise they use the first department in `Config.DepartmentOrder` of the matching type: law enforcement for police exports, EMS for medical exports (`CreateTreatment`, `CreateMedicalBill`, `AdmitPatient`, …) and DOJ for court exports (`CreateCourtCase`, `SetCourtVerdict`, …).
:::

:::hint{type="warning" title="Game-only actions"}
A few actions need a real player in the world (processing a suspect, handing out a printed citation, attaching to a call). As System they return `nil, 'game_only'` or `nil, 'not_in_game'`. Pass `officer = src` for those.
:::

## Permissions

Permission names are listed in `shared/permissions.lua` and returned by `GetPermissionList()`. Ranks map to permission sets per department.

```lua title="server.lua"
if exports['koja-mdt']:HasPermission(source, 'warrants.approve') then
    -- the player can approve warrants in the MDT
end
```

| Export | Returns |
|---|---|
| `HasPermission(src, perm)` | `boolean` |
| `GetPermissions(src)` | sorted `string[]` of the player's permissions |
| `GetPermissionList()` | every permission name the MDT knows |

## Error codes

Errors are short machine-readable strings. The ones you will meet most often:

| Code | Meaning |
|---|---|
| `invalid` | Missing or malformed input. |
| `not_found` | The record, call or player does not exist. |
| `not_authorized` | `officer` was given but the player is not an MDT member. |
| `no_permission` | The acting officer lacks the permission, or the record belongs to another department. |
| `cancelled` | A `before` hook returned `false` without a reason. Hooks can also return their own code. |
| `disabled` | The feature is turned off in `shared/config.lua`. |
| `game_only` / `not_in_game` | The action needs a live player; pass `officer`. |
| `target_offline` / `target_far` | The citizen must be online / nearby (processing, billing, impound fees). |
| `already_processed` | The suspect, sentence or payment was already applied. |
| `not_pending` / `not_active` / `not_payable` | The record is in the wrong state for the action. |
| `fine_failed` / `bill_failed` | Taking money from the player failed (not enough money). |
| `unknown_action` | `Invoke` was called with an action name that does not exist. |
| `failed` | Unexpected internal error. It is printed to the server console. |

Domain-specific codes (`plate_required`, `no_violations`, `bed_taken`, `already_impounded`, `bill_too_high`, `cooldown`, …) are listed next to each export.

## Common shapes

These shapes are returned by many exports. Fields can be `nil` when the value is unknown.

### Officer

```lua
{
    source = 4, identifier = 'ABC12345', name = 'John Doe', callsign = '1-ADAM-12', badge = '1042',
    department = 'lspd', rank = 'officer', gradeLabel = 'Officer II',
    status = 'available', onDuty = true, call = 12, image = 'https://...',
}
```

### Dispatch call

```lua
{
    id = 12, code = '10-90', title = 'Bank alarm', message = 'Fleeca Great Ocean', street = 'Great Ocean Hwy',
    coords = { x = 0.0, y = 0.0, z = 0.0 }, priority = 1, status = 'active',
    caller = { name = 'Alarm company', phone = nil },
    units = { { source = 4, callsign = '1-ADAM-12', name = 'John Doe', department = 'lspd' } },
    notes = { { by = '1-ADAM-12 John Doe', text = 'Suspects left north', at = 1730000000000 } },
    blip = { sprite = 161, color = 1, scale = 1.2 }, createdAt = 1730000000000,
}
```

### Timestamps

Database timestamps come back the way oxmysql returns them (usually a number of milliseconds). Live dispatch data (`createdAt`, note `at`) is always milliseconds since epoch.

## Generic action bridge

Every tablet action can also be called directly. Use the named exports when one exists; `Invoke` is the escape hatch for anything else.

```lua title="server.lua"
local result, err = exports['koja-mdt']:Invoke('divisions:list', {}, { department = 'lspd' })
```

See `Invoke` and `GetApiActions` in the [server exports](/new/mdt/api/server-exports) for details.
