Claude Code Output Format: JSON & Stream

Use claude -p --output-format with json, stream-json, and text in scripts and CI: one JSON object, NDJSON events, --json-schema, and jq recipes (2026).

Updated 2026-10-20

Need an API key? Sign up →Endpoints & base URLs →

On This Page

Last verified: 2026-10-20 · 📄 Per official docs (Claude Code CLI reference and headless docs, checked 2026-10-20)

claude -p --output-format json returns a single JSON object once the task finishes; --output-format stream-json returns newline-delimited JSON events while it runs; text is the default when you leave the flag out.

In scripts, CI/CD, and automation pipelines you need more than a human-readable answer — you need structured data a program can parse. Claude Code's headless mode (claude -p) offers three output formats via --output-format, letting you wire Claude Code into a pipeline like any ordinary CLI tool.

This is a Claude Code capability and is independent of which service it connects to: whether Claude Code points at the official API or at QCode, --output-format works the same. This page focuses on output formats; for the full headless flag reference and CI templates, see Automation & CI/CD.


1. The three formats at a glance

Format Flag Output shape Use case
Plain text --output-format text (default) A single block of plain text Human reading, simple pipes
JSON --output-format json One structured JSON object Script parsing, reading cost/usage/session ID
Streaming JSON --output-format stream-json A newline-delimited JSON event stream Live progress, step-by-step consumption, long tasks
# text is the default, so these two lines are equivalent
claude -p "Explain idempotence in one sentence"
claude -p "Explain idempotence in one sentence" --output-format text

2. text: the default plain text

Without --output-format you get text: stdout is simply the model's final answer — good for reading directly or feeding a simple pipe.

# Read directly
claude -p "Rewrite this function to be async"

# Pipe to another tool
claude -p "Generate 5 candidate commit messages" | head -5

# Write to a file
claude -p "Write a short README intro for this repo" > intro.txt

Limitation: text gives you only the "answer" — no cost, token usage, or session ID metadata, and multi-step tasks can't be distinguished from the final result. When you need that, switch to json or stream-json.


3. json: a single structured object

--output-format json emits one JSON object after the task completes, containing the final result plus a batch of metadata fields. This is the format scripts use most.

claude -p "Count how many TODO comments are under src/" --output-format json

Common fields in the returned object (the actual fields depend on your version's output):

Field Meaning
result The final answer text
total_cost_usd Cost of this call (USD)
usage Token usage (input / output / cache, etc.)
session_id Session ID, usable with --resume to continue

Parsing with jq

The json format is a natural fit for jq:

# Just the final answer
claude -p "Summarize this change in one sentence" --output-format json | jq -r '.result'

# Pull out the cost of this call
claude -p "Review auth.py" --output-format json | jq '.total_cost_usd'

# Grab several fields at once
claude -p "Analyze the architecture" --output-format json \
  | jq '{cost: .total_cost_usd, session: .session_id, tokens: .usage}'

Branching and accumulating cost in a script

#!/usr/bin/env bash
set -euo pipefail

out=$(claude -p "Write unit tests for UserService" \
        --output-format json --max-turns 8)

result=$(echo "$out" | jq -r '.result')
cost=$(echo "$out"   | jq -r '.total_cost_usd')
sid=$(echo "$out"    | jq -r '.session_id')

echo "Result: $result"
echo "Cost this call: \$$cost"
echo "Session ID: $sid (resume with claude --resume $sid)"

Structured output with --json-schema

Add --json-schema and a JSON Schema definition to --output-format json, and the response carries the structured result in the structured_output field, alongside metadata about the request (session ID, usage, and so on). The example below extracts function names as an array of strings and keeps only the structured part with jq:

claude -p "Extract function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'

Remove the | jq '.structured_output' part to print the whole response instead. If the value isn't a valid JSON Schema, claude exits with Error: --json-schema is not a valid JSON Schema, followed by the validator's diagnostic.

Tip: json emits the whole object in one shot after the task ends — there is no output while a long task runs. To watch progress live, use stream-json below.


4. stream-json: a real-time event stream

--output-format stream-json breaks execution into newline-delimited JSON events (NDJSON), one event per line, written out immediately. It suits live progress for long tasks, streaming UIs, or pipelines that consume output as it's produced.

claude -p "Refactor the whole utils directory and add tests" \
  --output-format stream-json --max-turns 20

The output looks like (each line is independent JSON; fields depend on your version):

{"type":"system","subtype":"init","session_id":"..."}
{"type":"assistant","message":{...}}
{"type":"assistant","message":{...}}
{"type":"result","result":"...","total_cost_usd":0.0123,"usage":{...}}

Consuming line by line

Every line is valid JSON, so you can process it as it streams:

# Print the type of each event in real time
claude -p "Migrate to the new API" --output-format stream-json \
  | jq -r '.type'

# Pull result and cost only when the final event appears
claude -p "Migrate to the new API" --output-format stream-json \
  | jq -r 'select(.type == "result") | "\(.result)\nCost $\(.total_cost_usd)"'
# Process line by line with a while loop for live progress feedback
claude -p "Repository-wide code review" --output-format stream-json | \
while IFS= read -r line; do
  type=$(echo "$line" | jq -r '.type')
  case "$type" in
    system) echo "▶ Starting…" ;;
    assistant) echo "… thinking" ;;
    result) echo "✓ Done: $(echo "$line" | jq -r '.result')" ;;
  esac
