> For the complete documentation index, see [llms.txt](https://adaptix-framework.gitbook.io/adaptix-framework/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://adaptix-framework.gitbook.io/adaptix-framework/development/teamserver-interface/server-web-api.md).

# Web API

AdaptixC2 Server exposes a REST API for managing the teamserver. The API runs over HTTPS and uses JSON for data transfer.

**Base URL:** `https://{host}:{port}{endpoint}`

* `{host}` — server IP address or domain
* `{port}` — teamserver port
* `{endpoint}` — base path defined in the profile configuration

{% hint style="info" %}
If authentication fails, the web server will respond with a 404 page by default.
{% endhint %}

## Authentication

The API uses JWT (JSON Web Tokens) for authentication. To obtain tokens, log in via `/login`.

<details>

<summary><strong>POST /login</strong></summary>

Authenticate a user and obtain JWT tokens.

***

**Request Body:**

```json
{
  "username": "string",
  "password": "string",
  "version": "string"
}
```

| Field        | Type   | Required | Description       |
| ------------ | ------ | -------- | ----------------- |
| **username** | string | Yes      | Operator name     |
| **password** | string | Yes      | Operator password |
| **version**  | string | No       | Client version    |

***

**Response (200 OK):**

```json
{
  "access_token": "string",
  "refresh_token": "string",
  "version": "v2.0"
}
```

**Errors:**

* Invalid credentials — returns 404 (configurable error page)

</details>

<details>

<summary><strong>POST /</strong>refresh</summary>

Refresh the access token using the refresh token.

***

**Request**&#x20;

Send the refresh token in the header:

```
Authorization: Bearer <refresh_token>
```

***

**Response (200 OK):**

```json
{
  "access_token": "string"
}
```

</details>

## OTP

OTP (One-Time Password) — one-time tokens with a short TTL (60 sec). Used for:

* Secure file transfers (upload/download)
* Establishing WebSocket connections (`/connect`)
* Creating WebSocket channels (`/channel`)

Every OTP route reads the token from the query parameter `?otp=`.&#x20;

* **TTL**: OTP token is valid for 60 seconds after generation
* **One-time use**: Each token can be used only once
* **Type isolation**: A `connect` token cannot be used on `/channel` and vice versa
* **Security**: The token contains no data — just a random 256-bit identifier

<details>

<summary><strong>POST /</strong>otp/generate</summary>

Generate a one-time token. Requires an Access Token.

***

**Request (JSON)**

Universal request format:

```json
{
  "type": "string",
  "data": { ... }
}
```

<table><thead><tr><th width="95">Field</th><th width="115">Type</th><th width="176">Required</th><th>Description</th></tr></thead><tbody><tr><td><strong>type</strong></td><td>string</td><td>Yes</td><td>OTP type (see below)</td></tr><tr><td><strong>data</strong></td><td>object</td><td>Depends on type</td><td>Type-specific JSON data</td></tr></tbody></table>

***

**Response (200 OK):**

```json
{
  "ok": true,
  "message": "<otp_token>"
}
```

**Response (error):**

```json
{
  "ok": false,
  "message": "error description"
}
```

</details>

<details>

<summary>connect</summary>

Client WebSocket connection

```json
{
  "type": "connect",
  "data": {
    "client_type": 1,
    "console_team_mode": false,
    "subscriptions": ["agents", "listeners"]
  }
}
```

<table><thead><tr><th width="194">data field</th><th width="98">Type</th><th width="94">Default</th><th>Description</th></tr></thead><tbody><tr><td><strong>client_type</strong></td><td>uint8</td><td>1 (UI)</td><td>Client type: 1=UI, 2=WEB, 3=CLI</td></tr><tr><td><strong>console_team_mode</strong></td><td>bool</td><td>false</td><td>Team-console mode</td></tr><tr><td><strong>subscriptions</strong></td><td>string[]</td><td>[]</td><td>Subscription categories</td></tr></tbody></table>

</details>

<details>

<summary>channel_tunnel</summary>

WebSocket channel for a tunnel

```json
{
  "type": "channel_tunnel",
  "data": {
    "tunnel_id": 1,
    "channel_id": 2,
    "mode": "tcp|udp",
    "host": "string",
    "port": "string"
  }
}
```

<table><thead><tr><th width="128">data field</th><th width="104">Type</th><th width="153">Required</th><th>Description</th></tr></thead><tbody><tr><td><strong>tunnel_id</strong></td><td>int64</td><td>Yes</td><td>Tunnel ID</td></tr><tr><td><strong>channel_id</strong></td><td>int64</td><td>Yes</td><td>Channel ID</td></tr><tr><td><strong>mode</strong></td><td>string</td><td>For SOCKS5</td><td>Mode: <code>tcp</code> or <code>udp</code></td></tr><tr><td><strong>host</strong></td><td>string</td><td>For SOCKS</td><td>Target host</td></tr><tr><td><strong>port</strong></td><td>string</td><td>For SOCKS</td><td>Target port (1-65535)</td></tr></tbody></table>

</details>

<details>

<summary>channel_terminal</summary>

WebSocket channel for a terminal

```json
{
  "type": "channel_terminal",
  "data": {
    "agent_id": 1,
    "terminal_id": 1,
    "program": "string",
    "size_h": 24,
    "size_w": 80,
    "oem_cp": 65001
  }
}
```

<table><thead><tr><th width="124">data field</th><th width="103">Type</th><th width="110">Required</th><th>Description</th></tr></thead><tbody><tr><td><strong>agent_id</strong></td><td>int64</td><td>Yes</td><td>Agent ID</td></tr><tr><td><strong>terminal_id</strong></td><td>int64</td><td>Yes</td><td>Terminal ID</td></tr><tr><td><strong>program</strong></td><td>string</td><td>Yes</td><td>Program to run</td></tr><tr><td><strong>size_h</strong></td><td>int</td><td>Yes</td><td>Terminal height (rows)</td></tr><tr><td><strong>size_w</strong></td><td>int</td><td>Yes</td><td>Terminal width (columns)</td></tr><tr><td><strong>oem_cp</strong></td><td>int</td><td>Yes</td><td>OEM Code Page</td></tr></tbody></table>

</details>

<details>

<summary>channel_agent_build</summary>

WebSocket channel for agent build

```json
{
  "type": "channel_agent_build",
  "data": {
    "agent_name": "string",
    "listeners_name": ["listener1", "listener2"]
  }
}
```

<table><thead><tr><th width="144">data field</th><th width="105">Type</th><th width="115">Required</th><th>Description</th></tr></thead><tbody><tr><td><strong>agent_name</strong></td><td>string</td><td>Yes</td><td>Agent name to build</td></tr><tr><td><strong>listeners_name</strong></td><td>string[]</td><td>Yes</td><td>List of listener names</td></tr></tbody></table>

</details>

<details>

<summary>download</summary>

File download

```json
{
  "type": "download",
  "data": {
    "id": 1
  }
}
```

<table><thead><tr><th width="108">data field</th><th width="92">Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>id</strong></td><td>int64</td><td>File id of a finished download</td></tr></tbody></table>

</details>

<details>

<summary>tmp_upload</summary>

Temporary file upload

```json
{
  "type": "tmp_upload",
  "data": {
    "id": 1
  }
}
```

<table><thead><tr><th width="113">data field</th><th width="76">Type</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>int64</td><td>Id of a temporary upload already registered on the server</td></tr></tbody></table>

</details>

**Full channel creation flow example (tunnel)**

1. Make sure the client is connected via `/connect`
2. POST `/otp/generate` (`Authorization: Bearer <access_token>`) Body: `{"type": "channel_tunnel", "data": {"tunnel_id": "...", "channel_id": "...", ...}}` → obtain otp\_token
3. GET `/channel?otp=<otp_token>` → WebSocket upgrade

<details>

<summary><strong>POST</strong> /otp/upload/temp?otp=</summary>

Upload bytes into a temporary upload that was already registered. The token type must be `tmp_upload`, and its `id` must still be in the server's temporary-upload map.

***

**Request**&#x20;

&#x20;`multipart/form-data`

<table><thead><tr><th width="86">Field</th><th width="130">Type</th><th>Description</th></tr></thead><tbody><tr><td>file</td><td>file</td><td>The file</td></tr></tbody></table>

***

**Response (200 OK):** an empty body.

A wrong token type, an unknown id, a missing `file` field, or a save error is a `404` text body with the error string. A bad or expired token is rejected by the OTP middleware before this handler runs.

</details>

<details>

<summary><strong>GET</strong> /otp/download/sync?otp=</summary>

Download a finished file. The token type must be `download`. The server returns the file only when that download's state is finished.

***

**Response (200 OK)**

the file bytes, with:

* `Content-Disposition: attachment; filename="<on-disk name>"`
* `Content-Type: application/octet-stream`
* `Content-Length: <size>`

The filename is the base name of the stored path, not a separate display name. If the id is unknown or the download is not finished, the response is `404` with a text error (`File not found: …` or `Download not finished`).

</details>

## Client Management

<details>

<summary><strong>POST /</strong>sync</summary>

Asks the server to push the current state to this operator's already open WebSocket. The handler does not read the body.&#x20;

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response (200 OK):**

```json
{
  "ok": true,
  "message": "sync started"
}
```

</details>

<details>

<summary><strong>POST</strong> /subscribe</summary>

Subscribe the client to event categories.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "categories": ["string"],
  "console_team_mode": true|false
}
```

<table><thead><tr><th width="194">Field</th><th width="108">Type</th><th width="90">Required</th><th>Description</th></tr></thead><tbody><tr><td><strong>categories</strong></td><td>string[]</td><td>Yes</td><td>List of categories to subscribe to</td></tr><tr><td><strong>console_team_mode</strong></td><td>boolean</td><td>No</td><td>Team-console mode</td></tr></tbody></table>

***

**Response (200 OK):**

```json
{
  "ok": true,
  "message": "subscribe started"
}
```

</details>

<details>

<summary><strong>GET</strong> /connect?otp=</summary>

Establish a WebSocket connection for real-time updates. Requires an OTP token of type `connect`.

***

**Response:** WebSocket upgrade

All connection parameters (client\_type, subscriptions, etc.) are passed when generating the OTP, not when connecting.

</details>

<details>

<summary><strong>GET</strong> /channel?otp=</summary>

Create a WebSocket channel. The token type must start with `channel_` (`channel_tunnel`, `channel_terminal`, or `channel_agent_build`). A token whose type is exactly `channel` is rejected.

***

**Response:** WebSocket upgrade

The channel type and initialization data are passed when generating the OTP, not when connecting.

</details>

## Listener

<details>

<summary><strong>GET /listener</strong>/list</summary>

Get the list of all listeners.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response (200 OK):**&#x20;

JSON array of `ListenerData`. An empty server returns `null`, not `[]`, because the handler marshals a nil slice.

```json
[
  {
    "l_name": "http-443",
    "l_reg_name": "BeaconHTTP",
    "l_protocol": "http",
    "l_type": "external",
    "l_bind_host": "0.0.0.0",
    "l_bind_port": "443",
    "l_agent_addr": "10.0.0.5:443",
    "a_create_time": 1718500000,
    "l_status": "Listen",
    "l_tags": "prod",
    "l_data": "{\"host_bind\":\"0.0.0.0\",\"port_bind\":\"443\"}",
    "l_watermark": "c04c4e57"
  }
]
```

</details>

<details>

<summary><strong>GET /listener</strong>/catalog</summary>

Registered listener **types** (not running instances).

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response (200 OK):**&#x20;

`axs` — the contents of `ax_config.axs` (the listener fields UI definition).

```json
[
  {
    "name": "GopherTCP",
    "protocol": "tcp",
    "type": "external",
    "axs": "function ListenerUI(mode_create) { ... }"
  }
]
```

</details>

<details>

<summary><strong>POST /</strong>listener/create</summary>

Create a new listener.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "string",
  "type": "string",
  "config": "string",
  "tags": "string"
}
```

