# Developer API

Developer API documentation for Nord Doorlock.

# Server Exports

Server exports provide the authoritative developer API.

API version:

```lua
local version = exports.nord_doorlock:GetApiVersion()
```

Current API version returned by the resource:

```text
1
```

## GetApiVersion

```lua
local version = exports.nord_doorlock:GetApiVersion()
```

Returns a number.

## IsReady

```lua
local ready, errorMessage = exports.nord_doorlock:IsReady()
```

Returns:

```text
boolean ready
string|nil errorMessage
```

Use this when another resource starts at the same time as Nord Doorlock.

## GetDoor

```lua
local door = exports.nord_doorlock:GetDoor(id)
```

Returns a copy of the door table or `nil`.

## GetDoors

```lua
local doors = exports.nord_doorlock:GetDoors()
```

Returns an array of copied door definitions sorted by ID.

## GetDoorCount

```lua
local count = exports.nord_doorlock:GetDoorCount()
```

Returns the number of loaded doors.

## GetDoorState

```lua
local locked = exports.nord_doorlock:GetDoorState(id)
```

Returns:

```text
true  = locked
false = unlocked
nil   = door not found
```

## GetDoorByName

Case-insensitive exact match:

```lua
local door = exports.nord_doorlock:GetDoorByName('Mission Row Front')
```

Returns the first matching door or `nil`.

## FindDoorsByName

Case-insensitive partial search:

```lua
local doors = exports.nord_doorlock:FindDoorsByName('mission')
```

Returns an array.

## FindDoorByCoords

```lua
local door, distance = exports.nord_doorlock:FindDoorByCoords(
    vector3(440.0, -998.0, 30.0),
    5.0
)
```

Coordinates can be a `vector3` or a table:

```lua
{ x = 440.0, y = -998.0, z = 30.0 }
```

Returns the nearest door within the radius and its distance.

## GetNearestDoor

Server-side nearest door for a player:

```lua
local door, distance =
    exports.nord_doorlock:GetNearestDoor(source, 10.0)
```

Requires a valid player source.

## HasAccess

```lua
local allowed = exports.nord_doorlock:HasAccess(source, doorId)
```

Returns `true` or `false`.

This includes the normal Nord Doorlock access rules and registered authorization hooks.

## SetDoorState

```lua
local ok, stateOrError =
    exports.nord_doorlock:SetDoorState(1, true)
```

`true` means locked.

On success:

```text
ok = true
stateOrError = true/false final state
```

On failure:

```text
ok = false
stateOrError = error code
```

## LockDoor

```lua
local ok, result = exports.nord_doorlock:LockDoor(1)
```

## UnlockDoor

```lua
local ok, result = exports.nord_doorlock:UnlockDoor(1)
```

When the door has `autoLock > 0`, unlocking through the API schedules automatic relocking.

## ToggleDoorState

```lua
local ok, result = exports.nord_doorlock:ToggleDoorState(1)
```

## CreateDoor

```lua
local ok, doorOrError, duplicateId =
    exports.nord_doorlock:CreateDoor({
        name = 'Police Front Door',
        distance = 2.0,
        autoLock = 0,
        defaultLocked = true,
        locked = true,

        parts = {
            {
                model = 123456789,
                coords = {
                    x = 440.0,
                    y = -998.0,
                    z = 30.0
                },
                heading = 90.0
            }
        },

        access = {
            jobs = {
                police = 0
            },
            identifiers = {},
            ace = {}
        },

        lockpick = {
            enabled = false
        }
    })
```

On success:

```text
ok = true
doorOrError = created door
```

Potential errors include:

```text
storage_not_ready
invalid_payload
invalid_parts
invalid_part
invalid_part_values
physical_door_already_registered
persistence_failed
```

When the physical door is already registered, the third return value can contain the conflicting door ID.

## UpdateDoor

Patch an existing door:

```lua
local ok, doorOrError, duplicateId =
    exports.nord_doorlock:UpdateDoor(1, {
        autoLock = 30,

        access = {
            jobs = {
                police = 2
            },
            identifiers = {},
            ace = {}
        }
    })
```

Fields not included in the patch are copied from the existing definition.

The `id` is supplied separately and is not overwritten by the patch.

## DeleteDoor

```lua
local ok, errorMessage =
    exports.nord_doorlock:DeleteDoor(1)
```

On success, the door is removed from persistence and clients receive a full synchronization.

## ReloadDoors

```lua
local ok, countOrError, removedDuplicates =
    exports.nord_doorlock:ReloadDoors()
```

On success:

```text
ok = true
countOrError = current door count
removedDuplicates = number removed during reload
```

## GetStorageHealth

```lua
local health = exports.nord_doorlock:GetStorageHealth()
```

See the **Storage & Persistence** page.

## RegisterHook

```lua
local hookId, errorMessage =
    exports.nord_doorlock:RegisterHook(
        'doorAuthorization',
        function(payload)
            return nil
        end,
        {}
    )
```

See the **Authorization Hooks** page.

## RemoveHooks

Remove one hook owned by the calling resource:

```lua
local ok = exports.nord_doorlock:RemoveHooks(hookId)
```