done

Token-level and subagent events

  • --include-partial-messages: with --output-format stream-json --verbose, receive tokens as they are generated; each line is a JSON object representing an event.
  • --include-hook-events: include hook lifecycle events in the output stream (requires --output-format stream-json).
  • --forward-subagent-text: emit subagent text into the stream-json output stream (requires --print and --output-format stream-json; Claude Code v2.1.211 or later). Without it, a foreground subagent's text is omitted.
  • --input-format stream-json: take the input in print mode as a JSON stream instead of a text prompt.

json vs stream-json: json waits for the task to finish and gives you one complete object — simple to parse; stream-json streams multiple events throughout, enabling live feedback but requiring line-by-line parsing. In CI, use json for the final result and stream-json for progress logs.


5. CI / pipeline recipes

Grab the result and post a comment in GitHub Actions

- name: Claude review
  env:
    ANTHROPIC_BASE_URL: https://api.qcode.cc/api
    ANTHROPIC_AUTH_TOKEN: ${{ secrets.QCODE_API_KEY }}
  run: |
    out=$(claude -p "Review this PR's diff and list issues" \
            --output-format json --max-turns 6 --allowedTools Read,Glob,Grep)
    echo "$out" | jq -r '.result' > review.md
    echo "This review cost \$$(echo "$out" | jq -r '.total_cost_usd')"

Add a cost gate (fail if over budget)

out=$(claude -p "Generate release notes" --output-format json)
cost=$(echo "$out" | jq -r '.total_cost_usd')
# Float comparison with awk: fail CI if over $0.50
awk -v c="$cost" 'BEGIN{ exit (c > 0.5) ? 1 : 0 }' \
  || { echo "Cost \$$cost over budget"; exit 1; }
echo "$out" | jq -r '.result'

Accumulate cost across multiple calls

total=0
for f in src/*.py; do
  out=$(claude -p "Write docstrings for $f" --output-format json)
  c=$(echo "$out" | jq -r '.total_cost_usd')
  total=$(awk -v t="$total" -v c="$c" 'BEGIN{ printf "%.4f", t + c }')
done
echo "Total cost across all files: \$$total"

Pointing at QCode: the commands above are identical for the official API and QCode — just point ANTHROPIC_BASE_URL at https://api.qcode.cc/api (note: no trailing slash) and put your cr_ key in ANTHROPIC_AUTH_TOKEN. CN users can use asia.qcode.cc. See Endpoints & API formats.


6. Common pitfalls

  • json is silent during long tasks: it outputs once, at the end — seeing no progress is normal; use stream-json for progress.
  • You can't jq stream-json as one blob: it's NDJSON; multiple objects don't form a single JSON document. Use line-by-line jq, jq -c, or --stream — don't run jq '.' over the whole stream.
  • Don't forget --max-turns: cap turns in automation to keep a task from running away and burning money.
  • No trailing slash on BASE_URL: https://api.qcode.cc/api is correct; an extra / builds a wrong path.
  • Metadata fields vary by version: trust the real output of claude -p ... --output-format json on your machine — run jq 'keys' first to see what's there.

Next steps

Once Claude Code is wired into your pipeline, cost is right there in the output — see QCode plans & pricing and pick a tier that fits your automation volume.

Get started with QCode

One API key for Claude Code, Codex and more — billed per token, per-model prices on qcode.cc/models

Enterprise →