<table><thead><tr><th width="93">Field</th><th width="99">Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>name</strong></td><td>string</td><td>Unique listener name (a-zA-Z0-9_-)</td></tr><tr><td><strong>type</strong></td><td>string</td><td>Listener type (depends on the extender)</td></tr><tr><td><strong>config</strong></td><td>string</td><td>JSON listener configuration</td></tr><tr><td><strong>tags</strong></td><td>string</td><td>Tags (free-form, e.g. <code>"prod,eu"</code>); optional</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": "Listener started"
}
```

</details>

<details>

<summary><strong>POST /</strong>listener/edit</summary>

Edit a listener configuration.

***

**Request**&#x20;

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "string",
  "type": "string",
  "config": "string",
  "tags": "string"
}
```

<table><thead><tr><th width="103">Field</th><th width="124">Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>name</strong></td><td>string</td><td>Listener name</td></tr><tr><td><strong>type</strong></td><td>string</td><td>Listener type</td></tr><tr><td><strong>config</strong></td><td>string</td><td>JSON listener configuration</td></tr><tr><td><strong>tags</strong></td><td>string</td><td>New tags (empty string clears); optional</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": "Listener Edited"
}
```

</details>

<details>

<summary><strong>POST /</strong>listener/stop</summary>

Stop and delete listener.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "string",
  "type": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Listener stopped"
}
```

</details>

<details>

<summary><strong>POST /</strong>listener/pause</summary>

Pause a listener.

***

**Request**&#x20;

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "string",
  "type": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Listener paused"
}
```

</details>

<details>

<summary><strong>POST /</strong>listener/resume</summary>

Resume a listener.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "string",
  "type": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Listener resumed"
}
```

</details>

<details>

<summary><strong>POST /</strong>listener/tags</summary>

Set arbitrary tags on a listener.&#x20;

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "string",
  "tags": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Agent

<details>

<summary><strong>GET /agent</strong>/list</summary>

Get the list of all agents.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response (JSON)**

```json
[
  {
    "a_crc": "beacon",
    "a_id": 12,
    "a_uid": "AQIDBAUGBwg=",
    "a_name": "beacon",
    "a_session_key": "AQIDBAUGBwgJCgsMDQ4PEA==",
    "a_listener": "http-443",
    "a_async": true,
    "a_external_ip": "203.0.113.10",
    "a_internal_ip": "10.0.0.20",
    "a_gmt_offset": 0,
    "a_sleep": 5,
    "a_jitter": 10,
    "a_pid": "4120",
    "a_tid": "512",
    "a_arch": "x64",
    "a_elevated": true,
    "a_process": "explorer.exe",
    "a_os": 1,
    "a_os_desc": "Windows 10",
    "a_domain": "CORP",
    "a_computer": "WS01",
    "a_username": "alice",
    "a_impersonated": "",
    "a_oemcp": 437,
    "a_acp": 1251,
    "a_create_time": 1718500000,
    "a_last_tick": 1718500100,
    "a_killdate": 0,
    "a_workingtime": 0,
    "a_tags": "finance",
    "a_mark": "",
    "a_color": "",
    "a_target": 0,
    "a_custom_data": null
  }
]
```

</details>

<details>

<summary><strong>GET /agent</strong>/catalog</summary>

Registered agent **types** (not live sessions).

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response (JSON)**

```json
[
  {
    "name": "gopher",
    "listeners": ["GopherTCP"],
    "fields": ["os", "arch", "format"],
    "choices": { "os": ["linux", "windows"], "arch": ["amd64"] }
  }
]
```

</details>

<details>

<summary><strong>GET</strong> /agent/generate</summary>

Generate an agent payload.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent": "string",
  "listener_name": ["string"],
  "config": "string",
  "save_to_store": true,
  "description": "string"
}
```

<table><thead><tr><th width="149">Field</th><th width="109">Type</th><th width="106">Required</th><th>Description</th></tr></thead><tbody><tr><td>agent</td><td>string</td><td>Yes</td><td>Agent type name</td></tr><tr><td>listener_name</td><td>string[]</td><td>Yes</td><td>List of listener names</td></tr><tr><td>config</td><td>string</td><td>Yes</td><td>JSON agent configuration</td></tr><tr><td>save_to_store</td><td>boolean</td><td>No</td><td>Register the payload in the Payload Store (default true)</td></tr><tr><td>description</td><td>string</td><td>No</td><td>Description for the Payload Store entry</td></tr></tbody></table>

***

**Response (JSON)**

```json
{
  "ok": true,
  "message": "<base64_filename>:<base64_content>"
}
```

`message` format: file name and content are base64-encoded and separated by a colon.

The client generates payloads through this endpoint (REST + Bearer token, 180-second timeout) when the WebSocket build-channel OTP is unavailable.

</details>

<details>

<summary><strong>POST /</strong>agent/remove</summary>

Remove agent data

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id_array": [1]
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/command/execute</summary>

Execute a command on an agent.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": 1,
  "ui": true|false,
  "cmdline": "string",
  "data": "string",
  "ax_hook_id": "string",
  "ax_handler_id": "string",
  "wait_answer": true|false
}
```

<table data-search="false"><thead><tr><th width="148">Field</th><th width="111">Type</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>int64</td><td>Agent ID</td></tr><tr><td>ui</td><td>boolean</td><td>Command from the UI</td></tr><tr><td>cmdline</td><td>string</td><td>Command line</td></tr><tr><td>data</td><td>string</td><td>JSON with command arguments</td></tr><tr><td>ax_hook_id</td><td>string</td><td>Hook ID (optional)</td></tr><tr><td>ax_handler_id</td><td>string</td><td>Handler ID (optional)</td></tr><tr><td>wait_answer</td><td>boolean</td><td>When true, the HTTP call waits until <code>TsAgentCommand</code> returns. The agent output is not included in this response</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/command/raw</summary>

