WebSocket API Reference

All operator interaction with the server goes through a single WebSocket endpoint. The REST login endpoint issues a JWT used to authenticate the WebSocket connection.


Endpoints

Endpoint Method Auth Description
/api/login POST none Authenticate and receive a JWT
/api/ws WebSocket JWT Bidirectional message bus

Authentication

1. Login

POST /api/login
Content-Type: application/json

Request body

{ "password": "fox3" }

Response — success (200)

{ "token": "<JWT>" }

Response — failure (401)

Plain-text error. After 5 consecutive failures from the same IP the endpoint returns 429 Too Many Requests for 15 minutes.

The JWT is valid for 24 hours and is signed with HMAC-SHA256 using the server’s --password flag value as the secret.

2. Connect WebSocket

Pass the token as a header or query parameter:

ws://localhost:8080/api/ws
Authorization: Bearer <JWT>

or

ws://localhost:8080/api/ws?token=<JWT>

The query-parameter form exists for browser clients (EventSource API doesn’t support custom headers).


Message framing

Client → server (request)

{
  "id":      "req-001",
  "action":  "<action-name>",
  "payload": { }
}
  • id — correlation ID; echoed back in the response. Use any unique string.
  • action — one of the action names listed below.
  • payload — action-specific map.

Server → client (response)

{
  "id":      "req-001",
  "type":    "response",
  "success": true,
  "payload": { },
  "error":   ""
}
  • success: false means payload is absent and error contains the reason.

Server → client (push event)

{
  "type":    "event",
  "event":   "<event-name>",
  "payload": { }
}

Push events are unsolicited. They arrive when agents check in, jobs complete, or listeners start/stop.

Server → client (binary — HVNC)

Binary WebSocket frames are used exclusively for HVNC screen streaming:

[16 bytes]  agent UUID (binary, little-endian)
[4 bytes]   frame width  (uint32 LE)
[4 bytes]   frame height (uint32 LE)
[N bytes]   JPEG image data

Actions

Stats

stats.get

Returns high-level counts.

Payload: none

Response payload

{
  "agents":      3,
  "listeners":   2,
  "credentials": 7
}

Agents

agents.list

Returns all agents currently tracked in memory.

Payload: none

Response payload — array of agent objects

[
  {
    "id":           "550e8400-e29b-41d4-a716-446655440000",
    "platform":     "windows/amd64",
    "host":         "DESKTOP-ABC123",
    "user":         "DESKTOP-ABC123\\victim",
    "process":      "explorer.exe",
    "status":       "Active",
    "alive":        true,
    "note":         "",
    "integrity":    3,
    "links":        [],
    "last_checkin": "2026-05-30T12:00:00Z",
    "sleep":        "30s"
  }
]

status is computed from last check-in time relative to the agent’s sleep interval:

  • Active — checked in within one sleep period
  • Delayed — between one and three sleep periods
  • Dead — more than three sleep periods
  • Init — never checked in

integrity mirrors Windows integrity levels: 1=Low, 2=Medium, 3=High, 4=System.

agents.get

Payload

{ "id": "<agent-uuid>" }

Response payload — single agent object (same shape as above)

agent.delete

Removes agent from memory and deletes associated pivots and screenshots.

Payload

{ "id": "<agent-uuid>" }

Response payload

{ "status": "removed" }

agent.note

Set a free-text note on an agent.

Payload

{ "id": "<agent-uuid>", "note": "lateral moved from webserver" }

Response payload

{ "status": "updated" }

Jobs

job.create

Dispatch a command to an agent.

Payload

{
  "agent_id": "<agent-uuid>",
  "type":     "<command>",
  "args":     ["arg1", "arg2"]
}
  • type — the command name (see commands.md)
  • args — optional string array of arguments; omit or pass [] for no-arg commands

Response payload

{ "message": "Created job <job-id> for agent <agent-uuid> at <timestamp>" }

Examples

{ "agent_id": "550e...", "type": "ls",         "args": ["C:\\Users"] }
{ "agent_id": "550e...", "type": "shell",       "args": ["whoami /all"] }
{ "agent_id": "550e...", "type": "sleep",       "args": ["60s"] }
{ "agent_id": "550e...", "type": "download",    "args": ["C:\\sensitive.txt"] }
{ "agent_id": "550e...", "type": "shellcode",   "args": ["<b64>", "self"] }
{ "agent_id": "550e...", "type": "exit",        "args": [] }

Broadcast to all agents using the sentinel UUID ffffffff-ffff-ffff-ffff-ffffffffffff.

jobs.list

Get jobs for an agent (active + last 50 completed).

Payload

{ "agent_id": "<agent-uuid>" }

Response payload — array of job objects

[
  {
    "id":       "<job-uuid>",
    "agent_id": "<agent-uuid>",
    "command":  "ls C:\\Users",
    "status":   "Complete",
    "created":  "2026-05-30T12:00:00Z",
    "sent":     "2026-05-30T12:00:05Z",
    "output":   "Volume in drive C...\n..."
  }
]

jobs.clear

Remove all unsent (queued) jobs for an agent.

Payload

{ "agent_id": "<agent-uuid>" }

Response payload

{ "status": "cleared" }

Listeners

listeners.list

Payload: none

Response payload — array of listener objects

[
  {
    "id":          "<listener-uuid>",
    "name":        "https-443",
    "protocol":    "https",
    "bind_addr":   "0.0.0.0:443",
    "status":      "Active",
    "description": ""
  }
]

listeners.options

Get default options for a given protocol.

Payload

{ "protocol": "https" }

Response payload — map of option names to default values

listener.create

The entire payload is the options map. All listener option keys go directly in payload.

