> For the complete documentation index, see [llms.txt](https://docs.tryterra.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tryterra.co/developer-tools/terra-cli/output-and-scripting.md).

# Output and scripting

Output formats, field selection, jq filtering, pagination, exit codes, and how to debug a failing Terra CLI command.

Output is a table on a terminal and JSON when piped, so people and scripts both get something usable without a flag.

```bash
terra environments list                      # a table
terra environments list | jq '.[].dev_id'    # JSON
```

## Formats

Override the default with `--format`.

| Format   | Output                                                                |
| -------- | --------------------------------------------------------------------- |
| `table`  | Columns for a list; flattened field/value rows for one object.        |
| `json`   | An indented JSON document. Lists are arrays of records.               |
| `ndjson` | One JSON record per line.                                             |
| `yaml`   | One YAML document, with a sequence for a list.                        |
| `csv`    | One header row followed by records, with nested values as JSON cells. |

```bash
terra whoami --format yaml
terra users list --format csv > users.csv
```

Tables fit the terminal width and can shorten values. Single objects use dotted paths for nested fields and indexed paths for arrays. Use JSON or YAML for the full nested structure, and CSV to export rows.

Without field selection or a jq filter, JSON and NDJSON preserve each record's field order and characters. Generated list commands unwrap the API's `data` envelope into an array of records. Table output escapes control characters. Numbers retain their precision in every format.

## Select fields

`--select` narrows the response to the fields you name, in the order you name them. It applies to every format, so one flag picks both the keys in a JSON document and the columns in a table.

```bash
terra environments list --select dev_id,name
terra environments list --select dev_id,name --format table
```

Run it with no value to report the available fields and exit with usage code 4. An unknown field is refused before the request is sent.

```bash
terra environments list --select
```

`--select` exists only where the API description says what the response contains. `terra api` and `terra data-api` do not have it. Use `--jq` there.

## Filter with jq

`--jq` runs a jq expression over the response. jq is built in, so it works on machines without `jq` installed, including Windows.

```bash
terra environments list --jq '.[].dev_id'
terra users list --jq '[.[] | select(.active)] | length'
terra api /me --jq '.scopes'
```

A string result prints bare, as `jq -r` would. An expression that fails to parse is refused before the request is sent, and one that fails on the data is a usage error, so a script can tell either apart from an API failure.

## Pagination

List commands fetch one page and say whether more is available.

```
$ terra users list
...
More results are available. Use --paginate to fetch them.
```

`--paginate` collects pages and renders one document in the selected format. The default remains a table on a terminal and a JSON array when piped.

```bash
terra users list --paginate | jq -r '.[].user_id'
terra users list --paginate --format ndjson | jq -r .user_id
terra users list --paginate --format csv > users.csv
terra users list --paginate --jq 'length'
```

All formats, including NDJSON, buffer the records until the walk finishes. `--jq` runs once over the collected array, so it can count, sort, or aggregate across pages. `--select` narrows each record before the jq expression runs.

`--max-pages` caps the walk at 10 pages by default. Raise it, or pass `0` for no limit. An unlimited walk buffers every record in memory. Hitting the cap prints a notice to stderr and returns the collected records with exit code 0.

```bash
terra users list --paginate --max-pages 100
```

Generated commands pace requests 100ms apart. Follow-up requests carry only the cursor and page size; the cursor preserves the initial filters and time window.

If a later page fails, the command exits nonzero and writes the records already fetched with an incomplete-output warning. With `--jq`, a failed walk writes no output because the filter requires a complete document.

To drive pagination yourself, use `terra api` to read the raw envelope with `next_cursor` and `has_more`, then pass the cursor on the next request. Generated list commands print records without that envelope.

## Empty results

| Format            | Zero results on stdout                               |
| ----------------- | ---------------------------------------------------- |
| `json`, `yaml`    | `[]`                                                 |
| `ndjson`, `table` | Nothing                                              |
| `csv`             | Header only when fields are known; otherwise nothing |

An empty list also prints a `No results.` note to stderr. Generated commands include the selected environment and filters in that note where applicable.

A response with no body, such as HTTP 204, prints nothing in every format.

## Exit codes

| Code | Meaning                                                      |
| ---- | ------------------------------------------------------------ |
| 0    | Success                                                      |
| 1    | API or transport error                                       |
| 2    | Not authenticated, or the token was rejected                 |
| 3    | Local validation failure or API `validation_failed`          |
| 4    | Usage error: an unknown command, a bad flag, wrong arguments |
| 5    | The product is not on the account                            |
| 6    | Internal error in the CLI                                    |
| 7    | Canceled: confirmation declined or request interrupted       |

These are a contract. Branch on them rather than matching on message text.

Exit 3 covers input rejected locally and an API `validation_failed` response. Check the error text to see what needs correcting. Exit 1 includes other API errors and transport failures. Exit 2 means credentials are missing or rejected. Exit 5 is reserved for a missing entitlement.

```bash
terra environments retrieve --env dev-prod >/dev/null 2>&1
case $? in
  0) ;;
  2) echo "log in first" ;;
  3) echo "bad input" ;;
  7) echo "canceled" ;;
  *) echo "something else went wrong" ;;
esac
```

{% hint style="warning" %}
Branch on `$?` directly. Inside `if ! cmd; then`, `$?` holds the status of the negation, which is always 0, so every case falls through to the last one.
{% endhint %}

## Debugging a command

`--show-headers` traces the request and response to stderr. Headers are redacted and bodies show only their byte count, so the output is safe to paste into an issue. The response body still goes to stdout.

```bash
terra environments list --show-headers
```

`--dry-run` prints the request without sending it, which is the fastest way to see which environment was resolved and what body was built. See [Guardrails](/developer-tools/terra-cli/guardrails.md#preview-a-request).

Errors print the API's own remediation text, which usually names the fix. A missing scope, for example, tells you which scope to request. Exit 3 can come from local or server-side validation; it does not prove that no request was sent.

`terra api <path>` reaches an endpoint without the generated command's validation or formatting in the way. That separates "the CLI built the wrong request" from "the API answered this". See [Raw API requests](/developer-tools/terra-cli/raw-requests.md).

| Symptom                                          | Cause                                                              | Fix                                                                  |
| ------------------------------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------- |
| `no environment selected`                        | The command acts on one environment and none was given             | Pass `--env`, set `TERRA_ENV`, or run `terra environments use`       |
| `not logged in`                                  | No stored token and no `TERRA_ADMIN_TOKEN`                         | Run `terra login`, or set the variable. Check with `terra whoami`.   |
| A destructive command refuses to run             | There is no terminal to confirm against                            | Add `--yes`                                                          |
| `More results are available`                     | The list has more pages                                            | Add `--paginate`                                                     |
| `the endpoint does not exist on this deployment` | A bare 404. The path is wrong, or the surface is not on that host. | Check `terra config --list` for the base URL in use                  |
| The token was written to a file, not the keyring | No OS keyring was available. The file is mode 0600.                | Nothing to fix. `terra config --list` shows which backend is in use. |
| `unknown flag: --select`                         | The command's response is not described, so there are no fields    | Use `--jq` instead                                                   |

## Color

Color is used on a terminal and not when piped. Override with `--color on` or `--color off`. `NO_COLOR`, `CLICOLOR`, `CLICOLOR_FORCE`, and `TERM=dumb` are honored.
