JSON output
Every command accepts the global --json flag for machine consumption.
The contract
The result payload is printed to stdout as a single JSON document.
Notes, warnings and progress go to stderr — never merge the two streams into one parser (no
2>&1).Errors produce a JSON error object and a non-zero exit code (see Exit codes).
Secrets never appear in any output stream — every transcript and log line passes a redaction layer that masks PINs, keys and other registered secrets.
--json is a global option: it goes before the command
(cryptnox-id --json fido info), not after it.
Result envelope
On success, each command writes one JSON object whose fields are specific to
that command. For example, cryptnox-id --json doctor:
{
"checks": [
{"check": "PC/SC service", "status": "ok", "detail": "1 reader(s) detected"},
{"check": "Card present", "status": "ok", "detail": "ATR 3BFA1300..."}
],
"ok": true
}
Read-only inspection commands (info, piv info/status, fido info,
mifare info …) each return the same fields their human output shows,
serialized as an object. Write commands return a small confirmation object
(what was done, and any public identifier produced — never a secret).
Error object
On failure the payload is an error object and the process exits non-zero:
{
"error": "card_access_denied",
"message": "FIDO2 access was blocked by Windows. ..."
}
error is a stable token (see Exit codes for the token/exit-code
taxonomy); message is human-readable. Errors that carry an ISO status word
add three fields:
{
"error": "status_word_error",
"message": "...",
"sw": "6A82",
"sw_name": "FILE_NOT_FOUND",
"context": "SELECT PIV"
}
Snapshot reports
report card / report piv / report mifare / report fido /
report genuine / report full emit secret-safe JSON snapshots of the
detected state, suitable for fleet inventory or support tickets (--out FILE
writes to a file). These are the primary machine-consumption shapes and are kept
stable.
Every report is wrapped in a common envelope:
{
"generated_by": "cryptnox-id 0.1.0",
"generated_at": "2026-08-18T12:00:00+00:00",
"safe_to_share": true,
"<section>": {}
}
report full carries all sections at once; the single-function reports carry
just theirs. The section shapes:
card
{"reader": "<reader name>", "state": {}, "cplc": "<hex or null>"}
piv
{
"state": "PivPersonalized",
"apt": {"aid": "...", "label": "OpenFIPS201", "url": "..."},
"pin": {"configured": true, "retries": 6},
"puk": {"configured": true, "retries": 6},
"objects_present": ["chuid", "ccc", "auth-cert"],
"notes": []
}
apt / pin / puk are null when not available.
mifare — one of:
{"state": "DesfireReachable", "version": {}, "free_memory": 1234,
"applications": ["CC0102"]}
{"state": "DesfireNeedsContactlessReader",
"note": "DESFire ... use a DESFire-capable contactless PC/SC reader."}
{"state": "DesfireNoAnswerContactless",
"note": "DESFire did not answer on this contactless interface. Re-present the card ..."}
DesfireNeedsContactlessReader means the session is on a contact interface, which
cannot reach DESFire at all; DesfireNoAnswerContactless means the interface is
already contactless (reader name / ATR evidence) and the card simply did not answer —
re-present the card, and check the reader passes native DESFire APDUs.
fido — one of:
{"state": "FidoPersonalized", "select_version": "U2F_V2", "get_info": {}}
{"state": "FidoBlockedByOS", "note": "<Windows elevation guidance>"}
{"state": "FidoNotPresent"}
genuine — one of:
{"state": "GenuinenessPersonalized", "leaf_subject": "...",
"info": "<hex>", "note": "state only; run `genuine verify` to prove ..."}
{"state": "GenuinenessNotPresent",
"note": "genuineness applet not found (contact-only; absent over contactless)."}
A section may also report {"state": "Unknown", "error": "<message>"} when a
probe fails. Nothing in any report contains a PIN, key, or private material —
safe_to_share is always true.