Remove all hooks owned by the calling resource:

```lua
local ok = exports.nord_doorlock:RemoveHooks()
```

## Compatibility Aliases

Lowercase compatibility aliases also exist:

```lua
exports.nord_doorlock:registerHook(...)
exports.nord_doorlock:removeHooks(...)
```

For new integrations, prefer:

```text
RegisterHook
RemoveHooks
```


# Client Exports

Client exports are intended for local door lookup, UI integrations and server-authoritative action requests.

Do not use client exports as a security boundary.

## GetApiVersion

```lua
local version = exports.nord_doorlock:GetApiVersion()
```

## GetDoor

```lua
local door = exports.nord_doorlock:GetDoor(id)
```

Returns a copied door table or `nil`.

## GetDoors

```lua
local doors = exports.nord_doorlock:GetDoors()
```

Returns all locally synchronized doors sorted by ID.

## GetDoorState

```lua
local locked = exports.nord_doorlock:GetDoorState(id)
```

Returns:

```text
true
false
nil
```

## IsDoorLocked

Alias-style state helper:

```lua
local locked = exports.nord_doorlock:IsDoorLocked(id)
```

## GetDoorByName

```lua
local door = exports.nord_doorlock:GetDoorByName('Police Front Door')
```

Case-insensitive exact match.

## FindDoorsByName

```lua
local doors = exports.nord_doorlock:FindDoorsByName('police')
```

Case-insensitive partial match.

## GetDoorCenter

```lua
local coords = exports.nord_doorlock:GetDoorCenter(id)
```

Returns:

```lua
{
    x = 440.0,
    y = -998.0,
    z = 30.0
}
```

or `nil`.

For double doors, the logical center is calculated from both parts.

## FindDoorByCoords

```lua
local door, distance =
    exports.nord_doorlock:FindDoorByCoords(
        GetEntityCoords(PlayerPedId()),
        5.0
    )
```

## GetNearestDoor

Returns the nearest door currently tracked by Nord Doorlock's interaction loop:

```lua
local door, distance = exports.nord_doorlock:GetNearestDoor()
```

The result may be `nil` when no relevant nearby door is being tracked.

## GetDoorEntities

```lua
local entities = exports.nord_doorlock:GetDoorEntities(id)
```

Returns an array indexed by door part.

Example:

```lua
local entities = exports.nord_doorlock:GetDoorEntities(12)

for index, entity in pairs(entities) do
    print(index, entity)
end
```

Only currently resolvable entities are returned.

## RequestToggle

```lua
local sent = exports.nord_doorlock:RequestToggle(id)
```

This does **not** directly change the door state.

It sends the standard server request, where distance and access are validated.

Returns `false` if the local door ID does not exist.

## RequestLockpick

```lua
local sent = exports.nord_doorlock:RequestLockpick(id)
```

Returns `false` if the local door does not exist or lockpicking is unavailable for that door.

The real validation still occurs server-side.

## RequestSync

```lua
exports.nord_doorlock:RequestSync()
```

Requests a fresh full door synchronization from the server.

## OpenAdmin

```lua
exports.nord_doorlock:OpenAdmin()
```

Requests the administrator panel.

The server still validates administrator permissions.


# Authorization Hooks

Authorization hooks allow another server resource to extend or override door access without modifying Nord Doorlock.

Supported hook event:

```text
doorAuthorization
```

## Basic Example

```lua
local hookId = exports.nord_doorlock:RegisterHook(
    'doorAuthorization',
    function(payload)
        if payload.door.name == 'VIP Entrance' then
            return exports.my_vip:IsVip(payload.source)
        end
    end
)
```

## Hook Payload

The callback receives:

```lua
{
    source = 12,
    door = {
        -- copied door definition
    },
    lockpick = false,
    authorised = true
}
```

### source

Player server ID.

### door

A copy of the current door definition.

### lockpick

Boolean indicating the authorization context supplied by Nord Doorlock.

### authorised

Authorization result calculated before this hook.

## Return Values

Return:

```lua
true
```

to authorize.

Return:

```lua
false
```

to deny.

Return nothing / `nil` to leave the current result unchanged.

Multiple hooks can run. A boolean returned by a later matching hook becomes the current result for subsequent hooks.

## Name Filter

Hook options may contain a Lua pattern:

```lua
local hookId = exports.nord_doorlock:RegisterHook(
    'doorAuthorization',
    function(payload)
        return exports.my_access:CanUseDoor(
            payload.source,
            payload.door.id
        )
    end,
    {
        nameFilter = '^Police'
    }
)
```

Only matching door names are evaluated by that hook.

`nameFilter` is used with Lua's `string.match`, so treat it as a Lua pattern rather than a plain substring.

## Remove One Hook

```lua
exports.nord_doorlock:RemoveHooks(hookId)
```

## Remove All Hooks Owned by Your Resource

```lua
exports.nord_doorlock:RemoveHooks()
```

A resource can only remove hooks registered under its own invoking resource identity.

## Recommended Resource Cleanup

