Packet Capturev7.0.20
Cut a pcapng capture straight from the appliance’s live monitoring stream: define an optional L4 filter, then either poll the growing capture in chunks or download the finished file.
Packet captures are cut directly from the appliance’s live monitoring stream — no separate capture process, no temporary tap to configure. You define an optional L4 filter (hosts, ports, protocols, VLANs, interfaces), the appliance starts writing matching packets to disk as a pcapng stream, and you either poll the growing capture in chunks or download the finished file. Captures are bounded by a byte cap and a deadline so a forgotten capture cannot fill the disk.
These endpoints belong to the capture functional area; an MCP or OAuth token needs the mcp:capture scope, and the calling user needs the capture permission (shown on each endpoint). The permission is enforced regardless of how the bearer token was obtained.
Captures are user-scoped and tag-scoped. A non-admin user sees and operates on only their own captures; requesting a capture you do not own returns 404, indistinguishable from a non-existent id. The sa (super-admin) role bypasses this and sees every capture. Tag-scoping folds in at the device-association layer where a capture targets a specific device.
Lifecycle. A capture moves through starting → running → stopped/expired/failed. Create returns a row in starting and transitions it to running once the capture engine accepts it. There is a cap on how many captures can run at once across the whole appliance; when it is reached, create returns 503 and no row is left behind. Stop is idempotent. A capture cannot be deleted while still active — stop it first.
Captures
List captures
GET/api/capturescapture
Returns the caller’s captures (or all captures for sa), newest first, capped at 200 rows.
Response 200
{
"captures": [
{
"id": 42,
"user_id": 3,
"device_id": 88,
"label": "web-debug",
"filter": { "ports": [443] },
"status": "running",
"started_at": "2026-06-15T14:00:00Z",
"ended_at": null,
"expires_at": "2026-06-16T14:00:00Z",
"packets": 1840,
"bytes": 2310455,
"byte_cap": 10485760,
"created_at": "2026-06-15T14:00:00Z"
}
]
}
Example
curl -sS https://APPLIANCE/api/captures \
-H "Authorization: Bearer $TOKEN"
Create a capture
POST/api/capturescapture
Starts a new capture against the live stream. The filter key must be present but may be an empty object {}, which means “capture every packet on the chosen interface(s)”. The response returns the capture row already transitioned to running.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
label | string | no | Free-text label, max 255. |
device_id | integer | no | Associate the capture with a device (must exist). |
byte_cap | integer | no | Stop after this many bytes. Min 65536, max 1073741824 (1 GB). Default 10485760 (10 MB). |
deadline_s | integer | no | Stop after this many seconds. Min 10, max 86400. Default 600. |
ephemeral | boolean | no | Mark as a short-lived poll-driven capture (reaped quickly when polling stops). |
filter | object | yes | Must be present; {} captures everything. |
filter.hosts | array of string | no | IP addresses to match (any direction). |
filter.ports | array of integer | no | TCP/UDP ports, 1–65535. |
filter.protocols | array of string | no | Any of tcp, udp, icmp. |
filter.vlans | array of integer | no | VLAN ids, 0–4095. |
filter.ifaces | array of string | no | Interface names to restrict to, max 32 chars each. |
Response 201
{
"capture": {
"id": 42,
"user_id": 3,
"label": "web-debug",
"filter": { "ports": [443] },
"status": "running",
"started_at": "2026-06-15T14:00:00Z",
"byte_cap": 10485760
}
}
Errors
| Status | Body | When |
|---|---|---|
422 | {"message":...,"errors":{...}} | Validation failed. |
422 | {"status":"error","reason":"…","message":"Filter or interface invalid."} | The capture engine rejected the filter or interface. |
503 | {"status":"error","reason":"too-many","message":"Maximum concurrent captures reached. Stop one and try again."} | Concurrent-capture cap reached; no row is created. |
502 | {"status":"error","message":"capture daemon unavailable"} | Capture engine unreachable. |
Example
curl -sS -X POST https://APPLIANCE/api/captures \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"label":"web-debug","filter":{"ports":[443],"protocols":["tcp"]}}'
Get a capture
GET/api/captures/{id}capture
Returns one capture’s status row. If the capture is still starting or running, its packet/byte counters and status are refreshed live before returning.
Path parameters
| Name | Type | Notes |
|---|---|---|
id | integer | Capture id. Out-of-scope or unknown ids return 404. |
Response 200
{ "capture": { "id": 42, "status": "running", "packets": 1840, "bytes": 2310455 } }
Errors
| Status | Body | When |
|---|---|---|
404 | {"status":"error","message":"not found"} | Unknown id, or a capture you do not own. |
Stop a capture
POST/api/captures/{id}/stopcapture
Stops a running capture and finalizes its packet/byte totals. Idempotent — stopping an already-stopped, expired, or failed capture simply returns its current row.
Path parameters
| Name | Type | Notes |
|---|---|---|
id | integer | Capture id. |
Response 200
{ "capture": { "id": 42, "status": "stopped", "ended_at": "2026-06-15T14:05:00Z", "packets": 5210, "bytes": 7340032 } }
Errors
| Status | Body | When |
|---|---|---|
404 | {"status":"error","message":"not found"} | Unknown id, or not yours. |
Delete a capture
DELETE/api/captures/{id}capture
Removes the capture row, cascade-deletes its chunk pointers, and removes the on-disk chunk files. The capture must be stopped (or expired/failed) first.
This permanently deletes the capture and all of its chunk files. The captured packets cannot be recovered afterward.
Path parameters
| Name | Type | Notes |
|---|---|---|
id | integer | Capture id. |
Response 200
{ "status": "ok" }
Errors
| Status | Body | When |
|---|---|---|
404 | {"status":"error","message":"not found"} | Unknown id, or not yours. |
409 | {"status":"error","reason":"still-active","message":"Stop the capture before deleting."} | Capture is still starting or running. |
Capturable interfaces
List interfaces
GET/api/captures/interfacescapture
Returns the appliance’s configured capture interface names — the set you may reference in filter.ifaces when creating a capture.
Response 200
{ "interfaces": ["eth0", "eth1"] }
Example
curl -sS https://APPLIANCE/api/captures/interfaces \
-H "Authorization: Bearer $TOKEN"
Polling chunks
Poll new chunks
GET/api/captures/{id}/chunkscapture
Polls for capture data written since a given sequence number, for streaming consumers (such as the Wireshark extcap integration). Each call returns the new chunks (base64-encoded pcapng fragments) and the highest sequence number it covered; pass that back as since on the next poll. The response is capped at 4 MB of chunk data per call, so a busy capture may take several polls to drain — keep polling while status is running. The first poll (since=0) also returns the pcapng section header block (pcapng_shb) so the consumer has a complete, valid pcapng stream.
Polling also serves as a liveness signal: ephemeral captures that stop being polled are reaped automatically.
Path parameters
| Name | Type | Notes |
|---|---|---|
id | integer | Capture id. |
Query parameters
| Name | Type | Notes |
|---|---|---|
since | integer | Return chunks with seq greater than this. Default 0 (also requests the SHB blob). |
Response 200
{
"status": "running",
"last_seq": 12,
"pcapng_shb": "TVCq+gEAAAA…",
"chunks": [
{ "seq": 1, "data": "BgAAAA…" },
{ "seq": 2, "data": "BgAAAA…" }
]
}
The pcapng_shb field is present only on the first poll (since=0). data and pcapng_shb are base64-encoded raw pcapng bytes.
Errors
| Status | Body | When |
|---|---|---|
404 | {"status":"error","message":"not found"} | Unknown id, or not yours. |
Example
curl -sS "https://APPLIANCE/api/captures/42/chunks?since=0" \
-H "Authorization: Bearer $TOKEN"
Downloading
Download the pcapng file
GET/api/captures/{id}/download.pcapngcapture
Streams the complete capture as a single pcapng file: the section header block followed by every chunk in sequence order. The body is streamed (not buffered), so even a 1 GB capture downloads without loading into memory. Pipe it to a file and open it in Wireshark.
The response is a binary file, not JSON:
Content-Type: application/vnd.tcpdump.pcapContent-Disposition: attachment; filename="<label-slug>.pcapng"— the filename is derived from the capture label (orcapture-<id>when unlabeled).
Path parameters
| Name | Type | Notes |
|---|---|---|
id | integer | Capture id. |
Errors
| Status | Body | When |
|---|---|---|
404 | {"status":"error","message":"not found"} | Unknown id, or not yours. |
Example
curl -sS https://APPLIANCE/api/captures/42/download.pcapng \
-H "Authorization: Bearer $TOKEN" \
-o web-debug.pcapng