Skip to content
Ice edited this page Mar 7, 2025 · 11 revisions

GFLBans API for Plugins

This document contains the necessary information to implement a basic GFLBans plugin.

The GFLBans API can be found at the /api/ route of the main instance. For example, if your instance is hosted at bans.gflclan.com, the api would be at bans.gflclan.com/api/

The following sub-routes make up the API:

Route Description
/infractions All infraction related operations
/gs Misc. routes useful to the GFLBans plugin
/rpc Routes that can be used by the GFLBans plugin to receive data from GFLBans

There are other sub-routes, but they aren't useful for the plugin.


Authentication

All API requests should be authenticated using the Authorization header.

Authorization: SERVER SERVER_ID SERVER_KEY

SERVER_ID and SERVER_KEY are user supplied parameters. Make sure the user can configure them.


Common structures

These structs are referenced throughout this document and are provided here to avoid repetition

class PlayerObjIPOptional:
    gs_service: str
    gs_id: str
    ip: Optional[str]

class PlayerObjNoIp(BaseModel):
    gs_service: str
    gs_id: str

class PlayerObjSimple(BaseModel):
    gs_service: str
    gs_id: str
    ip: Optional[str]

class CInfractionSummary:
    expiration: Optional[PositiveInt]  # Unix timestamp
    reason: str
    admin_name: str

# SEND ONLY ONE OF THESE FIELDS
class Initiator:
    ips_id: Optional[PositiveInt]  # Steam32 ID
    mongo_id: Optional[str]        # Internal database id
    gs_admin: Optional[PlayerObjNoIp]

class Infraction:
    id: Optional[str]
    flags: int
    comments: List[Comment]
    files: List[FileInfo] = []
    server: Optional[str]
    created: int
    expires: Optional[PositiveIntIncl0]
    player: PlayerObj
    reason: constr(min_length=1, max_length=280)
    admin: Optional[PositiveIntIncl0]

    # Removed data
    removed_on: Optional[PositiveIntIncl0]
    removed_by: Optional[PositiveIntIncl0]
    removal_reason: Optional[constr(min_length=1, max_length=280)]

    # For DEC_ONLINE_ONLY
    time_left: Optional[PositiveIntIncl0]
    orig_length: Optional[PositiveIntIncl0]

    # For tiering policy
    policy_id: Optional[str]

    # When was the last time a heartbeat caused this to be updated?
    last_heartbeat: Optional[PositiveIntIncl0]

class MessageLog(BaseModel):
    user: PlayerObj
    content: constr(min_length=1, max_length=256)
    created: int # UNIX timestamp

Heartbeat

The GFLBans plugin should periodically send data to GFLBans so that GFLBans can:

  • Know if the server is alive
  • Update infractions that only decrement while the player is online
  • Display information about the server in the Web UI

The heartbeat route is present at /api/gs/heartbeat and should be POSTed with JSON data containing the following parameters:

Field Type Description
hostname constr(max_length=96) The hostname of the server. Type hostname in console
max_slots int Max players of the server. Usually using GetMaxHumanPlayers() will work
players List[PlayerObjIpOptional] Self explanatory. A json array of players that follows PlayerObjIPOptional class
messages Optional[List[MessageLog]] A json array of chat messages for message logging
operating_system str Windows or Linux
mod str The name of the game folder (cs2 for CS2, garrysmod for GMod)
map str The current map
locked bool = False Whether sv_password is set so that only people with the password can join
include_other_servers bool = True If the plugin configuration wants to accept GLOBAL GFL bans

Example:

{
    "hostname":"Test Server",
    "max_slots":64,
    "players": [
        {
            "gs_service": "steam",
            "gs_id": "76561198041538434"
        }
    ],
    "messages": [
        {
            "user": {
                "gs_service": "steam",
                "gs_id": "76561198041538434",
                "ip": "127.0.0.1"
            },
            "content": "Test Message",
            "created": "1736311320"
        }
    ],
    "operating_system": "windows",
    "mod": "cs2",
    "map": "test_map",
    "include_other_servers": false
}

The API will reply with a list of HeartbeatChange objects:

class HeartbeatChange:
    player: PlayerObjNoIp
    check: CheckInfractionsReply

class CheckInfractionsReply:
    voice_block: Optional[CInfractionSummary]
    chat_block: Optional[CInfractionSummary]
    ban: Optional[CInfractionSummary]
    admin_chat_block: Optional[CInfractionSummary]
    call_admin_block: Optional[CInfractionSummary]
    item_block: Optional[CInfractionSummary]