Execute a raw command on an agent through the server-side AxScript engine. The command is parsed and executed on the server side without client-side hooks.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": 1,
  "cmdline": "string"
}
```

<table><thead><tr><th width="113">Field</th><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>int64</td><td>Agent ID</td></tr><tr><td>cmdline</td><td>string</td><td>Command line to execute</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>GET</strong> /agent/console/list</summary>

Cursor pagination of the console history for the specified agent. Returns messages with `Id < after_id` (newest first). When `around_id > 0` — loads N messages around the given ID (used to jump to context from search results).

***

**Request (JSON)**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="107.3499755859375">Parameter</th><th width="84.199951171875">Type</th><th width="101.5">Default</th><th>Description</th></tr></thead><tbody><tr><td><strong>agent_id</strong></td><td>int64</td><td>(required)</td><td>Agent ID (decimal)</td></tr><tr><td><strong>limit</strong></td><td>int</td><td>200</td><td>Max entries (cap 2000)</td></tr><tr><td><strong>after_id</strong></td><td>int64</td><td>0</td><td>Cursor: return messages with <code>Id &#x3C; after_id</code>. <code>0</code> = the newest ones.</td></tr><tr><td><strong>around_id</strong></td><td>int64</td><td>0</td><td>Load N messages around this ID (overrides <code>after_id</code>).</td></tr></tbody></table>

***

**Response (200 OK):**

```json
{
  "items": [
    {
      "time": 1718500000,
      "type": 105,
      "a_id": 12,
      "a_msg_type": 5,
      "a_message": "CORP\\alice",
      "a_text": "CORP\\alice"
    }
  ],
  "total": 1543,
  "oldest_id": 42,
  "has_more": true
}
```

* `total` — total number of records in the DB for this agent.
* `oldest_id` — ID of the oldest message in the response (used as the cursor for the next request).
* `has_more` — `true` if the DB has records older than `oldest_id`.

Each `items` element is the original sync packet, stored as it was sent over WebSocket. The example is `TYPE_AGENT_CONSOLE_OUT` (`type` 105). Other stored packets are `TYPE_AGENT_CONSOLE_TASK_SYNC` (106) and `TYPE_AGENT_CONSOLE_TASK_UPD` (107); those add `a_task_id`, `a_cmdline`, `a_client`, and completion fields.

**Errors (HTTP 400):**

* `agent_id is required` — parameter missing.
* `agent_id must be an integer` — invalid format.

</details>

<details>

<summary><strong>GET</strong> /agent/console/search</summary>

Full-text search over the agent console history. Searches the `a_cmdline`, `a_message`, `a_text`, `a_client` fields of the JSON packets.

***

**Request (JSON)**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="113.0833740234375">Parameter</th><th width="95.0999755859375">Type</th><th width="130.050048828125">Default</th><th>Description</th></tr></thead><tbody><tr><td><strong>agent_id</strong></td><td>int64</td><td>(required)</td><td>Agent ID</td></tr><tr><td><strong>q</strong></td><td>string</td><td>(required)</td><td>Search string</td></tr><tr><td><strong>limit</strong></td><td>int</td><td>50</td><td>Max results (cap 200)</td></tr><tr><td><strong>offset</strong></td><td>int</td><td>0</td><td>Result pagination offset</td></tr></tbody></table>

***

**Response (200 OK):**

```json
{
  "ok": true,
  "total": 3,
  "limit": 50,
  "offset": 0,
  "items": [
    {
      "id": 42,
      "snippet": "...matching text around the hit...",
      "packet": {
        "time": 1718500000,
        "type": 105,
        "a_id": 12,
        "a_msg_type": 5,
        "a_message": "CORP\\alice",
        "a_text": "CORP\\alice"
      }
    }
  ]
}
```

* `snippet` — highlighted text fragment (up to 200 characters) around the match.
* `packet` — the original JSON sync packet (valid JSON; invalid packets are skipped).

</details>

<details>

<summary><strong>POST /</strong>agent/set/tag</summary>

Set a tag on agents.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id_array": [1],
  "tag": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/set/mark</summary>

Set a mark on agents.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id_array": [1],
  "mark": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/set/color</summary>

Set the color of agents.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id_array": [1],
  "bc": "string",
  "fc": "string",
  "reset": true|false
}
```

<table><thead><tr><th width="156.75006103515625">Field</th><th width="122.800048828125">Type</th><th>Description</th></tr></thead><tbody><tr><td>agent_id_array</td><td>int64[]</td><td>Array of agent IDs</td></tr><tr><td>bc</td><td>string</td><td>Background color (hex or name)</td></tr><tr><td>fc</td><td>string</td><td>Foreground color (hex or name)</td></tr><tr><td>reset</td><td>boolean</td><td>Reset color to default</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/update/data</summary>

Update agent data.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "internal_ip": "string",
  "external_ip": "string",
  "gmt_offset": 0,
  "acp": 0,
  "oemcp": 0,
  "pid": "string",
  "tid": "string",
  "arch": "string",
  "elevated": true|false,
  "process": "string",
  "os": 0,
  "os_desc": "string",
  "domain": "string",
  "computer": "string",
  "username": "string",
  "impersonated": "string",
  "tags": "string",
  "mark": "string",
  "color": "string"
}
```

All fields except `agent_id` are optional. Send only the fields you want to update.

<table data-search="false"><thead><tr><th width="140.2666015625">Field</th><th width="125.35009765625">Type</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Agent ID (required)</td></tr><tr><td>internal_ip</td><td>string</td><td>Internal IP</td></tr><tr><td>external_ip</td><td>string</td><td>External IP</td></tr><tr><td>gmt_offset</td><td>int</td><td>GMT offset</td></tr><tr><td>acp</td><td>int</td><td>ANSI Code Page</td></tr><tr><td>oemcp</td><td>int</td><td>OEM Code Page</td></tr><tr><td>pid</td><td>string</td><td>Process ID</td></tr><tr><td>tid</td><td>string</td><td>Thread ID</td></tr><tr><td>arch</td><td>string</td><td>Architecture (x86/x64)</td></tr><tr><td>elevated</td><td>boolean</td><td>Elevated privileges</td></tr><tr><td>process</td><td>string</td><td>Process name</td></tr><tr><td>os</td><td>int</td><td>OS code</td></tr><tr><td>os_desc</td><td>string</td><td>OS description</td></tr><tr><td>domain</td><td>string</td><td>Domain</td></tr><tr><td>computer</td><td>string</td><td>Computer name</td></tr><tr><td>username</td><td>string</td><td>User name</td></tr><tr><td>impersonated</td><td>string</td><td>Impersonated user</td></tr><tr><td>tags</td><td>string</td><td>Tags</td></tr><tr><td>mark</td><td>string</td><td>Mark</td></tr><tr><td>color</td><td>string</td><td>Color (format: "bg-fg")</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>GET</strong> /agent/command_group/list?agent_id=</summary>

Command groups of an agent with their state (enabled/disabled). Groups are registered by the agent type's AxScript via `ax.create_commands_group`; overrides are stored per agent.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="120.9666748046875">Parameter</th><th width="105">Type</th><th width="138.25">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>agent_id</code></td><td><code>int64</code></td><td>(required)</td><td>Agent ID.</td></tr></tbody></table>

***

**Response (200 OK):**

```json
{
  "ok": true,
  "message": [
    {"name": "beacon", "description": "Beacon commands", "enabled": true},
    {"name": "bof", "description": "BOF commands", "enabled": false}
  ]
}
```

`message` — array of groups `{name, description, enabled}` (per-agent overrides applied).

</details>

<details>

<summary><strong>POST</strong> /agent/command_group/set</summary>

Enable/disable a command group for a specific agent. Disabled groups are hidden from the console autocomplete and filtered out by the `Commander`.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 42,
  "group": "bof",
  "enabled": false
}
```

<table><thead><tr><th width="116.61669921875">Field</th><th width="131.333251953125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>agent_id</code></td><td><code>int64</code></td><td>Agent ID.</td></tr><tr><td><code>group</code></td><td><code>string</code></td><td>Group name (required).</td></tr><tr><td><code>enabled</code></td><td><code>bool</code></td><td>New state.</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Groups

<details>

<summary><strong>GET</strong> /group/list</summary>

List of groups in the given scope.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

```json
[
  {
    "group_id": 1,
    "parent_group_id": 0,
    "group_name": "Domain Controllers",
    "scope": "agents",
    "members": [12, 34, 56]
  }
]
```

</details>

<details>

<summary><strong>POST</strong> /group/create</summary>

