Skip to content

FiniAC Resource API ​

The FiniAC Resource API provides server-side integration points for your custom resources. Through events and commands, you can synchronize your queue systems with FiniAC's connection flow and trigger custom detections from your own anti-cheat logic.

Overview ​

The Resource API allows your resources to:

  • Integrate with queue systems and connection handlers
  • Trigger custom detections from your own anti-cheat logic
  • Inspect, edit or cancel detections before FiniAC processes them

All API features run server-side and are designed to work seamlessly with FiniAC's detection and trigger systems.

Defer Events ​

FiniAC emits events during the player connection (deferral) process. These events allow you to integrate FiniAC with queue systems, custom connection handlers, or any logic that needs to run during the initial connection deferrals.

Why Use Defer Events? ​

Listening to FiniAC's defer events ensures that:

  • Your queue system only displays after FiniAC has verified the player
  • You avoid deferral message conflicts between FiniAC and your scripts
  • You can make decisions based on whether FiniAC allowed or rejected the connection
  • Your connection flow remains synchronized with FiniAC's security checks

Available Events ​

FiniAC:DeferStarted ​

Emitted when a player begins the connection verification process.

Parameters:

  • source (string): The temporary player source ID during connection

When it fires:

  • Immediately after the player begins connecting
  • Before any security checks are performed
  • Before the player receives a permanent source ID

FiniAC:DeferFinished ​

Emitted when FiniAC completes all connection verification checks.

Parameters:

  • source (string): The player source ID
  • joinAllowed (boolean): true if the player was allowed to join, false if they were rejected

When it fires:

  • After all security checks (bans, VPN, trust score, etc.)
  • After Terms of Service acceptance (if required)
  • Just before the player fully joins or gets kicked

Usage Examples ​

lua
-- Listen for when a player starts connecting
AddEventHandler("FiniAC:DeferStarted", function(source)
    print(("Player %s started FiniAC deferral"):format(source))
    -- Your queue logic can start here
end)

-- Listen for when FiniAC finishes verification
AddEventHandler("FiniAC:DeferFinished", function(source, joinAllowed)
    if joinAllowed then
        print(("Player %s passed FiniAC deferral"):format(source))
    else
        print(("Player %s was rejected by FiniAC"):format(source))
    end
end)
typescript
// Listen for when a player starts connecting
on("FiniAC:DeferStarted", (source: string) => {
  console.log(`Player ${source} started FiniAC deferral`);
  // Your queue logic can start here
});

// Listen for when FiniAC finishes verification
on("FiniAC:DeferFinished", (source: string, joinAllowed: boolean) => {
  if (joinAllowed) {
    console.log(`Player ${source} passed FiniAC deferral`);
  } else {
    console.log(`Player ${source} was rejected by FiniAC`);
  }
});

Best Practices ​

TIP

  • Only display your server's Terms of Service, rule acceptance, etc. deferrals after FiniAC:DeferFinished has fired.
  • Wait until FiniAC:DeferFinished before adding players to queue to avoid banned players taking up slots.

Routing Bucket Integration ​

FiniAC isolates players in their own routing bucket during from loading screen until the anti-cheat is fully initialised. Using FiniAC:SetPlayerOriginalRoutingBucket ensures FiniAC can properly restore players to their correct bucket after initialisation or security checks.

Why Set Original Routing Bucket? ​

If your server uses an entry system that isolates new players in a custom routing bucket, you use the FiniAC:SetPlayerOriginalRoutingBucket event to inform FiniAC of routing bucket the player should be returned to. By default FiniAC returns players to the routing bucket they are in during the loading screen.

FiniAC:SetPlayerOriginalRoutingBucket Event ​

Use this event on the server to set the routing bucket FiniAC should return the player to after they've initialised.

Parameters:

  • source (string): The player's source ID
  • bucket (number): The routing bucket number

When to use:

  • Your server isolates new players in a custom routing bucket
  • Your server uses multiple routing buckets for minigames, lobbies, etc.

Usage Examples ​

lua
-- Example: Server isolates new players in bucket 100
local playerId = source
local queueBucket = 100

-- Set the player's routing bucket
SetPlayerRoutingBucket(playerId, queueBucket)

-- Notify FiniAC of the original bucket so it can restore later
TriggerEvent("FiniAC:SetPlayerOriginalRoutingBucket", tostring(playerId), queueBucket)
typescript
// Example: Queue system places player in bucket 100
const playerId: string = source.toString();
const queueBucket: number = 100;

// Set the player's routing bucket
SetPlayerRoutingBucket(playerId, queueBucket);

// Notify FiniAC of the original bucket so it can restore later
emit("FiniAC:SetPlayerOriginalRoutingBucket", playerId, queueBucket);

Best Practices ​

TIP

  • Always call this event after setting the player's routing bucket with SetPlayerRoutingBucket
  • This is only necessary if you're using custom routing buckets
  • The event can only be triggered locally on the server