Payload (HTTP/HTTPS example)

{
  "Protocol":    "https",
  "Name":        "https-443",
  "Interface":   "0.0.0.0",
  "Port":        "443",
  "PSK":         "change-me",
  "X509Cert":    "/etc/ssl/certs/server.crt",
  "X509Key":     "/etc/ssl/private/server.key",
  "Transforms":  "jwe,json",
  "Authenticator": "none",
  "Description": "Primary HTTPS listener"
}

See listeners.md for all options per listener type.

Response payload — listener object (same shape as listeners.list entries)

listener.start

Payload

{ "id": "<listener-uuid>" }

Response payload

{ "status": "started" }

listener.stop

Payload

{ "id": "<listener-uuid>" }

Response payload

{ "status": "stopped" }

listener.delete

Stops the listener (if running) then removes it.

Payload

{ "id": "<listener-uuid>" }

Response payload

{ "status": "deleted" }

Credentials

credentials.list

Payload: none

Response payload — array of credential objects

[
  {
    "id":       "<uuid>",
    "domain":   "CORP",
    "username": "administrator",
    "password": "Password1",
    "hash":     "aad3...",
    "source":   "Mimikatz",
    "agent_id": "<agent-uuid>",
    "created":  "2026-05-30T12:00:00Z"
  }
]

credential.create

Payload

{
  "domain":   "CORP",
  "username": "svc-account",
  "password": "",
  "hash":     "aad3b435b51404eeaad3b435b51404ee",
  "source":   "secretsdump",
  "agent_id": "<agent-uuid>"
}

All fields are optional except username. agent_id defaults to nil UUID.

Response payload — created credential object

credential.delete

Payload

{ "id": "<credential-id>" }

Screenshots

screenshots.list

Payload: none

Response payload

[
  {
    "id":       "<uuid>",
    "agent_id": "<agent-uuid>",
    "note":     "",
    "size":     142365,
    "created":  "2026-05-30T12:00:00Z"
  }
]

screenshots.image

Retrieve screenshot binary as base64.

Payload

{ "id": "<screenshot-uuid>" }

Response payload

{ "data": "<base64-encoded-PNG/JPEG>" }

screenshot.create

Manually record a screenshot (the screenshot job command does this automatically).

Payload

{
  "agent_id": "<agent-uuid>",
  "data":     "<base64>",
  "note":     "optional note"
}

screenshot.delete

Payload

{ "id": "<screenshot-uuid>" }

Topology

topology.get

Returns the full agent/listener network graph for visualisation.

Payload: none

Response payload

{
  "nodes": [
    { "id": "server",         "label": "fox3 server", "group": "server" },
    { "id": "<listener-uuid>","label": "https-443",   "group": "listener" },
    { "id": "<agent-uuid>",   "label": "DESKTOP-ABC", "group": "agent", "integrity": 3, "status": "Active" }
  ],
  "edges": [
    { "from": "server",         "to": "<listener-uuid>" },
    { "from": "<listener-uuid>","to": "<agent-uuid>" }
  ]
}

Pivots

pivots.list

Payload: none

Response payload

[
  {
    "id":              "<uuid>",
    "name":            "smb-pivot-1",
    "parent_agent_id": "<agent-uuid>",
    "child_agent_id":  "<agent-uuid>",
    "protocol":        "smb",
    "created":         "2026-05-30T12:00:00Z"
  }
]

pivot.create

Payload

{
  "name":            "smb-pivot-1",
  "parent_agent_id": "<agent-uuid>",
  "child_agent_id":  "<agent-uuid>",
  "protocol":        "smb"
}

pivot.delete

Payload

{ "id": "<pivot-uuid>" }

HVNC

HVNC (Hidden VNC) streams desktop frames as binary WebSocket messages. Start it by dispatching hvnc_start to an agent (Windows only), then manage the session with these actions.

hvnc.status

Payload

{ "agent_id": "<agent-uuid>" }

Response payload

{ "active": true, "conn_id": "<uuid>" }

hvnc.start

Dispatches hvnc_start job to the agent and registers the session.

Payload

{ "agent_id": "<agent-uuid>" }

hvnc.stop

Dispatches hvnc_stop job and unregisters the session.

Payload

{ "agent_id": "<agent-uuid>" }

hvnc.input

Send keyboard/mouse input to the hidden desktop.

Payload

{
  "agent_id": "<agent-uuid>",
  "type":     "mouse_click",
  "x":        100,
  "y":        200,
  "button":   "left"
}

hvnc.launch

Launch a process on the hidden desktop.

Payload

{
  "agent_id": "<agent-uuid>",
  "command":  "cmd.exe"
}

hvnc.quality

Adjust JPEG compression quality for the frame stream.

Payload

{
  "agent_id": "<agent-uuid>",
  "quality":  75
}

Push events

Events arrive unsolicited from the server to all connected WebSocket clients.

Event name When Payload
agent_checkin Agent checks in Full AgentResponse object
agent_removed Agent deleted {"agent_id": "<uuid>"}
job_complete Job finishes {"agent_id": "<uuid>", "jobs": [<JobResponse>…]}
listener_start Listener created or started {"listener_id": "<uuid>"}
listener_stop Listener stopped or deleted {"listener_id": "<uuid>"} or with "deleted": "true"

Error handling

All errors follow the same response envelope:

{
  "id":      "req-001",
  "type":    "response",
  "success": false,
  "error":   "invalid agent_id: not-a-uuid"
}

Common errors:

Error Cause
invalid agent_id UUID parse failure
agent not found Agent not in memory (may have been removed)
unknown action: <x> Typo in action name
invalid listener id UUID parse failure on listener ID