Create a new group.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "scope": "agents",
  "name": "Domain Controllers",
  "parent_id": 0
}
```

<table><thead><tr><th width="118.050048828125">Field</th><th width="111.2667236328125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>scope</code></td><td><code>string</code></td><td><code>"agents"</code> or <code>"tasks"</code>.</td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>Group name.</td></tr><tr><td><code>parent_id</code></td><td><code>int64</code></td><td>Parent ID or <code>0</code> for a root group.</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /group/rename</summary>

Rename a group.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "group_id": 1,
  "name": "New Name"
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /group/delete</summary>

Delete a group.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "group_id": 1
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /group/members</summary>

Change the membership of a group.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "group_id": 1,
  "add": [78, 90],
  "remove": [12]
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /group/move_member</summary>

Move an agent between groups

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 78,
  "from_group_id": 3,
  "to_group_id": 7
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /group/reparent</summary>

Move a group under a new parent.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "group_id": 5,
  "new_parent_id": 1
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Tasks

<details>

<summary><strong>GET /</strong>agent/task/list</summary>

Paginated list of agent tasks (or all agents) with filtering and sorting. Includes both running and completed tasks (only those with `Sync = true`). For still-running JOB tasks, the output (`Message` / `ClearText` / `MessageType`) is overlaid with the accumulated live output from `RunningJobs` — progress is visible before the task finishes.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table data-search="false"><thead><tr><th width="123.83331298828125">Parameter</th><th width="97.550048828125">Type</th><th width="139.5333251953125">Default</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>omitted (all)</td><td>Agent ID. Omit it, or leave it empty, to list every agent. A non-integer value is rejected</td></tr><tr><td>completed</td><td>string</td><td>omitted</td><td><code>1</code>/<code>true</code> or <code>0</code>/<code>false</code>. Omit it to return both running and finished tasks</td></tr><tr><td>limit</td><td>int</td><td>100</td><td>Max entries (cap 1000)</td></tr><tr><td>offset</td><td>int</td><td>0</td><td>Pagination offset</td></tr><tr><td>q</td><td>string</td><td>""</td><td>Filter expression (see below)</td></tr><tr><td>sort</td><td>string</td><td>"StartDate"</td><td>Sort column</td></tr><tr><td>order</td><td>string</td><td>"desc"</td><td>Direction: <code>asc</code> or <code>desc</code></td></tr></tbody></table>

**Sortable columns (`sort`):** `TaskId`, `TaskType`, `AgentId`, `Client`, `User`, `Computer`, `StartDate`, `FinishDate`, `CommandLine`, `Completed`, `Message`. Unrecognized values are ignored (falls back to `StartDate DESC`).

**Filter expression (`q`):** mini-DSL for filtering by the fields `TaskId`, `AgentId`, `Client`, `User`, `Computer`, `CommandLine`, `Message`, `ClearText` (substring/LIKE):

* Words — regular terms (`shell` ⇒ `LIKE '%shell%'` against at least one column).
* `&` — AND (whitespace between terms also acts as AND).
* `|` — OR.
* `^(expr)` — NOT.
* `(expr)` — grouping.
* Nesting depth up to 16, string length up to 512 chars.

Examples: `shell`, `shell & ^(failed)`, `download | upload`, `(admin | system) & ^(test)`.

***

**Response**

```json
{
  "items": [
    {
      "a_task_type": 1,
      "a_task_id": 1,
      "a_id": 12,
      "a_client": "operator",
      "a_user": "alice",
      "a_computer": "WS01",
      "a_cmdline": "shell whoami",
      "a_start_time": 1700000000,
      "a_finish_time": 1700000005,
      "a_msg_type": 7,
      "a_message": "CORP\\alice",
      "a_text": "CORP\\alice",
      "a_completed": true
    }
  ],
  "total": 1234,
  "offset": 0,
  "limit": 100
}
```

`a_task_type`: `0` local, `1` task, `2` browser, `3` job, `4` tunnel, `5` proxy data.

**Errors (HTTP 400):**

* `invalid filter: ...` — syntax error in `q`.
* `agent <id> not found` — the agent does not exist (only if `agent_id` is non-empty).

</details>

<details>

<summary><strong>POST /</strong>agent/task/cancel</summary>

Cancel tasks — both queued and already dispatched to the agent

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "tasks_array": [1]
}
```

<table><thead><tr><th width="146.71673583984375">Field</th><th width="128.5167236328125">Type</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Agent ID</td></tr><tr><td>tasks_array</td><td>int64[]</td><td>Array of task IDs to cancel</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/task/delete</summary>

Remove task from Task Manager

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "tasks_array": [1]
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/task/save</summary>

Save data to Task Manager

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "command_line": "string",
  "message_type": 0,
  "message": "string",
  "clear_text": "string"
}
```

<table><thead><tr><th width="156.74993896484375">Field</th><th width="92.7000732421875">Type</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Agent ID</td></tr><tr><td>command_line</td><td>string</td><td>Command line</td></tr><tr><td>message_type</td><td>int</td><td>Message type</td></tr><tr><td>message</td><td>string</td><td>HTML message</td></tr><tr><td>clear_text</td><td>string</td><td>Text message</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>agent/task/hook</summary>

Send the result of a task hook

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "a_id": 1,
  "a_task_id": 1,
  "a_hook_id": "string",
  "a_job_index": 0,
  "a_msg_type": 0,
  "a_message": "string",
  "a_text": "string",
  "a_completed": true|false
}
```

<table data-search="false"><thead><tr><th width="135.96661376953125">Field</th><th width="133.800048828125">Type</th><th>Description</th></tr></thead><tbody><tr><td>a_id</td><td>int64</td><td>Agent ID</td></tr><tr><td>a_task_id</td><td>int64</td><td>Task ID</td></tr><tr><td>a_hook_id</td><td>string</td><td>Hook ID</td></tr><tr><td>a_job_index</td><td>int</td><td>Job index</td></tr><tr><td>a_msg_type</td><td>int</td><td>Message type</td></tr><tr><td>a_message</td><td>string</td><td>HTML message</td></tr><tr><td>a_text</td><td>string</td><td>Text message</td></tr><tr><td>a_completed</td><td>boolean</td><td>Task is complete</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Chat

<details>

<summary><strong>GET</strong> /chat/history</summary>

Paginated message history (newest first).

***

**Request**

Header `Authorization: Bearer <access_token>`

<table><thead><tr><th width="138.16668701171875">Parameter</th><th width="78.5">Type</th><th width="72.0166015625">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>limit</code></td><td>int</td><td>50</td><td>Number of messages. A value outside 1..200 is replaced with 50.</td></tr><tr><td><code>before_id</code></td><td>int64</td><td>—</td><td>Load messages older than this ID (for pagination).</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "messages": [
    {
      "id": 42,
      "username": "admin",
      "message": "Hello team!",
      "date": 1718500000,
      "edited": false,
      "deleted": false,
      "reactions": {"👍": ["user1", "user2"]},
      "reply_to_id": 0,
      "reply_to_name": "",
      "deleted_date": 0
    }
  ],
  "total": 1543
}
```

</details>

<details>

<summary><strong>GET</strong> /chat/search</summary>

Full-text search over messages.

***

**Request**

Header `Authorization: Bearer <access_token>`

<table><thead><tr><th width="112.36663818359375">Parameter</th><th width="72.8831787109375">Type</th><th width="74.5667724609375">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>q</code></td><td>string</td><td>—</td><td>Search string (LIKE match).</td></tr><tr><td><code>limit</code></td><td>int</td><td>50</td><td>Max results. A value outside 1..200 is replaced with 50.</td></tr><tr><td><code>before_id</code></td><td>int64</td><td>—</td><td>Pagination (older than this ID).</td></tr></tbody></table>

***

**Response**

</details>

<details>

<summary><strong>POST /</strong>chat/clear</summary>

Hard-delete all messages from the chat.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>chat/send</summary>

Send message to chat

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "message": "string",
  "reply_to_id": 0,
  "reply_to_name": ""
}
```

<table><thead><tr><th width="143.84991455078125">Field</th><th width="83.2166748046875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>message</code></td><td>string</td><td>Message text (Markdown supported).</td></tr><tr><td><code>reply_to_id</code></td><td>int64</td><td>ID of the message being replied to. <code>0</code> = no reply.</td></tr><tr><td><code>reply_to_name</code></td><td>string</td><td>Author name of the original message (for the preview). Empty when <code>reply_to_id = 0</code>.</td></tr></tbody></table>

***

**Response**

```json
{
  "ok": true,
  "message": "<id>"
}
```

</details>

<details>

<summary><strong>POST /</strong>chat/&#x3C;id>/edit</summary>

Edit a message (only the author can edit)

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
   "message": "New message text"
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>chat/&#x3C;id>/delete</summary>

Soft-delete a message (only the author can delete).

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>chat/&#x3C;id>/react</summary>