class CInfractionSummary:
    expiration: Optional[PositiveInt]  # Unix timestamp
    reason: str
    admin_name: str

Using that information, you should update the user's local state and, if necessary, kick them. You can omit call_admin_blocks from the local state since GFLBans enforces them server side.

It is recommended that a heartbeat is sent each minute, though servers can get away with doing as much as 10 minutes in between beats.


RPC Events

GFLBans uses RPC events to tell the server to do things and to inform it of changes. RPC events can be retrieved by polling /api/rpc/poll or sent as they become available by listening on Websocket /api/rpc/ws

RPC event responses have the generic form

class RPCEventBase:
    event_id: str
    time: datetime
    event: str

The event_id field is a unique id that GFLBans can use to identify events. The time is the time the event was created. The event is the event type.

Each event type has it's own fields.

Here are the currently implemented events:

#event: player_updated
class RPCPlayerUpdated(RPCEventBase):
    target_type: constr(regex=r'^(player|ip)$')  # ip or player. If player, then target is a PlayerObjNoIp, else string
    target: Union[PlayerObjNoIp, str]

    local: CheckInfractionsReply  # See definition in the heartbeat section
    glob: CheckInfractionsReply

The player_updated event is sent when the server wants to push new state to the game server. You should use this information to update the user's local state and, if necessary, kick them. The local field should be used if the server administrator has configured the server to ignore global bans, otherwise the glob field should be used.

#event: player_kick
class RPCKick(RPCEventBase):
    target_player: PlayerObjNoIp

If the player is present in-game, kick them.


Checking player infractions

GFLBans will send events to the server, however, when a player first joins, you should check a player's infractions to establish their initial state.

To retrieve a player's information, send a GET request to /api/infractions/check

The following parameters are accepted:

Parameter Type Description
gs_service str The service component of the player object, usually steam
gs_id str The id component of the player object. For steam, this is their steamid64
ip Optional[str] The IP address of the player
include_other_servers bool = True Whether or not to accept global infractions issued on other servers. Should be configurable by the manager

Example:

{
    "gs_service": "steam",
    "gs_id": "76561198041538434",
    "ip": "127.0.0.1",
    "include_other_servers": true
}

GFLBans will reply with an object like:

class CheckInfractionsReply:
    voice_block: Optional[CInfractionSummary]
    chat_block: Optional[CInfractionSummary]
    ban: Optional[CInfractionSummary]
    admin_chat_block: Optional[CInfractionSummary]
    call_admin_block: Optional[CInfractionSummary]
    item_block: Optional[CInfractionSummary]

class CInfractionSummary:
    expiration: Optional[PositiveInt]  # Unix timestamp
    reason: str
    admin_name: str

You should use this to reject the players connection / kick them if they are banned, or apply other restrictions.

For the sake of being user friendly, it is recommended that you inform the player of the admin that banned, muted, or gagged them and the reason for which they were punished.

It is unnecessary to take action for call_admin_block as that is handled by GFLBans web. Furthermore, admin_chat_block and item_block can be ignored if there is no admin chat or item tracking function.


Utilising VPN features (Temporarily Disabled)

You can use GFLBans' built in VPN detection by sending a GET request to /api/gs/vpn on player join. The route accepts the following parameters:

Parameter Type Description
gs_service str The service component of the player object, usually steam
gs_id str The id component of the player object. For steam, this is their steamid64
ip str The player's ip address

GFLBans will reply with something like this

class CheckVPNReply:
    is_vpn: bool
    is_cloud_gaming: bool
    is_immune: bool

The plugin is recommended to inform admins about a player joining with a vpn if is_vpn is true.

Depending on plugin configuration, the server may also kick them. If is_immune is set, the player should not be kicked.

The server may choose how to handle is_cloud_gaming depending on whether or not they want to allow or kick cloud gaming services.


Calling an admin

You can utilise GFLBans' implementation of call admin by sending a POST request to /api/gs/calladmin with JSON data containing the following parameters.

Parameter Type Description
caller PlayerObjNoIp The player issuing the report/admin call
caller_name str The name of the player issuing the report/admin call
include_other_servers bool = False Should be true if server is accepting global bans, and false if ignoring them
message constr(min_length=1, max_length=120) Reason that the player has given for the report/admin call
cooldown PositiveInt = 600 The minimum amount of seconds since the previous report/admin call in order for this one to go through
report_target Optional[PlayerObjNoIp] The player being reported
report_target_name Optional[str] The name of the player being reported

