---
title: "CLI automation"
description: "Use the lookio CLI in scripts, CI and agents, with automatic JSON output, ndjson, polling for imports, safe retries and destructive commands."
canonical: "https://lookio.app/docs/cli/automation"
updated: "2026-10-17"
---

# CLI automation

The CLI is designed to be driven by a script or an AI agent as much as by a person.

## JSON by default off a terminal

When stdout is not a terminal, every command prints JSON. A piped command, a CI job and an agent never have to remember `--json`, and can never receive a table by mistake.

| Mode     | When                   | Output                                             |
| -------- | ---------------------- | -------------------------------------------------- |
| `json`   | Default off a terminal | The whole result, indented.                        |
| `table`  | Default in a terminal  | A table or a list of fields, with colour.          |
| `ndjson` | `--output ndjson`      | One JSON object per line. For a list, one per row. |

`LOOKIO_NO_TUI` forces JSON in a terminal. `NO_COLOR` removes colour and nothing else.

List commands print the whole API envelope in JSON: `data`, `has_more`, `next_cursor` and `limit`. Pass `next_cursor` back as `--cursor` for the next page.

```bash
lookio resources list --limit 100 | jq -r '.data[] | select(.status == "failed") | .id'
```

Progress notes go to stderr, and only in a terminal. Warnings always go to stderr.

## Errors

A failure prints one JSON envelope on **stderr** and exits with a code you can branch on. It is the API's own error format, plus the same shape for failures that never reached the server, with `request_id` set to `null`.

```json
{
  "error": {
    "type": "not_found_error",
    "code": "resource_not_found",
    "message": "Resource not found.",
    "docs_url": "https://lookio.app/docs/api/errors#resource_not_found"
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}
```

Branch on the exit code or on `error.code`, never on the message. See [Exit codes](/docs/cli/exit-codes).

## Wait for an import

An import returns `queued` at once. Poll until the resource is final, and stop there.

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

id=$(lookio resources add-url https://example.com/pricing --tags pricing | jq -r '.id')

for _ in $(seq 1 60); do
  status=$(lookio resources get "$id" | jq -r '.status')
  case "$status" in
    ready)  echo "ready"; break ;;
    failed) lookio resources get "$id" | jq -r '.status_detail' >&2; exit 1 ;;
  esac
  sleep 5
done
```

A site takes minutes, not seconds, so allow a longer loop. A resource that is not `ready` cannot answer questions yet.

## Retries

The CLI retries by itself, up to 3 times with jittered backoff, and honours `Retry-After`:

- `429` is retried for every command.
- `408` and `5xx` are retried for reads, `PUT` and `DELETE`, and for every `POST`.
- A `PATCH` (`resources update`, `assistants update`, `members set`) is not retried after a `5xx`, because the change may already have landed. Re-read the resource before you try again.

Every `POST` carries an `Idempotency-Key`, a random UUID chosen once per command and reused by each automatic retry. A retried `lookio ask` is not charged twice, and a retried import does not create two resources. Running the command again yourself is a new request with a new key.

Do not add your own blind retry loop around a command. Use `--no-retry` if you want to take over.

An out-of-credits failure is never retried. It exits with code `7` so a job can stop and alert a human.

A single request gives up after 60 seconds. Change it with `--timeout`.

## Destructive commands

`resources delete`, `assistants delete` and `tags delete` ask for confirmation in a terminal. With no terminal, they do not wait: they exit with code `2` and an error that names `--force`. Add `--force` when you mean it.

## CI example

```yaml title=".github/workflows/refresh-docs.yml"
name: Refresh Lookio resources
on:
  schedule:
    - cron: "0 6 * * 1"

jobs:
  refresh:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Re-index the docs site
        env:
          LOOKIO_API_KEY: ${{ secrets.LOOKIO_API_KEY }}
        run: npx @lookio/cli resources reindex "$RESOURCE_ID"
```

Use an [API key](/docs/platform/api-keys) with only the scopes the job needs: `resources:write` here.

## For AI agents

```bash
lookio help --json
```

prints the whole command tree, every flag, the exit codes, the retry policy and the error format as one JSON document. An agent that reads it first stops guessing at flags.

- Call `lookio account` before assuming a capability. `credential.scopes` lists what the credential may do.
- Do not pass `--json`. It is already the output whenever stdout is not a terminal.
- Use `lookio api` for anything that has no command yet.