Toggle the user's reaction on a message. **Supported reactions:** `👍 ❤️ 🔥 👀 🚀 😂 ✅ ✓ ❌ 💩`

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "emoji": "👍"
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>GET</strong> /chat/todo</summary>

Get the current contents of the Shared ToDo.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

```json
{
  "ok": true,
  "content": "## Tasks for the week\n- [ ] Check the logs",
  "updated_by": "admin",
  "updated_at": 1718500000
}
```

</details>

<details>

<summary><strong>POST</strong> /chat/todo</summary>

Overwrite the contents of the Shared ToDo.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "content": "## New tasks\n- [x] Update the documentation"
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Download

<details>

<summary><strong>GET /download</strong>/list</summary>

Paginated list of downloads. Supports filtering and sorting

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="110.2166748046875">Parameter</th><th width="79.61669921875">Type</th><th width="117.550048828125">Default</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>omitted (all)</td><td>Agent ID. Omit it to list every agent. A non-integer value is rejected</td></tr><tr><td>limit</td><td>int</td><td>100</td><td>Max entries (cap 1000)</td></tr><tr><td>offset</td><td>int</td><td>0</td><td>Pagination offset</td></tr><tr><td>q</td><td>string</td><td>""</td><td>Filter expression</td></tr><tr><td>sort</td><td>string</td><td>"Date"</td><td>Sort column</td></tr><tr><td>order</td><td>string</td><td>"desc"</td><td>Direction: <code>asc</code> or <code>desc</code></td></tr></tbody></table>

**Sortable columns (`sort`):** `FileId`, `AgentId`, `AgentName`, `User`, `Computer`, `RemotePath`, `TotalSize`, `RecvSize`, `Date`, `State`.

**Filter expression (`q`):** mini-DSL with terms/`&`/`|`/`^()`/`(...)` (see `/agent/task/list`). Filter columns: `FileId`, `AgentId`, `AgentName`, `User`, `Computer`, `RemotePath`.

***

**Response**

```json
{
  "items": [
    {
      "t_file_id": 1001,
      "t_agent_id": 12,
      "t_agent_name": "beacon",
      "t_user": "alice",
      "t_computer": "WS01",
      "t_remote_path": "C:\\Users\\alice\\Desktop\\notes.txt",
      "t_local_path": "******",
      "t_total_size": 4096,
      "t_progress": 4096,
      "t_date": 1718500000,
      "t_state": 3,
      "t_tag": "",
      "t_cancellable": false,
      "t_kind": 0
    }
  ],
  "total": 42,
  "offset": 0,
  "limit": 100
}
```

</details>

<details>

<summary><strong>POST /</strong>download/sync</summary>

Get the contents of a file.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "file_id": 1
}
```

***

**Response**

```json
{
  "ok": true,
  "filename": "string",
  "content": "<base64_encoded_content>"
}
```

</details>

<details>

<summary><strong>POST /</strong>download/delete</summary>

Delete file

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "file_id_array": [123, 456]
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>download/set/tag</summary>

Bulk set/clear the tag of a group of downloads.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id_array": [123, 456, 789],
  "tag": "credentials-dump"
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Upload

<details>

<summary><strong>GET /</strong>upload/list</summary>

Paginated list of uploads

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="103.04998779296875">Parameter</th><th width="94.5167236328125">Type</th><th width="87.75">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>agent_id</code></td><td><code>int64</code></td><td><code>0</code> (all)</td><td>Filter by agent; <code>0</code> or empty — all</td></tr><tr><td><code>offset</code></td><td><code>int</code></td><td><code>0</code></td><td>Offset from the start of the result set</td></tr><tr><td><code>limit</code></td><td><code>int</code></td><td><code>100</code></td><td>Max entries per page (cap <code>1000</code>)</td></tr><tr><td><code>q</code></td><td><code>string</code></td><td>empty</td><td>Filter mini-DSL (<code>term &#x26; term</code>, <code>^(neg)</code>, <code>(group)</code>, `</td></tr><tr><td><code>sort</code></td><td><code>string</code></td><td><code>Date</code></td><td>Sort column (see table below)</td></tr><tr><td><code>order</code></td><td><code>string</code></td><td><code>desc</code></td><td><code>asc</code> or <code>desc</code></td></tr></tbody></table>

**Sortable columns:** `FileId`, `AgentId`, `AgentName`, `User`, `Computer`, `RemotePath`, `TotalSize`, `Progress`, `Date`, `State`, `Tag`, `Kind`, `ArtifactName`, `ArtifactType`.

**Filterable columns (`q`):** `FileId`, `AgentId`, `AgentName`, `User`, `Computer`, `RemotePath`, `Tag`, `ArtifactName`, `ArtifactType`.

***

**Response**

```json
{
  "items": [
    {
      "t_file_id": 1234567890,
      "t_agent_id": 42,
      "t_agent_name": "beacon_http",
      "t_user": "DESKTOP\\alice",
      "t_computer": "DESKTOP",
      "t_remote_path": "C:\\Users\\alice\\Desktop\\loader.exe",
      "t_local_path": "******",
      "t_total_size": 1048576,
      "t_progress": 524288,
      "t_date": 1715520000,
      "t_state": 1,
      "t_tag": "",
      "t_cancellable": true,
      "t_kind": 0,
      "t_artifact_name": "",
      "t_artifact_type": ""
    }
  ],
  "total": 17,
  "offset": 0,
  "limit": 100
}
```

</details>

<details>

<summary><strong>POST /</strong>download/delete</summary>

Bulk cancel-and-remove uploads.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id_array": [1234567890, 9876543210]
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Screenshot

<details>

<summary><strong>GET /screen</strong>/list</summary>

Paginated list of screenshots with filtering and sorting.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="100.18328857421875">Parameter</th><th width="79.75">Type</th><th width="103.9166259765625">Default</th><th>Description</th></tr></thead><tbody><tr><td>limit</td><td>int</td><td>100</td><td>Max entries (cap 1000)</td></tr><tr><td>offset</td><td>int</td><td>0</td><td>Pagination offset</td></tr><tr><td>q</td><td>string</td><td>""</td><td>Filter expression</td></tr><tr><td>sort</td><td>string</td><td>"Date"</td><td>Sort column</td></tr><tr><td>order</td><td>string</td><td>"desc"</td><td>Direction: <code>asc</code> or <code>desc</code></td></tr></tbody></table>

**Sortable columns (`sort`):** `ScreenId`, `User`, `Computer`, `Note`, `Date`.

**Filter expression (`q`):** Filter columns: `ScreenId`, `User`, `Computer`, `Note`.

***

**Response**

```json
{
  "items": [
    {
      "s_screen_id": 4,
      "s_agent_id": 12,
      "s_user": "alice",
      "s_computer": "WS01",
      "s_local_path": "",
      "s_note": "desktop",
      "s_date": 1718500000,
      "s_content": null
    }
  ],
  "total": 17,
  "offset": 0,
  "limit": 100
}
```

</details>

<details>

<summary><strong>GET /screen/image?</strong>screen_id=</summary>

Raw PNG for one screenshot.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

body is `image/png`. An unknown id returns `{"ok":false,"message":"..."}`.

</details>

<details>

<summary><strong>POST /</strong>screen/setnote</summary>

Set a note for a screenshot

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "screen_id_array": [1],
  "note": "string"
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>screen/remove</summary>