Example:

{
    "caller": {
        "gs_service": "steam",
        "gs_id": "76561198041538434"
    },
    "caller_name": "Ice",
    "message": "Meanie",
    "cooldown": 600,
    "report_target": {
        "gs_service": "steam",
        "gs_id": "76561198122734332"
    },
    "report_target_name": "Aurora"
}

Specifying report_target and report_target_name will change the admin call embed into a report embed.

GFLBans will send a reply that looks like this:

class ExecuteCallAdminReply:
    sent: bool
    is_banned: bool
    cooldown: Optional[PositiveIntIncl0]

The player should be informed whether an admin was called. If it failed, they should be informed of the reason why (if they are banned from using the feature or how long of a cooldown remains).


Standard Infractions

To make one of these infractions, POST to /api/infractions/ with JSON data containing the following parameters:

Field Type Description
duration Optional[PositiveInt] The length of the infraction in seconds. OMIT entirely for a permanent length
player PlayerObjSimple Self explanatory. The player object
admin Optional[Initiator] The admin whom initiated the infraction. OMIT for console
reason constr(min_length=1, max_length=280) The reason behind the infraction
punishments List[constr(regex=valid_types_regex)] Valid punishments are voice_block, chat_block, ban, admin_chat_block, call_admin_block, item_block
scope constr(regex=r'^(server|global)$') global infractions apply to all game servers except those explicitly opting out
session bool = False Behaves just like in sourcebans when no length is specified. The infraction is set to expire instantly on the web site and the game server should maintain it throughout the map
dec_online_only bool = False Whether the infraction only decreases while the player is connected to the server. Doesn't work for ban

Example:

{
    "player": {
        "gs_service": "steam",
        "gs_id": "76561198041538434"
    },
    "admin": {
        "gs_admin": {
            "gs_service": "steam",
            "gs_id": "76561198041538434"
        }
    },
    "reason": "test mute + ban",
    "punishments": [
        "voice_block",
        "ban"
    ],
    "scope": "global"
}

GFLBans will reply with the new Infraction object. You should apply the new restrictions yourself, however GFLBans will also push new state to the server using the RPC system


Removing infractions

GFLBans supports removing infractions by ID, but often admins want to remove all infractions applying to a player when they execute an in-game unban, so GFLBans supports this

To do this, you can send a POST request to /api/infractions/remove with JSON data containing the following parameters:

Field Type Description
player PlayerObjNoIp Self explanatory. The player object
remove_reason constr(min_length=1, max_length=280) The infraction removal reason
admin Optional[Initiator] The admin that removed the infraction. OMIT for console
include_other_servers bool = True Remove global bans or server bans
restrict_types Optional[List[constr(regex=valid_types_regex)]] Valid types are voice_block, chat_block, ban, admin_chat_block, call_admin_block, item_block

Example:

{
    "player": {
        "gs_service": "steam",
        "gs_id": "76561198041538434"
    },
    "admin": {
        "gs_admin": {
            "gs_service": "steam",
            "gs_id": "76561198041538434"
        }
    },
    "remove_reason": "test unmute + unban",
    "restrict_types": [
        "voice_block",
        "ban"
    ],
    "include_other_servers": true
}

GFLBans will reply with:

class RemoveInfractionsOfPlayerReply(BaseModel):
    num_removed: PositiveIntIncl0
    num_considered: PositiveIntIncl0
    num_not_removed: PositiveIntIncl0

You should invalidate any local state and either:

  1. Wait for GFLBans to push new state via RPC
  2. Send a new check request

Getting infractions stats

GFLBans allows you to get the info about past and current infractions applied to a player.

To do this, you can send a GET request to /api/infractions/stats

The following parameters are accepted:

Parameter Type Description
gs_service str The service component of the player object, usually steam
gs_id str The id component of the player object. For steam, this is their steamid64
ip Optional[str] The IP address of the player
include_other_servers bool = True Whether or not to include global infractions issued on other servers. Should be configurable by the manager
active_only bool = True If true, calculate stats only with infractions currently active on the player.
exclude_removed bool = False If true, will not include manually removed infractions in stats. Does not affect automatically removed (expired) infractions.
online_only bool = False If true, calculate stats only with playtime based infractions (DECL_ONLINE_ONLY).
count_only bool = True If true, only return total numbers of infractions. If false, include longest duration of each infraction type (if at least 1 infraction of that type).

