> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trytalkvalue.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Recipe: Scripting with jq

> Reusable jq snippets for filtering, projecting, aggregating, and paginating TalkValue CLI JSON output.

Every TalkValue CLI command supports `--json` (or auto-emits JSON when piped). [jq](https://jqlang.github.io/jq/) is the standard tool for slicing that JSON into the exact shape you need: a single field, a count, a CSV, a chart-ready projection. The snippets below cover the patterns that keep showing up in real scripts.

<Note>
  **Prerequisites:** authenticated CLI and `jq` installed. On macOS, `brew install jq`. On Debian/Ubuntu, `sudo apt install jq`. On Windows, `winget install jqlang.jq`.
</Note>

## The envelope

Every list command returns this shape:

```jsonc theme={null}
{
  "data": [ /* items */ ],
  "pagination": { "page": 0, "pageSize": 20, "totalElements": 4321, "totalPages": 217 }
}
```

Every single-resource command returns:

```jsonc theme={null}
{ "data": { /* item */ } }
```

Errors land on stderr as:

```jsonc theme={null}
{ "error": { "message": "..." } }
```

Almost every snippet below opens with `.data` or `.data[]`.

## Project: pick specific fields

```bash theme={null}
talkvalue path person list --json | jq '.data[] | {id, name, primaryEmail}'
```

Returns one object per person with only the three fields. The same pattern works on any list:

```bash theme={null}
talkvalue path event list --json   | jq '.data[] | {id, name, startAt}'
talkvalue path channel list --json | jq '.data[] | {id, name, personCount}'
talkvalue path company list --json | jq '.data[] | {id, displayName, domain}'
```

## Extract a single field as a flat list

```bash theme={null}
talkvalue path person list --json | jq -r '.data[].primaryEmail'
```

`-r` (raw output) strips JSON quotes so you get one email per line. Ready to pipe into `mail`, `wc -l`, or a follow-up CLI call.

## Filter: keep rows that match

```bash theme={null}
# People at Acme Inc.
talkvalue path person list --json \
  | jq '[.data[] | select(.companyName == "Acme Inc.")]'

# People who registered for an event this month
SINCE=$(date -u +"%Y-%m-01T00:00:00Z")
talkvalue path event person list 18 --json \
  | jq --arg since "$SINCE" '[.data[] | select(.joinedAt >= $since)]'

# Companies with a domain set
talkvalue path company list --json \
  | jq '[.data[] | select(.domain != null and .domain != "")]'
```

Wrap the pipeline in `[ ... ]` to collect the matches into an array. Drop the brackets for a stream of unwrapped objects.

## Count

```bash theme={null}
# Total people
talkvalue path person list --page-size 1 --json | jq '.pagination.totalElements'

# Registrants this month
talkvalue path event person list 18 --json \
  | jq --arg since "$SINCE" '[.data[] | select(.joinedAt >= $since)] | length'

# Job titles ranked for one company
talkvalue path company person list 88 --json \
  | jq '[.data[] | .jobTitle] | group_by(.) | map({title: .[0], count: length}) | sort_by(-.count) | .[0:10]'
```

`length` on an array returns its size. `pagination.totalElements` returns the server-side total without pulling every page.

## Group and count by company

```bash theme={null}
talkvalue path event person list 18 --page-size 500 --json \
  | jq '[.data[] | .companyName // "Unknown"]
        | group_by(.)
        | map({company: .[0], count: length})
        | sort_by(-.count)'
```

The `// "Unknown"` fallback keeps people without a company in the group instead of producing a `null` bucket.

## Project to CSV

```bash theme={null}
talkvalue path person list --json \
  | jq -r '["id","name","primaryEmail","companyName"],
           (.data[] | [.id, .name, .primaryEmail, .companyName])
           | @csv' \
  > people.csv
```

`@csv` quotes fields, escapes commas, and produces a valid CSV file. The first line is the header, the rest are rows.

## Walk every page

```bash theme={null}
page=0
while :; do
  resp=$(talkvalue path person list --page "$page" --page-size 100 --json)
  echo "$resp" | jq -e '.data | length > 0' >/dev/null || break
  echo "$resp" | jq '.data[]'
  page=$((page + 1))
done
```

`jq -e` exits non-zero when the test is false, so the loop stops the first time a page returns no rows. Streaming `.data[]` keeps every record on stdout for further piping.

## Combine: count by company across all pages

```bash theme={null}
page=0
EVENT_ID=18
all=$(
  while :; do
    resp=$(talkvalue path event person list "$EVENT_ID" --page "$page" --page-size 100 --json)
    echo "$resp" | jq -e '.data | length > 0' >/dev/null || break
    echo "$resp" | jq '.data[]'
    page=$((page + 1))
  done
)
echo "$all" \
  | jq -s 'group_by(.companyName // "Unknown")
           | map({company: .[0].companyName // "Unknown", count: length})
           | sort_by(-.count)'
```

The page-walk streams every record. `jq -s` (slurp) collects them back into a single array for the group-and-count step.

## Read the error envelope

```bash theme={null}
err=$(talkvalue path person get 999999 --json 2>&1 1>/dev/null)
code=$?
if [ "$code" -ne 0 ]; then
  echo "exit=$code message=$(echo "$err" | jq -r '.error.message')" >&2
fi
```

Captures stderr separately from stdout so you get both the exit code (see [Exit codes](/cli/exit-codes)) and the structured error message in one place.

## Tips

* Quote your jq filter in single quotes so the shell doesn't expand `$` characters before jq sees them.
* Pass dynamic values with `--arg name value` (string) or `--argjson name value` (parsed JSON). Inside the filter, reference them as `$name`.
* Pretty-printed JSON is the default. Pass `-c` for one-record-per-line output that streams cleanly into other tools.
* jq has a built-in `now` function. `now | strftime("%Y-%m-%d")` makes timestamped filenames trivial.

## See also

* [Output format](/cli/output-format). The JSON envelope every command returns.
* [Recipe: New registrants this week](/cli/recipes/new-registrants). Uses `select`, `--arg`, and `@csv` end-to-end.
* [Recipe: CSV import](/cli/recipes/csv-import). Uses `jq -r` to capture IDs from API responses.
* [Recipe: Channel analysis](/cli/recipes/channel-analysis). Uses `--argjson` to combine multiple commands.
* [jq manual](https://jqlang.github.io/jq/manual/). The upstream reference.