Remove a screenshots

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "screen_id_array": [1]
}
```

***

**Response**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Creds

<details>

<summary><strong>GET /</strong>creds/list</summary>

Paginated list of credentials with filtering and sorting.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="102.33331298828125">Parameter</th><th width="91.933349609375">Type</th><th width="123.433349609375">Default</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>omitted (all)</td><td>Source agent ID. Omit it to list every agent. A non-integer value is rejected</td></tr><tr><td>limit</td><td>int</td><td>100</td><td>Max entries (cap 1000)</td></tr><tr><td>offset</td><td>int</td><td>0</td><td>Pagination offset</td></tr><tr><td>q</td><td>string</td><td>""</td><td>Filter expression</td></tr><tr><td>sort</td><td>string</td><td>"Date"</td><td>Sort column</td></tr><tr><td>order</td><td>string</td><td>"desc"</td><td>Direction: <code>asc</code> or <code>desc</code></td></tr></tbody></table>

**Sortable columns (`sort`):** `CredId`, `Username`, `Password`, `Realm`, `Type`, `Tag`, `Date`, `Storage`, `AgentId`, `Host`.

**Filter expression (`q`):** Filter columns: `CredId`, `Username`, `Password`, `Realm`, `Type`, `Tag`, `Storage`, `AgentId`, `Host`.

***

**Response (200 OK):**

```json
{
  "items": [
    {
      "c_creds_id": 3,
      "c_username": "alice",
      "c_password": "password",
      "c_realm": "CORP",
      "c_type": "plaintext",
      "c_tag": "domain",
      "c_date": 1718500000,
      "c_storage": "lsass",
      "c_agent_id": 12,
      "c_host": "dc01.corp.local"
    }
  ],
  "total": 256,
  "offset": 0,
  "limit": 100
}
```

</details>

<details>

<summary><strong>POST /</strong>creds/add</summary>

Store credentials to Creds Manager

***

**Request Body:**

```json
{
  "creds": [
    {
      "username": "string",
      "password": "string",
      "realm": "string",
      "type": "string",
      "tag": "string",
      "storage": "string",
      "host": "string"
    }
  ]
}
```

<table data-search="false"><thead><tr><th width="119.48333740234375">Field</th><th width="95.2166748046875">Type</th><th>Description</th></tr></thead><tbody><tr><td>username</td><td>string</td><td>User name</td></tr><tr><td>password</td><td>string</td><td>Password / hash</td></tr><tr><td>realm</td><td>string</td><td>Domain / realm</td></tr><tr><td>type</td><td>string</td><td>Type (plaintext, ntlm, etc.)</td></tr><tr><td>tag</td><td>string</td><td>Tag</td></tr><tr><td>storage</td><td>string</td><td>Source</td></tr><tr><td>host</td><td>string</td><td>Host</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>creds/edit</summary>

Edit stored credentials

***

**Request:**

```json
{
  "cred_id": 1,
  "username": "string",
  "password": "string",
  "realm": "string",
  "type": "string",
  "tag": "string",
  "storage": "string",
  "host": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>creds/set/tag</summary>

Set creds tag

***

**Request Body:**

```json
{
  "id_array": [1],
  "tag": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>creds/remove</summary>

Delete credentials.

***

**Request:**

```json
{
  "cred_id_array": [1]
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Targets

<details>

<summary><strong>GET /</strong>targets/list</summary>

Paginated list of targets with filtering and sorting.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="98.0333251953125">Parameter</th><th width="85.7666015625">Type</th><th width="95.4666748046875">Default</th><th>Description</th></tr></thead><tbody><tr><td>limit</td><td>int</td><td>100</td><td>Max entries (cap 1000)</td></tr><tr><td>offset</td><td>int</td><td>0</td><td>Pagination offset</td></tr><tr><td>q</td><td>string</td><td>""</td><td>Filter expression</td></tr><tr><td>sort</td><td>string</td><td>"Date"</td><td>Sort column</td></tr><tr><td>order</td><td>string</td><td>"desc"</td><td>Direction: <code>asc</code> or <code>desc</code></td></tr></tbody></table>

**Sortable columns (`sort`):** `TargetId`, `Computer`, `Domain`, `Address`, `Tag`, `Os`, `OsDesk`, `Date`, `Info`, `Alive`.

**Filter expression (`q`):** mini-DSL (see `/agent/task/list`). Filter columns: `TargetId`, `Computer`, `Domain`, `Address`, `Tag`, `OsDesk`, `Info`.

***

**Response (200 OK):**

```json
{
  "items": [
    {
      "t_target_id": 2,
      "t_computer": "DC01",
      "t_domain": "corp.local",
      "t_address": "10.0.0.10",
      "t_os": 1,
      "t_os_desk": "Windows Server 2019",
      "t_tag": "dc",
      "t_info": "",
      "t_date": 1718500000,
      "t_alive": true,
      "t_agents": [12]
    }
  ],
  "total": 8,
  "offset": 0,
  "limit": 100
}
```

`t_os` uses the same values as `a_os`: `0` unknown, `1` Windows, `2` Linux, `3` macOS.

</details>

<details>

<summary><strong>POST /</strong>targets/add</summary>

Store targets to Targets Manager

***

**Request:**

```json
{
  "targets": [
    {
      "computer": "string",
      "domain": "string",
      "address": "string",
      "os": 0,
      "os_desk": "string",
      "tag": "string",
      "info": "string",
      "alive": true|false
    }
  ]
}
```

<table data-search="false"><thead><tr><th width="104.433349609375">Field</th><th width="103.933349609375">Type</th><th>Description</th></tr></thead><tbody><tr><td>computer</td><td>string</td><td>Computer name</td></tr><tr><td>domain</td><td>string</td><td>Domain</td></tr><tr><td>address</td><td>string</td><td>IP address</td></tr><tr><td>os</td><td>int</td><td>OS code</td></tr><tr><td>os_desk</td><td>string</td><td>OS description</td></tr><tr><td>tag</td><td>string</td><td>Tag</td></tr><tr><td>info</td><td>string</td><td>Additional information</td></tr><tr><td>alive</td><td>boolean</td><td>Alive status</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>targets/edit</summary>

Edit stored target

***

**Request:**

```json
{
  "t_target_id": 1,
  "t_computer": "string",
  "t_domain": "string",
  "t_address": "string",
  "t_os": 0,
  "t_os_desk": "string",
  "t_tag": "string",
  "t_info": "string",
  "t_alive": true|false
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>targets/set/tag</summary>

Set target tag

***

**Request:**

```json
{
  "id_array": [1],
  "tag": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST /</strong>targets/remove</summary>

Remove stored targets

***

**Request:**

```json
{
  "target_id_array": [1]
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Tunnel

<details>

<summary><strong>GET /</strong>tunnel/list</summary>

Get a list of tunnels with full information

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

```json
[
  {
    "p_tunnel_id": 7,
    "p_agent_id": 12,
    "p_computer": "WS01",
    "p_username": "alice",
    "p_process": "explorer.exe",
    "p_type": "socks5",
    "p_info": "socks5 127.0.0.1:1080",
    "p_interface": "127.0.0.1",
    "p_port": "1080",
    "p_client": "operator",
    "p_fhost": "",
    "p_fport": "",
    "p_auth_user": "",
    "p_auth_pass": "",
    "p_date": 1718500000,
    "p_active": true
  }
]
```

</details>

<details>

<summary><strong>POST /</strong>start/socks5</summary>

Start socks5 proxy

***

**Request:**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "listen": true|false,
  "desc": "string",
  "l_host": "string",
  "l_port": 1080,
  "use_auth": true|false,
  "username": "string",
  "password": "string"
}
```

<table data-search="false"><thead><tr><th width="114.51666259765625">Field</th><th width="88.6500244140625">Type</th><th width="218.050048828125">Required</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Yes</td><td>Agent ID</td></tr><tr><td>listen</td><td>boolean</td><td>No</td><td>Listening mode</td></tr><tr><td>desc</td><td>string</td><td>No</td><td>Tunnel description</td></tr><tr><td>l_host</td><td>string</td><td>Yes</td><td>Local host to bind</td></tr><tr><td>l_port</td><td>int</td><td>Yes</td><td>Local port (1-65535)</td></tr><tr><td>use_auth</td><td>boolean</td><td>No</td><td>Use authentication</td></tr><tr><td>username</td><td>string</td><td>When use_auth=true</td><td>User name</td></tr><tr><td>password</td><td>string</td><td>When use_auth=true</td><td>Password</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": 123
}
```

</details>

<details>

<summary><strong>POST /</strong>start/socks4</summary>

Start socks4 proxy

***

**Request:**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "listen": true|false,
  "desc": "string",
  "l_host": "string",
  "l_port": 1080
}
```

<table><thead><tr><th width="105.91668701171875">Field</th><th width="97.816650390625">Type</th><th width="107.683349609375">Required</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Yes</td><td>Agent ID</td></tr><tr><td>listen</td><td>boolean</td><td>No</td><td>Listening mode</td></tr><tr><td>desc</td><td>string</td><td>No</td><td>Tunnel description</td></tr><tr><td>l_host</td><td>string</td><td>Yes</td><td>Local host</td></tr><tr><td>l_port</td><td>int</td><td>Yes</td><td>Local port (1-65535)</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": 123
}
```

</details>

<details>

<summary><strong>POST /</strong>start/lportfwd</summary>

Start local port forwarding

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "listen": true|false,
  "desc": "string",
  "l_host": "string",
  "l_port": 8080,
  "t_host": "string",
  "t_port": 80
}
```

<table data-search="false"><thead><tr><th width="111.64996337890625">Field</th><th width="92.949951171875">Type</th><th width="96.6500244140625">Required</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Yes</td><td>Agent ID</td></tr><tr><td>listen</td><td>boolean</td><td>No</td><td>Listening mode</td></tr><tr><td>desc</td><td>string</td><td>No</td><td>Description</td></tr><tr><td>l_host</td><td>string</td><td>Yes</td><td>Local host</td></tr><tr><td>l_port</td><td>int</td><td>Yes</td><td>Local port (1-65535)</td></tr><tr><td>t_host</td><td>string</td><td>Yes</td><td>Target host</td></tr><tr><td>t_port</td><td>int</td><td>Yes</td><td>Target port (1-65535)</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": 123
}
```

</details>

<details>

<summary><strong>POST /</strong>start/rportfwd</summary>

Start reverse port forwarding

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 1,
  "listen": true|false,
  "desc": "string",
  "port": 8080,
  "t_host": "string",
  "t_port": 80
}
```