Detection Hooks ​

The AddDetectionHook export lets your resource inspect every detection before FiniAC processes it. A hook can let the detection through unchanged, edit its data, or cancel it entirely. Cancelled detections are never logged and never trigger a screenshot.

Why Use Detection Hooks? ​

  • Suppress detections your own scripts, such as ArmedPed being triggered by guards from heist scripts
  • Add context from your framework to a detection, such as the player's job or the inventory they had open
  • Feed detections into your own logging or moderation tooling

AddDetectionHook Export ​

typescript
interface FiniACHookPlayer {
  source: number;
  name: string;
  identifiers: {
    license?: string;
    license2?: string;
    discord?: string;
    fivem?: string;
    xbl?: string;
    live?: string;
    ip?: string;
  };
}

interface FiniACHookDetection {
  type: string;
  data: string[];
}

global.exports.FiniAC.AddDetectionHook(
  (
    player: FiniACHookPlayer,
    detection: FiniACHookDetection,
  ): FiniACHookDetection | false | void => {
    // return false to cancel, return detection to edit, return nothing to keep as is
  },
);
lua
---@class FiniACHookPlayer
---@field source number The player's server ID
---@field name string The player's name
---@field identifiers { license?: string, license2?: string, discord?: string, fivem?: string, xbl?: string, live?: string, ip?: string }

---@class FiniACHookDetection
---@field type string The detection name, for example "NoclipV2"
---@field data (string|number)[] The detection data

---@param player FiniACHookPlayer
---@param detection FiniACHookDetection
---@return FiniACHookDetection|false|nil
exports.FiniAC:AddDetectionHook(function(player, detection)
    -- return false to cancel, return detection to edit, return nil to keep as is
end)

Return value:

  • false: The detection is cancelled
  • A table with the same type and data shape: The detection's type and data are replaced with what you returned, then FiniAC continues processing it
  • nil or no return: The detection continues unchanged

How detection hooks run:

  • Hooks run on the server, synchronously, in the order they were registered. Each hook sees the result of the previous one.
  • Hooks must not yield. Calling Wait() inside a hook counts as an invalid return.

Registering Your Hook ​

FiniAC finishes starting shortly after the resource starts, so the export is not available in the first moments after start FiniAC. Two signals cover both start orders:

  • FiniAC:Started (server event): Fired every time FiniAC finishes starting. Register your hook in its handler, this also re-registers it after a FiniAC restart.
  • FiniAC:Started (convar, 1 while FiniAC is running): Check it when your own resource starts, in case FiniAC was already running.

Usage Examples ​

lua
local function registerHooks()
    ---@param player FiniACHookPlayer
    ---@param detection FiniACHookDetection
    exports.FiniAC:AddDetectionHook(function(player, detection)
        -- Cancel BlacklistedWeapon detections while the player in paintball
        if detection.type == 'BlacklistedWeapon' and Player(player.source).state.inPaintball then
            return false
        end

        -- Add context to a detection and let it through
        if detection.type == 'BadTaze' then
            table.insert(detection.data, ('job:%s'):format(MyFramework.getJob(player.source)))
            return detection
        end

        -- Nothing returned: the detection continues unchanged
    end)
end

-- FiniAC starts (or restarts) after this resource
AddEventHandler('FiniAC:Started', registerHooks)

-- FiniAC was already running when this resource started
if GetConvarInt('FiniAC:Started', 0) == 1 then
    registerHooks()
end
typescript
// FiniACHookPlayer and FiniACHookDetection as declared above

function registerHooks(): void {
  global.exports.FiniAC.AddDetectionHook(
    (player: FiniACHookPlayer, detection: FiniACHookDetection) => {
      // Cancel BlacklistedWeapon detections while the player in paintball
      if (
        detection.type === "BlacklistedWeapon" &&
        Player(player.source).state.inPaintball
      ) {
        return false;
      }

      // Add context to a detection and let it through
      if (detection.type === "BadTaze") {
        detection.data.push(`job:${MyFramework.getJob(player.source)}`);
        return detection;
      }

      // Nothing returned: the detection continues unchanged
    },
  );
}

// FiniAC starts (or restarts) after this resource
on("FiniAC:Started", registerHooks);

// FiniAC was already running when this resource started
if (GetConvarInt("FiniAC:Started", 0) === 1) {
  registerHooks();
}

Custom Detection Command ​

The fini detect command allows you to trigger FiniAC detections from your own server resources. This is useful for integrating custom anti-cheat logic, detecting exploits in your scripts, or flagging suspicious behavior that your code detects.

Command Syntax ​

bash
fini detect <source> <message>

Parameters:

  • source (string|number): The player's source ID or license identifier
  • message (string): Description of the detection. Use quotes if it contains spaces.