```lua
local hookId

CreateThread(function()
    hookId = exports.nord_doorlock:RegisterHook(
        'doorAuthorization',
        function(payload)
            -- custom access
        end
    )
end)

AddEventHandler('onResourceStop', function(resource)
    if resource ~= GetCurrentResourceName() then return end

    if hookId then
        exports.nord_doorlock:RemoveHooks(hookId)
    end
end)
```


# Door Data Structure

A Nord Doorlock door is represented as a Lua table.

## Example — Single Door

```lua
{
    id = 1,
    name = 'Police Front Door',
    distance = 2.0,
    autoLock = 0,
    locked = true,
    defaultLocked = true,

    parts = {
        {
            model = 123456789,
            coords = {
                x = 440.1234,
                y = -998.5678,
                z = 30.1234
            },
            heading = 90.0
        }
    },

    access = {
        jobs = {
            police = 0
        },

        identifiers = {},

        ace = {}
    },

    lockpick = {
        enabled = false
    }
}
```

## Example — Double Door

```lua
{
    name = 'Main Double Door',

    parts = {
        {
            model = 111111111,
            coords = {
                x = 100.0,
                y = 200.0,
                z = 30.0
            },
            heading = 90.0
        },

        {
            model = 222222222,
            coords = {
                x = 102.0,
                y = 200.0,
                z = 30.0
            },
            heading = 270.0
        }
    }
}
```

## Field Reference

| Field | Type | Description |
| --- | --- | --- |
| `id` | number | Persistent internal ID |
| `name` | string | Friendly door name |
| `distance` | number | Interaction distance |
| `autoLock` | number | Seconds before relocking, `0` disables |
| `locked` | boolean | Current persistent state |
| `defaultLocked` | boolean | Default configured state |
| `parts` | array | One or two physical door parts |
| `access` | table | Jobs, identifiers and ACE rules |
| `lockpick` | table | Lockpick settings |

## Part Structure

```lua
{
    model = 123456789,

    coords = {
        x = 0.0,
        y = 0.0,
        z = 0.0
    },

    heading = 0.0
}
```

The captured heading is important for physical fallback behavior on doors that do not correctly close through GTA DoorSystem alone.

## Access Structure

```lua
access = {
    jobs = {
        police = 0
    },

    identifiers = {
        'license:...'
    },

    ace = {
        'door.police'
    }
}
```

## Lockpick Structure

```lua
lockpick = {
    enabled = true
}
```

## Developer API Copies

`GetDoor`, `GetDoors` and similar read exports return copies.

Changing the returned table does not edit the actual Nord Doorlock definition.

Use `UpdateDoor` for persistent server-side edits.


# Integration Examples

## Lock a Door From Another Server Resource

```lua
local success, result =
    exports.nord_doorlock:LockDoor(12)

if not success then
    print(('Failed to lock door: %s'):format(result))
end
```

## Unlock a Door Temporarily

If the door has an auto-lock delay, using `UnlockDoor` schedules its normal relock behavior.

```lua
exports.nord_doorlock:UnlockDoor(12)
```

## Toggle by Door Name

```lua
local door =
    exports.nord_doorlock:GetDoorByName('Bank Main Entrance')

if door then
    exports.nord_doorlock:ToggleDoorState(door.id)
end
```

## Check Player Access Before Running Another Action

```lua
RegisterNetEvent('my_script:server:secureAction', function(doorId)
    local src = source

    if not exports.nord_doorlock:HasAccess(src, doorId) then
        return
    end

    -- Run protected server-side action.
end)
```

## Find a Door Near Coordinates

```lua
local door, distance =
    exports.nord_doorlock:FindDoorByCoords(
        vector3(440.0, -998.0, 30.0),
        10.0
    )

if door then
    print(('Found %s at %.2fm'):format(
        door.name,
        distance
    ))
end
```

## Create a Job Door Programmatically

```lua
local ok, doorOrError, duplicateId =
    exports.nord_doorlock:CreateDoor({
        name = 'EMS Secure Room',
        distance = 2.0,
        locked = true,
        defaultLocked = true,

        parts = {
            {
                model = 123456789,
                coords = {
                    x = 300.0,
                    y = -600.0,
                    z = 43.0
                },
                heading = 180.0
            }
        },

        access = {
            jobs = {
                ambulance = 2
            },
            identifiers = {},
            ace = {}
        },

        lockpick = {
            enabled = false
        }
    })

if not ok then
    print('CreateDoor failed:', doorOrError, duplicateId)
end
```

## Add Temporary Custom Access With a Hook

```lua
local hook = exports.nord_doorlock:RegisterHook(
    'doorAuthorization',
    function(payload)
        if payload.door.name == 'Event Room' then
            return GlobalState.eventRoomOpen == true
        end
    end
)
```

## Client HUD Integration

```lua
CreateThread(function()
    while true do
        local door, distance =
            exports.nord_doorlock:GetNearestDoor()

        if door and distance then
            -- Update your HUD.
        end

        Wait(250)
    end
end)
```

## Client Interaction Button

Do not set the local state yourself.

Use:

```lua
exports.nord_doorlock:RequestToggle(doorId)
```

This preserves server-side permissions and distance validation.