<table><thead><tr><th width="105.91668701171875">Field</th><th width="98.5333251953125">Type</th><th width="86.183349609375">Required</th><th>Description</th></tr></thead><tbody><tr><td>agent_id</td><td>int64</td><td>Yes</td><td>Agent ID</td></tr><tr><td>listen</td><td>boolean</td><td>No</td><td><code>true</code> — server tunnel, <code>false</code> — client tunnel (target on the client side)</td></tr><tr><td>desc</td><td>string</td><td>No</td><td>Description</td></tr><tr><td>port</td><td>int</td><td>Yes</td><td>Port on the agent (1-65535)</td></tr><tr><td>t_host</td><td>string</td><td>Yes</td><td>Target host</td></tr><tr><td>t_port</td><td>int</td><td>Yes</td><td>Target port (1-65535)</td></tr></tbody></table>

Modes:

* `listen=true` (server-side) — the agent's inbound connections are proxied by the server to `t_host:t_port`.
* `listen=false` (client-side) — the `t_host:t_port` target is dialed **by the owning client**: on an inbound connection the agent sends `TsTunnelConnectionAccept`, the server creates a pending channel and delivers the sync packet `TYPE_TUNNEL_ACCEPT (0x5a)` to the owner; the client dials the target locally (15-second timeout) and attaches to the channel via channel-OTP (`mode="reverse"`), or rejects it via `POST /tunnel/channel/nack`. The owner has 45 seconds to attach (`reverseAttachTimeout`).

***

**Response:**

```json
{
  "ok": true,
  "message": 123
}
```

</details>

<details>

<summary><strong>POST /</strong>set/info</summary>

Set tunnel info

***

**Request (JSON)**

Header `Authorization: Bearer <access_token>`

```json
{
  "p_tunnel_id": 123,
  "p_info": "string"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Tunnel info updated"
}
```

</details>

<details>

<summary><strong>POST</strong> /tunnel/stop</summary>

Stop tunnel

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "p_tunnel_id": 123
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Tunnel stopped"
}
```

</details>

<details>

<summary><strong>POST</strong> /tunnel/pause</summary>

Pause a tunnel

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "p_tunnel_id": 123
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Tunnel paused"
}
```

</details>

<details>

<summary><strong>POST</strong> /tunnel/resume</summary>

Resume a tunnel

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "p_tunnel_id": 123
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "Tunnel resumed"
}
```

</details>

## Payloads

<details>

<summary><strong>GET /</strong>payload/list</summary>

Paginated payload list.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="123.83331298828125">Parameter</th><th width="93.9666748046875">Type</th><th width="92.0833740234375">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>show_hidden</code></td><td><code>bool</code></td><td><code>false</code></td><td>Include hidden payloads.</td></tr><tr><td><code>q</code></td><td><code>string</code></td><td>—</td><td>Filter DSL (same as <code>/agent/task/list</code>).</td></tr><tr><td><code>sort</code></td><td><code>string</code></td><td><code>Created</code></td><td>Sort column.</td></tr><tr><td><code>order</code></td><td><code>string</code></td><td><code>desc</code></td><td><code>asc</code> / <code>desc</code>.</td></tr><tr><td><code>limit</code></td><td><code>int</code></td><td><code>100</code></td><td>Max entries.</td></tr><tr><td><code>offset</code></td><td><code>int</code></td><td><code>0</code></td><td>Offset.</td></tr></tbody></table>

***

**Response**

````json
```json
{
  "items": [
    {
      "p_id": 12,
      "p_name": "beacon_https",
      "p_type": "beacon",
      "p_artifact": "exe",
      "p_arch": "x64",
      "p_listeners": ["http-443"],
      "p_size": 307200,
      "p_sha1": "…",
      "p_sha256": "…",
      "p_md5": "…",
      "p_creator": "admin",
      "p_date": 1718500000,
      "p_hidden": false,
      "p_filename": "beacon_https.exe",
      "p_notes": "",
      "p_tag": "",
      "p_color": "",
      "p_build_id": "",
      "p_watermark": "c04c4e57",
      "p_missing": false
    }
  ],
  "total": 42,
  "offset": 0,
  "limit": 100
}
```
````

</details>

<details>

<summary><strong>POST</strong> /payload/get</summary>

Full payload by id

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "payload_id": 12
}
```

***

**Response:**

```json
{
  "ok": true,
  "payload": {
    "p_id": 12,
    "p_name": "beacon_https",
    "p_type": "beacon",
    "p_artifact": "exe",
    "p_arch": "x64",
    "p_listeners": ["http-443"],
    "p_size": 307200,
    "p_sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "p_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "p_md5": "d41d8cd98f00b204e9800998ecf8427e",
    "p_creator": "admin",
    "p_date": 1718500000,
    "p_hidden": false,
    "p_config": "{\"sleep\":5}",
    "p_build_id": "b1",
    "p_watermark": "c04c4e57",
    "p_filename": "beacon_https.exe",
    "p_notes": "",
    "p_tag": "",
    "p_uid": "uid-1",
    "p_color": "",
    "p_missing": false
  }
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/download</summary>

Payload content.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "payload_id": 12
}
```

***

**Response:**

```json
{
  "ok": true,
  "filename": "beacon_https.exe",
  "content": "<base64>"
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/hide</summary>

Hide/show payloads

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id_array": [12, 13],
  "hidden": true
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/update</summary>

Update payload metadata.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "payload_id": 12,
  "name": "beacon_https_v2",
  "notes": "staging build",
  "artifact": "exe",
  "arch": "x64",
  "hidden": false
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/set_color</summary>

Payload row colour.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id_array": [12],
  "background": "#222222",
  "foreground": "#ff0000",
  "reset": false
}
```

***

**Response:**

```json
{
  "ok": true,
  "filename": "beacon_https.exe",
  "content": "<base64>"
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/set/tag</summary>

Payload tag.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id_array": [12],
  "tag": "staging"
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/remove</summary>

Remove payloads. `hard=true` — delete both the record and the on-disk file; `hard=false` — record only (the file stays).

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id_array": [12],
  "hard": true
}
```

</details>

<details>

<summary><strong>POST</strong> /payload/import</summary>

Import an external file into the store.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "name": "custom_agent",
  "agent_type": "beacon",
  "artifact": "exe",
  "arch": "x64",
  "listeners": [],
  "content": "<base64>",
  "config": ""
}
```

<table data-search="false"><thead><tr><th width="133.0999755859375">Field</th><th width="129.7833251953125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>string</code></td><td>Name (default <code>imported</code>).</td></tr><tr><td><code>agent_type</code></td><td><code>string</code></td><td>Agent type (default <code>imported</code>).</td></tr><tr><td><code>artifact</code></td><td><code>string</code></td><td>Artifact kind.</td></tr><tr><td><code>arch</code></td><td><code>string</code></td><td>Architecture.</td></tr><tr><td><code>listeners</code></td><td><code>[]string</code></td><td>Bound listeners.</td></tr><tr><td><code>content</code></td><td><code>string</code></td><td><strong>Base64</strong> file content (required).</td></tr><tr><td><code>config</code></td><td><code>string</code></td><td>Build-config JSON.</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true
}
```

</details>

## Services

<details>

<summary><strong>GET /</strong>service/list</summary>

Get the list of loaded services.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

````json
```json
{
  "message": "success",
  "services": "[{\"Name\":\"notifier\",\"AX\":\"function ServiceUI() {}\"}]"
}
```
````

</details>

<details>

<summary><strong>GET /</strong>service/catalog</summary>

Registered services with their AxScript command catalogs.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response**

````json
```json
[
  {
    "name": "notifier",
    "title": "Notifier",
    "description": "",
    "rpc": true,
    "commands": [
      { "name": "get_config", "description": "..." }
    ]
  }
]
```
````

</details>

<details>

<summary><strong>POST</strong> /plugin/service/call</summary>

Call a service function.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "service": "string",
  "command": "string",
  "args": "string"
}
```

***

**Response:**

```json
{
  "message": "success",
  "result": "ok"
}
```

</details>

<details>

<summary><strong>POST</strong> /plugin/service/call_wait</summary>

Synchronous service function call (`TsPluginServiceCallWait`). Blocks until the `CallRPC` JSON result or timeout.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "service": "string",
  "command": "string",
  "args": "string",
  "timeout_ms": 30000
}
```

***

**Response:**

```json
{
  "message": "success",
  "result": "{...json from CallRPC...}"
}
```

</details>

<details>

<summary><strong>POST</strong> /plugin/agent/call</summary>