How It Works ​

  1. Your resource detects suspicious behavior (e.g., plate swapping, inventory exploits)
  2. You execute fini detect with the player source and a description
  3. FiniAC creates a CommandDetection log entry
  4. Your configured triggers can automatically ban, kick, or send webhooks based on the detection

Setting Up Permissions ​

Resources must have permission to execute the fini command. Add this to your server.cfg:

bash
# Replace 'YourResourceName' with your actual resource name
add_ace resource.YourResourceName command.fini allow

Examples:

bash
add_ace resource.ox_inventory command.fini allow
add_ace resource.qb-smallresources command.fini allow
add_ace resource.my_anticheat command.fini allow

Usage Examples ​

Basic Detection ​

lua
-- Trigger a simple detection
local playerId = 5
ExecuteCommand(('fini detect %s "Suspicious behavior detected"'):format(playerId))

-- With spaces in the message (must use quotes)
ExecuteCommand(('fini detect %s "Player is plate swapping"'):format(playerId))
typescript
// Trigger a simple detection
const playerId: number = 5;
ExecuteCommand(`fini detect ${playerId} "Suspicious behavior detected"`);

// With spaces in the message (must use quotes)
ExecuteCommand(`fini detect ${playerId} "Player is plate swapping"`);

Detection from Client Event ​

This example shows how to trigger a detection when your server receives an event from a client, such as your custom anti-cheat detecting suspicious behavior:

lua
-- Server-side: Listen for suspicious activity from your client-side anticheat
RegisterServerEvent("myanticheat:suspiciousActivity")
AddEventHandler("myanticheat:suspiciousActivity", function(detectionType, detectionData)
    local src = source -- The player who triggered the event
    local playerName = GetPlayerName(src)

    -- Log the detection with FiniAC
    local message = ("%s - %s - %s"):format(playerName, detectionType, detectionData)
    ExecuteCommand(('fini detect %s "%s"'):format(src, message))
end)
typescript
// Server-side: Listen for suspicious activity from your client-side anticheat
onNet(
  "myanticheat:suspiciousActivity",
  (detectionType: string, detectionData: string) => {
    const src = (global as any).source; // The player who triggered the event
    const playerName: string = GetPlayerName(src.toString());

    // Log the detection with FiniAC
    const message = `${playerName} - ${detectionType} - ${detectionData}`;
    ExecuteCommand(`fini detect ${src} "${message}"`);
  },
);

Client-side code example:

lua
-- Client-side: Detect suspicious activity and notify server
function DetectSuspiciousActivity(detectionType, detectionData)
    TriggerServerEvent("myanticheat:suspiciousActivity", detectionType, detectionData)
end

-- Example usage
if playerIsUsingInvalidWeapon then
    DetectSuspiciousActivity("InvalidWeapon", "Player has blacklisted weapon")
end
typescript
// Client-side: Detect suspicious activity and notify server
function detectSuspiciousActivity(
  detectionType: string,
  detectionData: string,
): void {
  emitNet("myanticheat:suspiciousActivity", detectionType, detectionData);
}

// Example usage
if (playerIsUsingInvalidWeapon) {
  detectSuspiciousActivity("InvalidWeapon", "Player has blacklisted weapon");
}

Custom detections appear in the FiniAC web panel under the Logs section with the type CommandDetection and the message containing your custom detection message.

Troubleshooting ​

Command Not Working ​

Problem: The fini detect command doesn't seem to trigger any detections.

Solutions:

  1. Verify ACE permissions are set correctly in server.cfg:
    bash
    add_ace resource.YourResourceName command.fini allow
  2. Check that FiniAC is running and initialized
  3. Verify the source ID is valid and the player is connected
  4. Check server console for any error messages

Message Formatting Issues ​

Problem: The detection message appears malformed or truncated.

Solutions:

  1. Always wrap messages containing spaces in quotes
  2. Escape special characters (quotes, backslashes) in your message
  3. Keep messages under 1000 characters for best results
  4. Avoid using newlines or special formatting in messages
lua
-- ❌ Wrong - no quotes around message with spaces
ExecuteCommand('fini detect ' .. src .. ' This will not work')

-- ✅ Correct - quotes around message
ExecuteCommand(('fini detect %s "This will work"'):format(src))

-- ✅ Correct - escaping quotes in message
local message = 'Player said "hello"'
ExecuteCommand(('fini detect %s "%s"'):format(src, message:gsub('"', '\\"')))
typescript
// ❌ Wrong - no quotes around message with spaces
ExecuteCommand(`fini detect ${src} This will not work`);

// ✅ Correct - quotes around message
ExecuteCommand(`fini detect ${src} "This will work"`);

// ✅ Correct - escaping quotes in message
const message = 'Player said "hello"';
ExecuteCommand(`fini detect ${src} "${message.replace(/"/g, '\\"')}"`);