-
Notifications
You must be signed in to change notification settings - Fork 0
Output Formats
sipnab has four output modes: interactive TUI (default), per-message CLI
text (-N), structured NDJSON (--json), and the MCP server.
This page documents the machine-readable formats.
Prerequisites:
--jsonrequires non-interactive mode (-N/--no-tui) — sipnab refuses to start otherwise (exception:--call-reportimplies non-interactive output). No extra build feature is needed: NDJSON output is part of the default build. Full flag reference: CLI reference.
--json emits one JSON object per SIP message — newline-delimited, so
each line is independently parseable and the stream is pipe-friendly:
sipnab -N -I capture.pcap --json | jq .Message record (fields with no value are omitted, not null):
{
"schema_version": 1,
"timestamp": "2026-06-12T14:03:21.412345+00:00",
"src": "192.0.2.1",
"src_port": 5060,
"dst": "192.0.2.2",
"dst_port": 5060,
"transport": "UDP",
"is_request": true,
"method": "INVITE",
"call_id": "abc123@192.0.2.1",
"from": "\"Alice\" <sip:1001@192.0.2.1>;tag=1c145053",
"to": "<sip:1002@192.0.2.2>",
"contact": "<sip:1001@192.0.2.1:5060>",
"ua": "FreePBX-16",
"sdp": "v=0\r\no=- 1 1 IN IP4 192.0.2.1\r\ns=-\r\nc=IN IP4 192.0.2.1\r\nt=0 0\r\nm=audio 40000 RTP/AVP 0 96\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:96 telephone-event/8000\r\n",
"cseq": { "number": 1, "method": "INVITE" }
}A response record carries is_request: false plus status_code,
reason, and response_context instead of method:
{
"schema_version": 1,
"timestamp": "2026-06-12T14:03:21.598204+00:00",
"src": "192.0.2.2",
"src_port": 5060,
"dst": "192.0.2.1",
"dst_port": 5060,
"transport": "UDP",
"is_request": false,
"status_code": 180,
"reason": "Ringing",
"call_id": "abc123@192.0.2.1",
"from": "\"Alice\" <sip:1001@192.0.2.1>;tag=1c145053",
"to": "<sip:1002@192.0.2.2>;tag=782609",
"cseq": { "number": 1, "method": "INVITE" },
"response_context": "1 INVITE"
}from and to are the full From / To header values — display
name, URI and tags included — not the bare user part. (The aggregated
dialog object below is the one that carries the user part.) Match them
with a regex or a substring test, not with equality.
cseq (the parsed CSeq header — { number, method }) is emitted on every
message, requests included, so re-requests within a dialog (e.g. two REGISTERs
with CSeq 1 and 2) stay distinguishable. It is omitted only when the CSeq
header is absent or unparseable.
contact is the Contact header value when present (routing-critical, omitted
otherwise). sdp is the raw SDP body, emitted only when Content-Type is
application/sdp and the body is valid UTF-8 — it lets a consumer verify the
negotiated media (connection / m= / a=rtpmap) that dynamic-PT decode depends
on; omitted for non-SDP or non-UTF-8 bodies.
response_context ("<num> <method>", responses only — what the response
answers) is a deprecated alias of cseq, retained for backward compatibility
under schema_version 1. Prefer cseq.
malformed is a list of structural-defect diagnostics, present only when a
message is malformed (a well-formed message omits the field). It surfaces crafted
or broken input rather than silently accepting it: missing mandatory headers
(Call-ID/CSeq/From/To/Via), an unparseable CSeq, a Content-Length
larger than the body actually present (truncated/lying length), and control/NUL
bytes in a header. Example: "malformed": ["missing mandatory header: Call-ID"].
schema_version increments on breaking field changes — pin your
consumers to it.
One recipe per question, each a complete pipeline — run the one you want, not the whole list.
Keep only the INVITEs:
sipnab -N -I capture.pcap --json | jq 'select(.method == "INVITE")'Find the calls placed by one user. from carries the whole From header, so
test it for a substring rather than comparing it for equality:
sipnab -N -I capture.pcap --json | jq 'select(.from // "" | test("sip:1001@"))'Count the messages per method, to see what a capture is made of before reading any of it:
sipnab -N -I capture.pcap --json \
| jq -s 'group_by(.method) | map({method: .[0].method, n: length})'Pull the failed responses (4xx/5xx/6xx) with the reason each one carries:
sipnab -N -I capture.pcap --json \
| jq 'select(.status_code != null and .status_code >= 400)
| {ts: .timestamp, code: .status_code, reason, call_id}'List the distinct Call-IDs seen — this is the list you feed into
--call-report:
sipnab -N -I capture.pcap --json | jq -r '.call_id' | sort -uBuild a response-code histogram, counting how many of each status code the capture holds:
sipnab -N -I capture.pcap --json \
| jq -r 'select(.is_request == false) | .status_code' \
| sort | uniq -c | sort -rn--json prints a line per message. For end-of-run summaries instead,
combine the report flags with
--no-cli-print (which suppresses the
per-message stream but not the report).
For the aggregate report over the whole capture, and nothing else:
sipnab -N -I capture.pcap --report --no-cli-printFor a deep dive into a single call, named by its Call-ID:
sipnab -N -I capture.pcap --call-report 'abc123@192.0.2.1' --no-cli-printThe richer aggregated dialog object — state, timing (PDD / setup /
ring / teardown milliseconds, retransmit counts), sdp_timeline,
streams with jitter and loss, and the diagnosis flags (one_way_audio,
nat_mismatch, no_media) plus hints — is one shape produced by a single
serializer. It is documented once, with a full worked example, under
GET /v1/dialogs/{call_id} in the REST API reference.
The same document is what you get from:
- the REST
GET /v1/dialogs/{call_id}andGET /v1/dialogs/{call_id}/reportendpoints, - MCP tool responses (
get_dialog_reportwithformat: "json"— see the MCP server reference), and -
SIPNAB_JSONin--on-dialog-exechooks. Note the dialog hook fillsstreams: []and a defaultdiagnosis— it fires on a dialog event, when media analysis for that call may not be complete, so the stream and diagnosis fields are placeholders there rather than populated data.
--on-quality-exec is separate: it passes the stream object on its own,
under its own variable name — SIPNAB_STREAM_JSON, not SIPNAB_JSON.
-O <file> writes captured packets; --pcapng selects PCAP-NG. With TLS
decryption, --pcap-export-mode controls whether decryption secrets
(DSBs) are embedded for Wireshark. Rotation: --split filesize:N /
--split duration:N, or SIGUSR1 on demand.
pcapng timestamps are nanosecond-resolution, declared via if_tsresol=9
in the Interface Description Block. Files written by sipnab <= 0.5.0
stored nanosecond ticks but omitted that declaration, so other readers
inflated every time value ×1000 (capinfos reporting year 58484);
scripts/repair_pcapng_tsresol.py
repairs such old captures in place without touching packet data.
- cli-reference.md — every output flag
- examples.md — NDJSON-to-jq pipeline recipes
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
Using the TUI
CLI & automation
Configuration
Integrations (API & MCP)
Development & internals