Skip to content
Command lineJSON output

JSON output

Every command's --json output is one envelope on stdout, with the same fields for every command. Read it from scripts, and follow long jobs with progress events.

On this page

Get JSON from any command

Add --json to any command. stdout then holds exactly one JSON document, even when the command fails. Human messages go to stderr, and so do progress events when you ask for them.

Terminal
census findings --safety safe --json
JSON
{
  "schema_version": 5,
  "command": "findings",
  "status": "ok",
  "exit_code": 0,
  "errors": [],
  "warnings": [],
  "results": {
    "findings": [],
    "totals": {
      "safe": { "allocated_bytes": 0, "count": 0 },
      "review": { "allocated_bytes": 0, "count": 0 },
      "keep": { "allocated_bytes": 0, "count": 0 }
    },
    "truncated": 0
  }
}

Options can go before or after the command, so census --json top is the same as census top --json. Help text is always plain text, never JSON.

The envelope

Every JSON document has these top-level fields:

FieldMeaning
schema_versionThe contract version. The current version is 5.
commandThe command as invoked, such as top or clean. The dupes alias reports duplicates. It is null when the arguments failed to parse before a command was known.
statusok, complete, partial, failed, not_found or cancelled.
exit_codeThe process’s exit code.
errorsFailures, as {code, message} entries. Empty when exit_code is 0, never empty otherwise.
warningsWarning strings.

Queries add scan, the saved scan they read, and results, which differs by command. Each command’s page shows its results.

Statuses

StatusExit codeMeaning
ok0A query succeeded, or a cleanup deleted everything it could. Skipped items are normal.
complete0A scan read everything in its scope.
failed1 or 2A bad request (1) or an operational failure (2).
partial4Some folders couldn’t be read, some items couldn’t be fully deleted, or a maintenance task failed.
not_found7There is no saved scan, or the path or app isn’t in it.
cancelled130Ctrl-C or SIGTERM stopped a long operation.

Exit codes and errors lists every error code.

Paths

Paths and names in query results are PathValue objects, because a macOS file name doesn’t have to be valid UTF-8:

FieldMeaning
displayThe path as text. Home folders appear as they are on your Mac, such as /Users/me.
lossytrue when display had to replace bytes that aren’t valid UTF-8.
bytes_hexThe exact bytes, in hex. Present only when display loses them.
JSON
{ "display": "/Applications/Xcode.app/Contents", "lossy": false }

review --json is the exception: its path is a plain string, because selection files for apply use exactly those strings.

Sizes

Sizes in results are whole bytes. allocated_bytes is space on disk, which is what deleting frees; logical_bytes is a file’s length. Sizes you pass as options, such as --min-size 500K, take bytes or a number followed by K, M or G, in powers of 1000, as macOS shows sizes.

Read JSON in a script

Check exit_code before reading results. These examples use jq:

Terminal
# The five largest files, size then path
census top --files --limit 5 --json \
  | jq -r '.results.files[] | "\(.allocated_bytes)\t\(.path.display)"'

# Stop with census's own message when a command fails
out=$(census findings --json)
if [ "$(jq -r .exit_code <<<"$out")" -ne 0 ]; then
  jq -r '.errors[0].message' <<<"$out" >&2
  exit 1
fi

The same in Python:

Python
import json
import subprocess

proc = subprocess.run(
    ["census", "findings", "--safety", "safe", "--json"],
    capture_output=True,
    text=True,
)
doc = json.loads(proc.stdout)  # stdout is JSON even when the command fails
if doc["exit_code"] != 0:
    raise SystemExit(doc["errors"][0]["message"])

safe = doc["results"]["totals"]["safe"]
print(f"{safe['count']} safe items, {safe['allocated_bytes'] / 1e9:.1f} GB")

When stdout closes early, as in census top | head -1, census exits quietly with status 141 (SIGPIPE), like other Unix tools.

Follow progress

Long jobs accept --progress. It writes one JSON record per line (NDJSON) to stderr, flushed as it goes, so stdout still holds only the result:

Terminal
census scan --json --progress 2>progress.ndjson

--progress works with scan, duplicates, exports that include duplicates, any query with --refresh, clean, review --refresh, apply, uninstall, maintain with tasks, optimize and empty-trash. Each record starts with event and elapsed_ms (milliseconds since the command started), then the fields of its phase:

PhaseFields
loading_cacheNone
replaying_changesNone
walkingdirectories_read, files_seen, allocated_bytes
summarizingNone
savingNone
hashingstage (starts or contents), files_done, files_total, bytes_read, bytes_total
deletingitems_done, items_total, bytes_freed: once before the first item, then after each
maintainingtask (null in the last record), tasks_done, tasks_total
JSON
{"event":"progress","elapsed_ms":1234,"phase":"walking","directories_read":1200,"files_seen":32000,"allocated_bytes":987654321}

A full scan’s last walking record matches the report’s metrics.files_seen, then come summarizing and, unless --no-save, saving. An incremental rescan reports loading_cache, replaying_changes, walking, summarizing and saving, and its walking totals count only the folders it read again.

Versioning

schema_version goes up only when a change breaks the contract. New fields can appear at any time without a new version, so ignore fields you don’t know. Version 5:

  • calls the scan command scan (it was scan.start);
  • adds exit_code to every envelope;
  • names the scan’s unreadable-path summary read_errors, leaving errors for command failures;
  • returns top --files --json results as {files, filters}.

Updated · census JSON schema 5