Example:

{
    "gs_service": "steam",
    "gs_id": "76561198041538434",
    "exclude_removed": true,
    "online_only": true,
    "count_only": false
}

GFLBans will reply with:

class InfractionStatisticsReply(BaseModel):
    voice_block_count: PositiveIntIncl0
    voice_block_longest: Optional[PositiveIntIncl0]
    text_block_count: PositiveIntIncl0
    text_block_longest: Optional[PositiveIntIncl0]
    ban_count: PositiveIntIncl0
    ban_longest: Optional[PositiveIntIncl0]
    admin_chat_block_count: PositiveIntIncl0
    admin_chat_block_longest: Optional[PositiveIntIncl0]
    call_admin_block_count: PositiveIntIncl0
    call_admin_block_longest: Optional[PositiveIntIncl0]
    item_block_count: PositiveIntIncl0
    item_block_longest: Optional[PositiveIntIncl0]
    warning_count: PositiveIntIncl0
    warning_longest: Optional[PositiveIntIncl0]

(DEPRECATED) Creating an infraction using tiering policies

This feature is deprecated. It is recommended to use standard infractions, as this feature may be removed in the future.

If you want to implement https://gflclan.com/forums/topic/68218-new-punishment-system-for-gflbans-03-beta/, these API routes will let you do it.

The first component of this is registering an infraction template with GFLBans. This should be done before any bans would be made, probably during plugin init.

The infraction template should only be registered once. You should check to see if you have a policy_id for the offense code before trying again.

You can register the template by POSTing to /api/infractions/register_policy

The following data should be posted:

class RegisterInfractionTieringPolicy:
    name: str
    server: Optional[str]
    include_other_servers: bool = True
    tier_ttl: int  # How long an infraction counts for tiering purposes
    default_reason: constr(min_length=1, max_length=280)
    tiers: List[InfractionTieringPolicyTier]

class InfractionTieringPolicyTier:
    punishments: List[constr(regex=valid_types_regex)]
    duration: Optional[conint(gt=0)]
    dec_online: bool = False

name is just a user friendly name of the policy. server is the SERVER_ID (same one as you stick in the auth header) include_other_servers is whether or not the server is accepting globals punishments should be one of voice_block, chat_block, ban, admin_chat_block, call_admin_block, or item_block duration is the length of the infraction in seconds. OMIT entirely for permanent. dec_online is whether or not the infraction only decreases while the player is connected to the server. Doesn't work with ban

GFLBans will reply with

class RegisterInfractionTieringPolicyReply:
    policy_id: str

policy_id is a unique id of the newly created tiering policy that GFLBans uses to identify the saved configuration.

You should save this in local storage (SQLite, config file, etc) and associate it with the offense code as laid out in the above forum post.

When you'd actually like to ban a player, you can POST to /api/infractions/using_policy with the following:

class CreateInfractionUsingPolicy(BaseModel):
    player: PlayerObjSimple
    admin: Optional[Initiator]
    reason: Optional[constr(min_length=1, max_length=280)]
    scope: constr(regex=r'^(server|global)$')
    policy_id: str
    consider_other_policies: List[str] = []

GFLBans will reply with the new Infraction object. You should apply the new restrictions yourself, however GFLBans will also push new state to the server using the RPC system

To implement the revocation code component of the spec, it is recommended that you store the id of the infraction in memory and associate it with a short, randomly generated code.

Should an admin use the revocation code, you should send a PATCH request to /api/infractions/INFRACTION_ID_HERE with the following content

{
  "admin": {
    "gs_admin": {
      "gs_service": "steam",
      "gs_id": "steamidhere"
    }
  },
  "set_removal_state": true,
  "removed_by": {
    "gs_admin": {
      "gs_service": "steam",
      "gs_id": "steamidhere"
    }
  },
  "removal_reason": "Revoked in-game using the revocation code."
}

GFLBans will reply with the altered infraction, but you can probably just ignore it. The revocation code should be removed once it is used.


If the plugin should notice that a tiering policy is no longer referenced, it is recommended to unlink the server from the tiering policy to prevent it from cluttering up the WebUI.

class UnlinkInfractionTieringPolicy(BaseModel):
    policy_id: str

GFLBans will reply with a 204 once the policy has been unlinked. The server should delete the stored policy ID once it has been unlinked.