Asynchronous call of an **agent**-plugin function for a specific session (`TsPluginAgentCall`).&#x20;

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "agent_id": 42,
  "command": "get_panel",
  "args": "{\"foo\": 1}"
}
```

***

**Response:**

```json
{
  "message": "success",
  "result": "ok"
}
```

</details>

<details>

<summary><strong>POST</strong> /plugin/listener/call</summary>

Asynchronous call of a **listener**-plugin function for a specific instance (`TsPluginListenerCall`).

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "listener": "http-443",
  "command": "get_stats",
  "args": "{}"
}
```

***

**Response:**

```json
{
  "message": "success",
  "result": "{...json from CallRPC...}"
}
```

</details>

## Events

<details>

<summary><strong>GET</strong> /events/handlers</summary>

Paginated handler list with filtering.

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table data-search="false"><thead><tr><th width="108.06671142578125">Parameter</th><th width="101.4000244140625">Type</th><th width="83.9000244140625">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>offset</code></td><td><code>int</code></td><td><code>0</code></td><td>Offset</td></tr><tr><td><code>limit</code></td><td><code>int</code></td><td><code>50</code></td><td>Max entries (cap 1000; <code>0</code> → 50)</td></tr><tr><td><code>q</code></td><td><code>string</code></td><td>—</td><td>Search over id/name/description/group/event/source/last_error</td></tr><tr><td><code>event</code></td><td><code>string</code></td><td>—</td><td>Exact event type (e.g. <code>agent.new</code>)</td></tr><tr><td><code>source</code></td><td><code>string</code></td><td>—</td><td>Source: <code>handler</code> (AxScript) / <code>extender</code> / <code>core</code></td></tr><tr><td><code>group</code></td><td><code>string</code></td><td>—</td><td>Exact group name</td></tr><tr><td><code>enabled</code></td><td><code>string</code></td><td>—</td><td><code>true</code>/<code>1</code> — enabled only; <code>false</code>/<code>0</code> — disabled only</td></tr></tbody></table>

***

**Response**

````json
```json
{
  "items": [
    {
      "id": "h_abc123",
      "name": "notify-new-agents",
      "description": "Logs new agents",
      "group": "notifications",
      "event": "agent.new",
      "source": "handler",
      "enabled": true,
      "event_muted": false,
      "filters": { "agent_names": ["win-*"], "os": ["windows"] },
      "last_error": "",
      "last_run_at": 1718500000
    }
  ],
  "total": 12,
  "offset": 0,
  "limit": 50
}
```
````

</details>

<details>

<summary><strong>POST</strong> /events/handlers/register</summary>

Register a new handler (or update an existing one when `id` is passed). The script is dry-compiled: it must define `function handler(event)` (or `onEvent(event)`), otherwise the registration is rejected.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": "",
  "name": "notify-new-agents",
  "group": "notifications",
  "description": "Logs new agents",
  "event": "agent.new",
  "script": "function handler(event) { ax.log('new agent: ' + event.agentName); }",
  "enabled": true,
  "filters": { "os": ["windows"] }
}
```

<table data-search="false"><thead><tr><th width="138.11663818359375">Field</th><th width="100.550048828125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>string</code></td><td>Optional. ID of an existing handler — to update it.</td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>Required. Name (sanitised: letters/digits/<code>-</code>/<code>_</code>).</td></tr><tr><td><code>group</code></td><td><code>string</code></td><td>Optional. Grouping.</td></tr><tr><td><code>description</code></td><td><code>string</code></td><td>Optional. Description.</td></tr><tr><td><code>event</code></td><td><code>string</code></td><td>Required. Event type (see <code>KnownEventTypes</code>, e.g. <code>agent.new</code>).</td></tr><tr><td><code>script</code></td><td><code>string</code></td><td>Required. JS code with <code>handler(event)</code>.</td></tr><tr><td><code>enabled</code></td><td><code>bool</code></td><td>Optional (default <code>true</code>).</td></tr><tr><td><code>filters</code></td><td><code>object</code></td><td>Optional. <code>EventFilter</code> (see below).</td></tr></tbody></table>

***

**Response:**

```json
{
  "ok": true,
  "message": "",
  "data": {
    "id": "h_abc123",
    "name": "notify-new-agents",
    "group": "notifications",
    "description": "Logs new agents",
    "event": "agent.new",
    "script": "function handler(event) { ax.log('new agent'); }",
    "enabled": true,
    "filters": { "os": ["windows"] },
    "created_by": "admin",
    "updated_by": "admin",
    "created_at": 1718500000,
    "updated_at": 1718500000,
    "last_error": "",
    "last_run_at": 0
  }
}
```

</details>

<details>

<summary><strong>POST</strong> /events/handlers/get</summary>

Fetch a full handler (including the source script).

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": "h_abc123"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": "",
  "data": {
    "id": "h_abc123",
    "name": "notify-new-agents",
    "group": "notifications",
    "description": "Logs new agents",
    "event": "agent.new",
    "script": "function handler(event) { ax.log('new agent'); }",
    "enabled": true,
    "filters": { "os": ["windows"] },
    "created_by": "admin",
    "updated_by": "admin",
    "created_at": 1718500000,
    "updated_at": 1718500000,
    "last_error": "",
    "last_run_at": 0
  }
}
```

**Errors:**

* `id is required`.
* `event handler not found`.

</details>

<details>

<summary><strong>POST</strong> /events/handlers/enable</summary>

Enable a handler.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": "h_abc123"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /events/handlers/disable</summary>

Disable a handler

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": "h_abc123"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /events/handlers/remove</summary>

Remove a handler.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "id": "h_abc123"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /events/mute</summary>

Mute an event type: all hook dispatch for this type is suppressed (both AxScript handlers and extender hooks).&#x20;

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "event": "agent.update"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /events/unmute</summary>

Unmute an event type.

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "event": "agent.update"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

<details>

<summary><strong>POST</strong> /events/mutes</summary>

List of muted event types.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response:**

```json
{
  "ok": true,
  "message": "",
  "items": ["agent.update", "task.update"]
}
```

</details>

<details>

<summary><strong>POST</strong> /events/types</summary>

Sorted list of all valid event types — system (`KnownEventTypes`) plus the custom `domain.action` format.

***

**Request**

Header `Authorization: Bearer <access_token>`

***

**Response:**

```json
["agent.new", "agent.remove", "task.create", "ops.deploy"]
```

</details>

<details>

<summary><strong>POST</strong> /events/emit</summary>

Emit a custom event

***

**Request**

Header `Authorization: Bearer <access_token>`

```json
{
  "event": "ops.deploy",
  "source": "operator:admin",
  "text": "staging deploy finished"
}
```

***

**Response:**

```json
{
  "ok": true,
  "message": ""
}
```

</details>

## Logs

<details>

<summary><strong>GET</strong> /logs/list</summary>

Paginated reads from the server-side `LogManager` ring buffer

***

**Request**

Header `Authorization: Bearer <access_token>`

Query Parameters:

<table><thead><tr><th width="117.38336181640625">Parameter</th><th width="104.13330078125">Type</th><th width="87.2166748046875">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>limit</code></td><td><code>int</code></td><td><code>200</code></td><td>Max entries in the response. Cap <code>2000</code>.</td></tr><tr><td><code>offset</code></td><td><code>int</code></td><td><code>0</code></td><td>How many most-recent entries to skip (ignored when <code>before_id</code> is set).</td></tr><tr><td><code>before_id</code></td><td><code>int64</code></td><td>—</td><td>Return entries with <code>Id &#x3C; before_id</code> (cursor pagination for "Load earlier").</td></tr><tr><td><code>source</code></td><td><code>string</code></td><td>—</td><td>Filter by the <code>source</code> field (<code>server</code>, <code>listener</code>, <code>agent</code>, <code>axscript</code>, <code>service</code>).</td></tr><tr><td><code>category</code></td><td><code>string</code></td><td>—</td><td>Filter by category.</td></tr><tr><td><code>q</code></td><td><code>string</code></td><td>—</td><td>Substring: matches <code>source</code>, <code>category</code>, the log-source key, and the entry text (<code>message</code>).</td></tr></tbody></table>

***

**Response**

````json
```json
{
  "items": [
    {
      "id": 12345,
      "time": 1714322400,
      "status": 1,
      "level": 0,
      "source": "listener",
      "category": "beacon_http",
      "message": "Started listener 'http-1': https://0.0.0.0:8443"
    },
    {
      "id": 12344,
      "time": 1714322399,
      "status": 4,
      "level": 0,
      "source": "server",
      "category": "profile",
      "message": "'Teamserver.cert': failed to stat file: no such file"
    }
  ],
  "total": 5000,
  "offset": 0,
  "limit": 200,
  "has_more": true,
  "oldest_id": 12344,
  "sources": ["server:profile", "listener:beacon_http"],
  "categories": { "server": ["profile", "database"], "listener": ["beacon_http"] }
}
```
````

</details>
