> ## Documentation Index
> Fetch the complete documentation index at: https://docs.technified.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Moderation

> Check ban and mute status, and manage Roblox punishments from a game server.

These endpoints let a Roblox game server check whether a player is banned or muted, and create or lift Roblox punishments that sync back to the dashboard. The guild ID lives in the path and must match your API key's guild.

<Note>
  The plain `/moderation/:guildId/ban`, `/unban`, `/mute`, and `/bans/active` routes are dashboard-only. With a guild API key, use the `roblox/` variants documented here.
</Note>

<Info>
  Read endpoints (`ban-status`, `mute-status`, `bans/active`, `mutes/active`, `logs`) require the `moderation:read` scope. Write endpoints (`roblox/ban`, `roblox/unban`, `roblox/mute`, `roblox/unmute`) require `moderation:write`. See [Scopes](/developer-api/introduction#scopes).
</Info>

## Check ban status

The high-frequency check a game server runs when a player joins. Responses are cached briefly for performance.

```http theme={null}
GET /moderation/:guildId/ban-status/:identifier
```

<ParamField path="guildId" type="string" required>
  Your guild ID. Must match the key's guild.
</ParamField>

<ParamField path="identifier" type="number" required>
  The Roblox user ID to check.
</ParamField>

<ParamField query="place_id" type="number">
  The Roblox place ID. Lets the check honour place-specific bans in addition to global ones.
</ParamField>

<ParamField query="no_cache" type="boolean">
  Set to `true` to bypass the cache and read live.
</ParamField>

<ResponseField name="data.banned" type="boolean" />

<ResponseField name="data.ban_info" type="object">
  Present only when `banned` is `true`.

  <Expandable title="properties">
    <ResponseField name="reason" type="string" />

    <ResponseField name="banned_by" type="string" />

    <ResponseField name="created_at" type="number" />

    <ResponseField name="expires_at" type="number | null" />

    <ResponseField name="is_permanent" type="boolean" />
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json Banned theme={null}
  {
    "status": "success",
    "code": 200,
    "message": "User is banned",
    "data": {
      "banned": true,
      "ban_info": {
        "reason": "Exploiting",
        "banned_by": "987654321",
        "created_at": 1746712921,
        "expires_at": null,
        "is_permanent": true
      }
    }
  }
  ```

  ```json Not banned theme={null}
  {
    "status": "success",
    "code": 200,
    "message": "User is not banned",
    "data": { "banned": false }
  }
  ```
</ResponseExample>

## Check mute status

```http theme={null}
GET /moderation/:guildId/mute-status/:identifier
```

Same path params and query options as the ban check. The response uses `muted` and `mute_info` (with `muted_by` instead of `banned_by`).

<ResponseExample>
  ```json Muted theme={null}
  {
    "status": "success",
    "code": 200,
    "message": "User is muted",
    "data": {
      "muted": true,
      "mute_info": {
        "reason": "Spam",
        "muted_by": "987654321",
        "created_at": 1746712921,
        "expires_at": 1746799321,
        "is_permanent": false
      }
    }
  }
  ```
</ResponseExample>

## Create a ban

Bans a Roblox user. The ban syncs to your connected games and appears in the dashboard.

```http theme={null}
POST /moderation/:guildId/roblox/ban
```

<ParamField path="guildId" type="string" required />

<ParamField body="roblox_id" type="string" required>
  The Roblox user ID to ban.
</ParamField>

<ParamField body="reason" type="string" required />

<ParamField body="moderator_roblox_id" type="string" required>
  The Roblox ID of the staff member issuing the ban.
</ParamField>

<ParamField body="username" type="string">
  The target's Roblox username, for nicer logs.
</ParamField>

<ParamField body="moderator_username" type="string" />

<ParamField body="duration" type="string">
  A duration like `5m`, `1h`, `7d`, or `permanent`. Omit for a permanent ban.
</ParamField>

<ParamField body="is_global" type="boolean" default="true">
  Whether the ban applies across all games or only the source place.
</ParamField>

<ParamField body="source_server_id" type="string">
  The `game.JobId` of the server issuing the ban, used to prevent echo loops.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.technified.xyz/api/v1/moderation/$GUILD_ID/roblox/ban" \
    -H "X-API-Key: $TECHNIFIED_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "roblox_id": "261",
      "username": "Shedletsky",
      "reason": "Exploiting",
      "duration": "7d",
      "moderator_roblox_id": "1",
      "moderator_username": "Builderman"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "status": "success",
    "code": 200,
    "message": "User banned successfully via game server",
    "data": {
      "banned": true,
      "expires_at": 1747317721,
      "is_permanent": false
    }
  }
  ```
</ResponseExample>

<Warning>
  A second ban on an already-banned user returns `400`. Check status first if you are unsure.
</Warning>

## Lift a ban

```http theme={null}
POST /moderation/:guildId/roblox/unban
```

<ParamField path="guildId" type="string" required />

<ParamField body="roblox_id" type="string" required />

<ParamField body="moderator_roblox_id" type="string" required />

<ParamField body="reason" type="string">
  Defaults to "Unbanned via game server".
</ParamField>

<ParamField body="source_server_id" type="string" />

Returns `404` if the user has no active ban.

## Create or lift a mute

Mutes mirror bans exactly. Use the same fields.

```http theme={null}
POST /moderation/:guildId/roblox/mute
POST /moderation/:guildId/roblox/unmute
```

`mute` accepts the same body as `ban` (`roblox_id`, `reason`, `duration`, `moderator_roblox_id`, and so on). `unmute` accepts the same body as `unban`.

## List active punishments

Fetch every active ban or mute for the guild, for example to seed an admin system on startup.

```http theme={null}
GET /moderation/:guildId/roblox/bans/active
GET /moderation/:guildId/roblox/mutes/active
```

<ResponseField name="data.bans" type="array">
  The list of active ban records. The mutes endpoint returns `data.mutes`.
</ResponseField>

<ResponseField name="data.total" type="number" />

<ResponseExample>
  ```json 200 theme={null}
  {
    "status": "success",
    "code": 200,
    "message": "Active bans retrieved",
    "data": {
      "bans": [
        {
          "id": 42,
          "identifier": "261",
          "reason": "Exploiting",
          "banned_by": "1",
          "expires_at": null,
          "is_active": 1
        }
      ],
      "total": 1
    }
  }
  ```
</ResponseExample>

## Read moderation logs

Read the guild's moderation log history, for example to mirror punishments into your own audit tooling. Requires the `moderation:read` scope.

```http theme={null}
GET /guilds/:guildId/moderation/logs
```

<ParamField path="guildId" type="string" required>
  Your guild ID. Must match the key's guild.
</ParamField>

<ParamField query="limit" type="number">
  Maximum number of log entries to return.
</ParamField>

<ParamField query="offset" type="number">
  Number of entries to skip, for pagination.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.technified.xyz/api/v1/guilds/$GUILD_ID/moderation/logs?limit=50" \
    -H "X-API-Key: $TECHNIFIED_API_KEY"
  ```
</RequestExample>

<Note>
  This is the API-key-accessible counterpart to the dashboard's moderation log view. The bare `/moderation/:guildId/logs` route remains dashboard-only.
</Note>
