Suricata
The Suricata API gives you the alerts the Suricata intrusion detection engine raises on a capture file, as a ranked triage summary or as the individual alerts behind it. It is the same data as the Threat Assessment view in CloudShark, in a form scripts, dashboards, and security tools can use.
Authentication
All endpoints require an API token with the
PCAP analysis permission enabled, and the token’s user must be able to view
the capture. Pass the token in the Authorization header:
Authorization: <token>
The examples below use these values:
${CLOUDSHARK_URL}: CloudShark instance URL.- Example:
https://cloudshark.local
- Example:
${CAPTURE_ID}: CloudShark capture ID.- Example:
ea238bd44aaa
- Example:
${API_TOKEN}: API token with PCAP analysis enabled.- Example:
82f2230b777174f098623e4a5203e4ce
- Example:
Scanning
You never have to start a scan yourself. If Suricata has not scanned a capture yet, or the ruleset has been updated since it last did, the first request for that capture runs the scan and answers when it is done. Later requests read the stored results.
If a scan of the capture is already running, for example because another user just opened it in Threat Assessment, the request waits for that scan to finish rather than returning partial results.
A scan can take a while on a large capture, so allow for a slow first response.
Why a summary
A busy capture can raise hundreds of alerts, and most rulesets include many rules that describe ordinary traffic: an operating system checking its network connection, a browser using DNS over HTTPS, a software update. Sorted by count or time, the one alert that matters ends up buried under the ones that fire constantly.
The summary groups alerts by signature, so each rule appears once with its alert count, and ranks the groups so the most serious come first:
- Priority. How serious the ruleset rates the kind of activity, taken from
Suricata’s own severity for the rule:
High,Medium,Low, orOther. - Severity. The rule author’s own rating, where the rule declares one:
Critical,Major,Minor, orInformational. A rule its author callsInformationalis a benign observation, so it ranks one priority band lower than its priority alone would put it. - Behavior. How the traffic behaved in this capture. Alerts that recur on a regular interval, like a beacon checking in, rank above ones that don’t, and alerts spread across at least half the capture rank above a single burst.
- Count, then the signature ID, so the order is the same every time.
Each step only breaks ties in the one before it. A serious alert that fired once always ranks above a minor one that fired a thousand times, because volume never outranks seriousness.
The summary also keeps two kinds of noise out of the ranked list:
- Engine events such as
SURICATA STREAM excessive retransmissionsdescribe how Suricata saw the traffic, not a threat. They are listed separately as anomalies. - Long tails are capped. Each finding shows up to five flows and five
HTTP, DNS, TLS, and file entries, and the host list shows up to 20 hosts.
When anything is cut,
truncationsays what.
If you want every alert to filter or count your own way, the alert list has them all.
The absence of alerts is weak evidence. Suricata can only see what its rules describe, and encrypted traffic to an unknown server usually matches nothing. An empty summary means no rule matched, not that the capture is clean.
Endpoints
Get the summary
GET /api/v2/suricata/:id/alerts/summary
Returns the ranked triage summary for the capture.
Response: 200 OK (shortened to the first finding)
{
"capture": {
"alerts": 9,
"window": {
"start": "2026-10-01T19:27:27.845Z",
"end": "2026-10-01T19:30:55.252Z",
"duration_seconds": 207.407442
}
},
"findings": [
{
"sid": 2018959,
"signature": "ET INFO PE EXE or DLL Windows file download HTTP",
"category": "Potential Corporate Privacy Violation",
"priority": "High",
"severity": "Informational",
"rule": { "family": "INFO", "confidence": "High", "tags": null },
"behavior": {},
"count": 1,
"n_flows": 1,
"n_src": 1,
"n_dst": 1,
"first": "2026-10-01T19:30:37.031Z",
"last": "2026-10-01T19:30:37.031Z",
"flows": [
{
"community_id": "1:c8V1cQkZy/GD6rcc/qORahY2s6Y=",
"src_ip": "10.10.1.128",
"src_port": 63723,
"dest_ip": "107.175.82.242",
"dest_port": 9000,
"proto": "tcp",
"app_proto": "http",
"alerts": 1,
"wire_bytes": { "up": 1389, "down": 48148 }
}
],
"http": [
{
"n": 1,
"method": "GET",
"host": "107.175.82.242",
"url": "/wilow/runner_ilove.exe",
"ua": "curl/8.21.0",
"status": 200
}
],
"dns": null,
"tls": null,
"files": [
{ "n": 1, "filename": "/wilow/runner_ilove.exe", "max_size": 44200 }
]
}
],
"anomalies": [
{
"sid": 2221036,
"signature": "SURICATA HTTP Response excessive header repetition",
"reason": "suricata-engine-event",
"count": 1,
"n_flows": 1,
"sample_community_ids": ["1:c8V1cQkZy/GD6rcc/qORahY2s6Y="]
}
],
"hosts": [
{
"ip": "10.10.1.128",
"scope": "internal",
"high": 1,
"medium": 3,
"low": 3,
"anomaly": 2,
"n_peers": 4
}
],
"truncation": null
}
Summary fields
| Field | Description |
|---|---|
capture |
Total alerts and the time span they cover. |
findings |
One entry per signature, most serious first. See below. |
anomalies |
Suricata engine events, with their counts and up to three sample Community IDs. |
hosts |
Each IP involved in an alert, with its alert counts by priority, whether it is an internal (private) or external address, and how many peers it had alerts with. Most involved first. |
truncation |
What was capped, if anything; null when the summary is complete. |
Finding fields
| Field | Description |
|---|---|
sid, signature, category |
The rule that fired. Use sid to list its alerts. |
priority |
High, Medium, Low, or Other, from Suricata’s severity for the rule. |
severity |
The rule author’s signature_severity, or empty when the rule doesn’t declare one. |
rule |
What the rule’s metadata says: its ruleset family (such as MALWARE or INFO), the author’s confidence, and the malware_family, target, and tags where present. |
behavior |
periodic_seconds when the alerts recur on a regular interval (at least ten alerts), and span_pct, how much of the capture lies between the first and last alert. |
count, n_flows, n_src, n_dst |
Alerts, flows, source IPs, and destination IPs for this signature. |
first, last |
Time of the first and last alert. |
flows |
The flows the rule fired on, each with its Community ID and the bytes sent each way. |
http, dns, tls, files |
What the alerts saw at the application layer: requests, lookups, TLS server names and certificates, and file names and sizes. |
List alerts
GET /api/v2/suricata/:id/alerts
Returns individual alerts in time order, with the total that matched.
| Parameter | Type | Description |
|---|---|---|
sid |
integer | Only alerts from this signature. |
community_id |
string | Only alerts on this flow. |
limit |
integer | Maximum alerts to return. Defaults to 100. |
offset |
integer | Alerts to skip before the returned ones. |
sid and community_id can be combined. Each alert includes an id, which
you can use to get the alert.
The id is not assigned by Suricata. It is the alert’s position among all the
alerts in the capture’s current scan, counting from 0, so it stays the same
whatever filter you list with. Because it is only a position, it identifies an
alert within one scan: when the capture is scanned again, for example after a
ruleset update, the same id can refer to a different alert. Don’t store it
as a permanent reference.
Response: 200 OK
{
"alerts": [
{
"id": 5,
"timestamp": "2026-10-01T19:30:37.031Z",
"signature_id": 2018959,
"rev": 6,
"signature": "ET INFO PE EXE or DLL Windows file download HTTP",
"severity": 1,
"src_ip": "107.175.82.242",
"src_port": 9000,
"dest_ip": "10.10.1.128",
"dest_port": 63723,
"app_proto": "http",
"community_id": "1:c8V1cQkZy/GD6rcc/qORahY2s6Y=",
"indicator": {
"http.hostname": "107.175.82.242",
"http.http_user_agent": "curl/8.21.0",
"http.url": "/wilow/runner_ilove.exe"
}
}
],
"total": 1
}
severity here is Suricata’s number for the rule, 1 being the most serious;
the summary’s priority is the same value as a word. indicator holds the
most useful application-layer fields for the alert.
Get an alert
GET /api/v2/suricata/:id/alerts/:alert_id
Returns one alert in full, including the text of the rule that raised it and everything Suricata recorded about the flow.
Response: 200 OK
{
"id": 5,
"timestamp": "2026-10-01T19:30:37.031Z",
"src_ip": "107.175.82.242",
"src_port": 9000,
"dest_ip": "10.10.1.128",
"dest_port": 63723,
"proto": "TCP",
"app_proto": "http",
"community_id": "1:c8V1cQkZy/GD6rcc/qORahY2s6Y=",
"flow_id": 1174607991823444,
"alert": {
"signature_id": 2018959,
"rev": 6,
"signature": "ET INFO PE EXE or DLL Windows file download HTTP",
"category": "Potential Corporate Privacy Violation",
"severity": 1,
"rule": "alert http $EXTERNAL_NET any -> $HOME_NET any (msg:\"ET INFO PE EXE or DLL Windows file download HTTP\"; flow:established,to_client; flowbits:isnotset,ET.http.binary; flowbits:isnotset,ET.INFO.WindowsUpdate; flowbits:set,ET.http.binary; file.data; content:\"MZ\"; within:2; byte_jump:4,58,relative,little; content:\"PE|00 00|\"; distance:-64; within:4; classtype:policy-violation; sid:2018959; rev:6; metadata:created_at 2014_08_19, confidence High, signature_severity Informational, updated_at 2024_03_14;)"
},
"http": {
"hostname": "107.175.82.242",
"method": "GET",
"url": "/wilow/runner_ilove.exe",
"user_agent": "curl/8.21.0",
"status": 200
},
"files": [
{ "filename": "/wilow/runner_ilove.exe", "size": 44200, "state": "UNKNOWN" }
]
}
Examples
The examples use curl and the jq JSON parser.
Triage a capture
The ranked findings, one line each:
curl --request GET \
--header "Authorization: ${API_TOKEN}" \
--silent \
--url "${CLOUDSHARK_URL}/api/v2/suricata/${CAPTURE_ID}/alerts/summary" \
| jq -r '.findings[] | [.priority, .severity, .count, .sid, .signature] | @tsv'
Output
High Informational 1 2018959 ET INFO PE EXE or DLL Windows file download HTTP
Medium Informational 1 2016141 ET INFO Executable Download from dotted-quad Host
Medium Informational 1 2021076 ET HUNTING SUSPICIOUS Dotted Quad Host MZ Response
Medium Informational 1 2034567 ET HUNTING curl User-Agent to Dotted Quad
Low Informational 1 2014819 ET INFO Packed Executable Download
Low Informational 1 2031071 ET INFO Microsoft Connection Test
Low Informational 1 2047866 ET INFO Observed Google DNS over HTTPS Domain (dns .google in TLS SNI)
The top five findings describe the same event from different angles: a Windows
executable fetched with curl from a bare IP address on port 9000. The
Microsoft connection test and Google DNS over HTTPS rank last, where routine
traffic belongs.
Hosts most involved
Which addresses appear in the most serious alerts, and whether each is on your network:
curl --request GET \
--header "Authorization: ${API_TOKEN}" \
--silent \
--url "${CLOUDSHARK_URL}/api/v2/suricata/${CAPTURE_ID}/alerts/summary" \
| jq -c '.hosts[] | {ip, scope, high, medium, low, n_peers}'
Output
{"ip":"10.10.1.128","scope":"internal","high":1,"medium":3,"low":3,"n_peers":4}
{"ip":"107.175.82.242","scope":"external","high":1,"medium":3,"low":1,"n_peers":1}
{"ip":"23.219.87.72","scope":"external","high":null,"medium":null,"low":1,"n_peers":1}
{"ip":"8.8.4.4","scope":"external","high":null,"medium":null,"low":1,"n_peers":1}
{"ip":"195.63.128.106","scope":"external","high":null,"medium":null,"low":null,"n_peers":1}
Counts that are zero are left out of the response, which jq shows as null.
Every alert on one flow
A finding’s flows give the Community ID of each flow it fired on. List every
alert on that flow, from any rule, in time order:
curl --request GET \
--header "Authorization: ${API_TOKEN}" \
--silent \
--get \
--data-urlencode "community_id=1:c8V1cQkZy/GD6rcc/qORahY2s6Y=" \
--url "${CLOUDSHARK_URL}/api/v2/suricata/${CAPTURE_ID}/alerts" \
| jq -r '.alerts[] | [.id, .timestamp, .severity, .signature] | @tsv'
Output
2 2026-10-01T19:30:36.887Z 2 ET INFO Executable Download from dotted-quad Host
3 2026-10-01T19:30:36.887Z 2 ET HUNTING curl User-Agent to Dotted Quad
4 2026-10-01T19:30:36.887Z 3 SURICATA HTTP Response excessive header repetition
5 2026-10-01T19:30:37.031Z 1 ET INFO PE EXE or DLL Windows file download HTTP
6 2026-10-01T19:30:37.031Z 2 ET HUNTING SUSPICIOUS Dotted Quad Host MZ Response
7 2026-10-01T19:30:37.031Z 3 ET INFO Packed Executable Download
Use --data-urlencode for Community IDs: they are base64 and can contain +
and /.
Read the rule behind an alert
Get one alert and print the rule that raised it, to see exactly what Suricata matched:
curl --request GET \
--header "Authorization: ${API_TOKEN}" \
--silent \
--url "${CLOUDSHARK_URL}/api/v2/suricata/${CAPTURE_ID}/alerts/5" \
| jq -r '.alert.rule'
Output
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ET INFO PE EXE or DLL Windows file download HTTP"; flow:established,to_client; flowbits:isnotset,ET.http.binary; flowbits:isnotset,ET.INFO.WindowsUpdate; flowbits:set,ET.http.binary; file.data; content:"MZ"; within:2; byte_jump:4,58,relative,little; content:"PE|00 00|"; distance:-64; within:4; classtype:policy-violation; sid:2018959; rev:6; metadata:created_at 2014_08_19, confidence High, signature_severity Informational, updated_at 2024_03_14;)
The rule looks for the MZ and PE headers of a Windows executable in an HTTP
response body.
Your own filtering
The alert list returns every alert, so you can filter on anything in it. For
example, only the most serious alerts, Suricata severity 1:
curl --request GET \
--header "Authorization: ${API_TOKEN}" \
--silent \
--url "${CLOUDSHARK_URL}/api/v2/suricata/${CAPTURE_ID}/alerts?limit=1000" \
| jq -c '.alerts[] | select(.severity == 1) | {timestamp, signature, src_ip, dest_ip}'
Output
{"timestamp":"2026-10-01T19:30:37.031Z","signature":"ET INFO PE EXE or DLL Windows file download HTTP","src_ip":"107.175.82.242","dest_ip":"10.10.1.128"}
Compare total with the number of alerts returned to check you have them all,
and use offset to fetch the rest.
From an alert to Zeek and the packets
The Community ID connects a Suricata alert to the same flow in
Zeek’s conn.log, and from there to the packets
themselves. Find the Zeek connection for the flow above:
curl --request POST \
--header "Authorization: ${API_TOKEN}" \
--silent \
--data '{
"fields": ["uid", "id.orig_h", "id.resp_h", "id.resp_p", "service", "duration", "resp_bytes"],
"filters": [{ "field": "community_id", "op": "eq", "value": "1:c8V1cQkZy/GD6rcc/qORahY2s6Y=" }]
}' \
--url "${CLOUDSHARK_URL}/api/v2/zeek/${CAPTURE_ID}/logs/conn.log/query"
Output
# matched: 1 groups: 1 returned: 1 omitted: 0
uid id.orig_h id.resp_h id.resp_p service duration resp_bytes
CDFUSj3EEz8oxoKuFb 10.10.1.128 107.175.82.242 9000 http 2.141484 1647168
Then turn the uid into a display filter with Zeek’s
connection filters
endpoint, and read the packets with the
analysis API.
Errors
Errors are returned as JSON with the message in exceptions:
{ "exceptions": ["API Token is not allowed to execute analysis method."], "status": 403 }
| Status | Cause |
|---|---|
403 |
The token does not have the PCAP analysis permission. |
400 |
The capture does not exist or the token’s user cannot view it, Suricata cannot scan the file (for example a compressed capture), the scan did not finish in time, or the alert ID does not exist. The message explains which. |
404 |
The path is not one of the endpoints above. |