Skip to content

JSON output: schema-v1

--json makes a command emit one JSON object on stdout instead of the human render. Every payload carries "schemaVersion": 1; the version only bumps on a breaking change, and fields are additive; consumers must ignore unknown keys.

stdout stays pure JSON: progress and warnings go to stderr, and handled failures emit the error envelope on stdout (so piped consumers always get parseable JSON on handled errors).

Error envelope

{
  "schemaVersion": 1,
  "error": { "type": "ValueError", "message": "…" }
}

error.type is the stable tag to match on: KeyError (unknown account), ValueError (validation), UnsupportedClaudeVersion (contract gate), TimeoutError (lock bound exceeded), RootRefused (euid 0 outside a container). Exit code is 1 for all of them.

Per-command payloads

cam list --json: registry rows plus cached usage:

{
  "schemaVersion": 1,
  "active": "work",
  "accounts": [
    {
      "name": "work", "email": "…", "accountUuid": "…",
      "organizationUuid": "…", "organizationName": "…",
      "isOrganization": true, "active": true, "enabled": true,
      "quarantined": false,
      "usageStatus": "ok", "usage": { … }, "usageFetchedAt": "…", "usageAgeSeconds": 12.3
    }
  ]
}

cam status --json: the live login. {"schemaVersion": 1, "active": null} when no claude login exists; otherwise active holds identity fields plus managed (in cam's registry?) and, when managed, managedAs and the same usage fields as a list row.

cam usage <name> --json:

{
  "schemaVersion": 1, "account": "work",
  "usageStatus": "ok | unavailable | relogin_required",
  "usage": null,
  "stale": false, "usageError": null,
  "permanentAuthError": false, "quarantined": false
}

usage is {"fiveHour": {"pct": …, "resetsAt": …}, "sevenDay": {…}, "scoped": [{"name": …, "pct": …}]} or null. Rows without trusted usage may carry display-grade lastGoodUsage/lastGoodFetchedAt/ lastGoodAgeSeconds; rows in backoff carry usageRetryAt.

cam switch --json reports the transaction result: dryRun, switched, outcome, from/to as {"name", "email"} or null, unmanagedLive, preservedTo, strategy, skipped ([{"name", "reason"}]), quarantined, message.

cam config get/list --json: {"key", "value", "isSet"} for one key, or {"path", "settings": [{"key", "value", "isSet"}]} for the full table.

cam auto --json: JSONL events

The loop can't stream one dict, so it emits one object per line:

{"schemaVersion": 1, "event": "poll", "ts": "…", "active": "work", "headroomPct": 12.3, "threshold": 90.0}
{"schemaVersion": 1, "event": "switch", "ts": "…", "trigger": "…", "from": "work", "to": "personal", "dryRun": false}

event is one of poll, switch, no_switch, quarantined, all_exhausted, sleep, error, update-available; the remaining fields are per-kind. update-available fires once per run when PyPI reports a newer cam: {"currentVersion", "latestVersion", "releaseType"} with releaseType one of patch, minor, major. It is advisory and never self-applies. Consumers must ignore unknown event kinds and unknown fields.