API Reference
Everything a resource can call — server-side mutations and reads, plus client-side helpers.
Related: Statebags (raw client data) · Events · Hooks
Before mutating: wait for
bln_society:ready. Mutating exports returnnot_readyuntil boot finishes.Money primitives:
AddMoney/RemoveMoneyhave no permission check (revenue and costs).Deposit/Withdrawcheck permissions and move player money.
Response shape
Every mutating function answers with the same table:
{
success = true,
code = 'member_hired',
message = 'Arthur Morgan has been hired as Deputy.',
args = { 'Arthur Morgan', 'Deputy' },
data = { ... },
}code is stable across languages — branch on it and show message. Pure reads (GetBalance, HasPermission, IsOnDuty, …) return their value directly.
The actor argument
Most functions take a trailing actor:
| You pass | Treated as |
|---|---|
nothing, or nil | System call — permission checks skipped, audit still written |
player source (number) | That player — permissions enforced |
| character id (string) | Same, resolved from character |
{ source = ..., charId = ... } | When you have both |
{ system = true, charId = ... } | System call attributed to a character in the audit |
Pass source when a player clicked something. Omit it when your resource acts on its own (e.g. booking a sale into the till).
Server exports
Meta
| Export | Arguments | Returns |
|---|---|---|
IsReady() | – | boolean |
GetConstants() | – | Const table: statuses, ledger types, actions, hook names |
GetPermissionRegistry() | – | permission groups from config |
GetPresets() | – | society presets from config |
GetSettings() | – | merged config settings |
GetBlips() | – | blip sprite catalogue with NUI icon URLs |
Each entry from GetBlips():
{
name = 'blip_shop_store',
dict = 'blips',
label = 'Store',
img = 'https://cfx-nui-bln_society/ui/blips/blip_shop_store.png',
}Blips are defined in config/blips.cfg.lua.
Session
| Export | Arguments | Returns |
|---|---|---|
GetCharacterId(source) | player source | string|nil |
GetSource(characterId) | character id | integer|nil |
GetCharacterName(characterId) | character id | string |
Societies
| Export | Arguments | Returns |
|---|---|---|
CreateSociety(data, actor?) | see below | response, data = society |
EnsureSociety(data) | requires ownerResource + externalKey | response, creates only if missing |
GetSociety(societyId) | response | |
GetSocietyByKey(ownerResource, externalKey) | response | |
GetSocieties(filter?) | { status?, fundingSource? } | response, data = array |
UpdateSociety(societyId, data, actor?) | { name?, status?, fundingSource?, metadata?, chargeFees?, uiPolicy? } | response |
GetUiPolicy(societyId) | response, data = UI policy | |
UpdateSettings(societyId, settings, actor?) | merged into settings bag | response |
GetSetting(societyId, key, fallback?) | value | |
SetFundingSource(societyId, 'account'|'state', actor?) | system only | response |
IsStateFunded(societyId) | boolean | |
SetSocietyPoint(societyId, point, actor?) | see below | response |
ClearSocietyPoint(societyId, actor?) | response | |
GetSocietyPoint(societyId) | response, data = point or nil | |
OpenSociety(societyId, source, opts?) | opens menu for player; optional trusted opts.ui tightens restrictions for this open | response |
TransferOwnership(societyId, characterId, actor?) | owner or system | response |
IsOwner(societyId, characterId) | boolean | |
GetOwner(societyId) | response, data = { characterId, name } | |
DeleteSociety(societyId, actor?) | soft delete | response |
PurgeSociety(societyId) | permanent, system only | response |
CreateSociety data:
{
name = 'Valentine Sheriff',
preset = 'law',
fundingSource = 'state',
owner = characterId,
ownerName = 'Arthur Morgan',
status = 'active',
point = { ... },
ownerResource = 'bln_stores',
externalKey = 'valentine_store',
ranks = { ... },
balances = { [0] = 500.0 },
settings = { require_no_other_society = true },
metadata = { ... },
uiPolicy = {
tabs = { settings = false, payroll = false },
actions = { society_delete = false, society_rename = false, society_status = false },
},
createStorages = true,
}UI policy — stored as metadata.ui. Set at creation or via UpdateSociety with a system actor. false hides a tab or action even when the viewer has permission. Permissions still apply; policy only narrows what the menu may show.
| Kind | Keys |
|---|---|
tabs | members, ranks, finances, payroll, storage, fines, taxes, logs, settings |
actions | society_delete, society_rename, society_status |
Trusted open override (server export only — cannot loosen stored policy):
exports.bln_society:OpenSociety(societyId, source, {
ui = { tabs = { finances = false } },
})Point shape (fields optional except coords; defaults from Config.points.defaults):
{
enabled = true,
coords = { x = -324.1, y = 776.5, z = 117.8, h = 90.0 },
blip = { enabled = true, sprite = 'blip_shop_store', name = 'Valentine Store' },
marker = { type = 'default', color = { r = 255, g = 255, b = 255, a = 180 }, scale = 1.0, distance = 15.0 },
prompt = { enabled = true, name = 'Open', key = Keys.E, distance = 2.0 },
}Society settings (UpdateSettings):
| Key | Type | Purpose |
|---|---|---|
require_no_other_society | boolean | Block hiring anyone already in another society |
inactive_days | integer | Days idle before auto fire/suspend; 0 disables |
inactive_warn_days | integer | Warn window before threshold |
inactive_action | 'fire' | 'suspend' | What the inactivity sweep does |
discord_webhook | string | Discord webhook URL for audit lines |
Ranks
| Export | Arguments | Returns |
|---|---|---|
GetRanks(societyId) | response, ranks sorted by level | |
GetRank(societyId, rankId) | response | |
CreateRank(societyId, data, actor?) | { name, level, salary?, salaryCurrency?, permissions? } | response |
UpdateRank(societyId, rankId, data, actor?) | partial update | response |
DeleteRank(societyId, rankId, actor?) | fails while members hold it | response |
SetRankSalary(societyId, rankId, amount, actor?) | response |
Members
| Export | Arguments | Returns |
|---|---|---|
AddMember(societyId, characterId, rankId?, actor?) | rank defaults to lowest | response |
RemoveMember(societyId, characterId, actor?) | response | |
SetMemberRank(societyId, characterId, rankId, actor?) | response | |
SetMemberStatus(societyId, characterId, 'active'|'suspended', actor?) | response | |
GetMember(societyId, characterId) | response, includes permissions | |
GetMembers(societyId) | response, array with rank and duty flags | |
IsMember(societyId, characterId) | boolean | |
GetMemberships(characterId) | response, all societies for character | |
GetPlayerMemberships(source) | online player | statebag payload |
CanHire(societyId, characterId) | dry run | response |
CountMembers(societyId) | integer |
Permissions
| Export | Arguments | Returns |
|---|---|---|
HasPermission(societyId, characterId, permission) | boolean | |
GetPermissions(societyId, characterId) | map, wildcards expanded | |
GetPermissionOverrides(societyId, characterId) | member overrides only | |
GrantPermission(societyId, characterId, permission, actor?) | response | |
RevokePermission(societyId, characterId, permission, actor?) | explicit deny | response |
ClearPermission(societyId, characterId, permission, actor?) | inherit from rank | response |
GetRankLevel(societyId, characterId) | integer |
Duty
| Export | Arguments | Returns |
|---|---|---|
SetDuty(societyId, characterId, onDuty, actor?) | response | |
ToggleDuty(societyId, characterId, actor?) | response | |
IsOnDuty(characterId, societyId?) | omit society = anywhere | boolean |
GetDuty(characterId) | { societyId, rankId, since }|nil | |
GetPlayerDuty(source) | statebag payload | |
GetOnDutyMembers(societyId) | array of character ids | |
CountOnDuty(societyId) | integer | |
ForceOffDuty(societyId, characterId, reason?) | system | response |
GetActivity(societyId) | response, per-member activity | |
GetLastSeen(societyId) | response, last seen timestamps | |
GetInactivityReport(societyId) | read only | response |
SweepInactive(societyId?) | run sweep now | response |
Finance
Currencies: 0 = cash, 1 = gold, 2 = rol (Const.CASH, Const.GOLD, Const.ROL).
| Export | Arguments | Returns |
|---|---|---|
GetBalance(societyId, currency?) | number | |
GetBalances(societyId) | { [currency] = number } | |
CanAfford(societyId, amount, currency?) | boolean | |
AddMoney(societyId, amount, opts?) | no permission check | response |
RemoveMoney(societyId, amount, opts?) | no permission check | response |
Deposit(societyId, amount, opts?, actor?) | from player pocket | response |
Withdraw(societyId, amount, opts?, actor?) | to player pocket | response |
Transfer(fromId, toId, amount, opts?, actor?) | response | |
GetLedger(societyId, filters?, actor?) | paged | response |
GetLedgerSummary(societyId, currency?, days?) | { income, expenses, net, days } | |
GetStateSpending(societyId?, days?) | state-funded wages | number |
Finance opts:
{
currency = 0,
type = 'sale',
reason = 'Cart sold',
ref = 'store:sale:8821', -- idempotency key
actorId = characterId,
targetId = characterId,
metadata = { ... },
}Pass a unique ref to make retries safe — duplicate ref returns the original entry.
Payroll and paychecks
| Export | Arguments | Returns |
|---|---|---|
RunPayroll(societyId, actor?) | response with totals | |
GetPayrollHistory(societyId, filters?, actor?) | paged | response |
GetSalary(societyId, characterId) | number | |
GetPayrollEligible(societyId) | array | |
GetPaychecks(societyId, characterId) | response | |
GetPendingPay(characterId) | all societies | map |
CollectPaychecks(societyId, actor) | response | |
CollectAllPaychecks(actor) | response | |
IssuePaycheck(societyId, characterId, amount, opts?) | bonus, etc. | response |
Storage
| Export | Arguments | Returns |
|---|---|---|
GetStorages(societyId, charId?) | response | |
GetStorage(societyId, key) | response | |
OpenStorage(societyId, key, actor) | opens inventory | response |
BuyStorage(societyId, actor, payload) | paid from account | response |
SetStorageAccess(societyId, key, minRankLevel, actor?) | response | |
CalculateStorageCost(slots, maxWeight, acceptWeapons) | response | |
CreateStorage(societyId, key, opts?) | response |
Fines
| Export | Arguments | Returns |
|---|---|---|
IssueFine(societyId, characterId, amount, reason, actor?) | response | |
PayFine(fineId, actor) | response | |
CancelFine(fineId, actor?) | response | |
GetFines(societyId, filters?, actor?) | paged | response |
GetCharacterFines(characterId) | response |
Action fees
| Export | Arguments | Returns |
|---|---|---|
GetFees(societyId, actor?) | response | |
CalculateFee(societyId, action, baseAmount?) | preview | response |
ChargeFee(societyId, action, baseAmount?, opts?) | manual charge | response |
Fee actions: payroll_run, member_hire, finance_withdraw, finance_transfer, society_rename, storage_buy.
Logs
| Export | Arguments | Returns |
|---|---|---|
Log(societyId, action, opts?) | custom audit line | response |
GetLogs(societyId, filters?, actor?) | paged | response |
Directory and hooks registry
| Export | Arguments | Returns |
|---|---|---|
GetDirectory() | public society directory | |
GetRevision() | integer, bumped on structural change | |
RegisterHook(name, handler) | hook id | |
RemoveHook(name, id) | – | |
ListHooks() | hook names |
Society handle
For code that works with one society at a time:
local society = exports.bln_society:Society(societyId)
local society = exports.bln_society:SocietyByKey('bln_stores', 'valentine_store')
local society = exports.bln_society:EnsureSocietyHandle({ ... })Methods (same as exports, without societyId):
Exists Get Data Update Delete Settings Setting UpdateSettingsIsStateFunded SetFunding Owner IsOwner TransferOwnershipPoint SetPoint ClearPoint OpenRanks Rank CreateRank UpdateRank DeleteRank SetRankSalaryMembers Member IsMember AddMember RemoveMember SetMemberRank SetMemberStatus CanHireHasPermission Permissions GrantPermission RevokePermission LevelSetDuty ToggleDuty IsOnDuty OnDuty CountOnDutyBalance Balances CanAfford AddMoney RemoveMoney Deposit Withdraw TransferTo Ledger LedgerSummaryRunPayroll PayrollHistory Salary Paychecks CollectPaychecks IssuePaycheckStorages OpenStorage BuyStorage SetStorageAccess CreateStorageFines IssueFineFees CalculateFee ChargeFeeLogs Log
if society:HasPermission(charId, 'finances.withdraw') then
society:Withdraw(100, { reason = 'Restock' }, source)
endClient exports
Convenience wrappers around statebags — permission checks, wildcards, and filtering handled for you. No server round trip.
| Export | Returns |
|---|---|
IsReady() | boolean |
GetBlips() | blip sprite catalogue with NUI icon URLs (same shape as server) |
GetMemberships() | every society you belong to, keyed by id as string |
GetMembership(societyId) | one entry, or nil |
IsMember(societyId) | boolean |
HasPermission(societyId, permission) | boolean, wildcards resolved |
IsOnDuty(societyId?) | boolean |
GetDuty() | { id, name, type, rankId, since } or false |
GetActiveSociety() | membership you are on duty for |
GetPendingPay(societyId?) | { count, amounts } |
GetBalance(societyId, currency?) | number|nil, requires finances.view |
GetStorage(societyId, key?) | tier info, requires storage.use |
GetDirectory() | every society: name, member count, on duty count |
GetRevision() | integer — compare to invalidate cache |
GetPlayerMemberships(serverId) | another player's memberships |
GetPlayerDuty(serverId) | another player's duty |
OpenUi(societyId) / OpenSociety(societyId) / CloseUi() | management menu |
IsUiOpen() | boolean |
ToggleDuty(societyId?) | asks server |
OpenStorage(societyId, key) | asks server |
GetStorageInfo(societyId, key) | tier info from bag |
Error codes
Branch on response.code. Every code has a line in config/locale.cfg.lua.
General — generic_error, db_error, rate_limited, invalid_request, invalid_amount, player_offline, character_not_found, feature_disabled, ui_restricted, blocked_by_hook, not_ready
Society — society_not_found, not_member, no_permission, admin_only, point_invalid
Members — already_member, target_not_member, cannot_target_self, target_outranks_you, cannot_remove_owner
Hiring — target_in_other_society, too_many_societies
Ranks — rank_not_found, rank_in_use, rank_level_taken, rank_above_you, cannot_delete_owner_rank, invalid_rank_name
Duty — already_on_duty, not_on_duty, duty_elsewhere
Finance — insufficient_funds, insufficient_player_funds, transfers_disabled, negative_balance_blocked
Payroll — payroll_no_members, payroll_unaffordable, no_paychecks, paycheck_too_far
Storage — storage_not_found, storage_max_tier, storage_no_access
Fines — fine_not_found, fine_not_pending
Success codes — society_created, member_hired, member_promoted, deposited, withdrawn, payroll_done, paycheck_collected, storage_upgraded, fine_issued, point_updated, and more.
