Output contract, exit codes and kelta api

The rules that make the CLI safe to script — output formats and flattening, stdout vs stderr, the error envelope and exit codes, destructive-command confirmation, the list query grammar, and the raw API escape hatch.

Output#

  • --output table|json|yaml|csv|ndjson. On a TTY the default is a table; when piped it is JSON. Never parse the table — it has no stability guarantee.
  • JSON:API resources are flattened to { "id", ...attributes, "<toOneRelationship>": "<id>" }. --raw gives the unflattened envelope with links, meta and relationships.
  • --quiet prints ids only, one per line.
  • stdout carries data only; progress and diagnostics always go to stderr. No colour is ever emitted.

Errors and exit codes#

On failure the CLI prints one JSON line on stderr:

{"error":{"code":"VALIDATION_FAILED","status":400,"detail":"…","source":{"pointer":"/data/attributes/name"},"meta":{"requestId":"…"}},"errors":[/* … */]}

error is the first entry flattened for convenience; errors is the full JSON:API array. Branch on code.

ExitMeaning
0success
1API error (4xx/5xx other than the below)
2usage error (INVALID_ARGUMENTS), or a destructive command without --yes off-TTY (CONFIRMATION_REQUIRED)
3authentication required or failed
4not found
5conflict or rate limit

Argument errors use the same envelope. A missing required argument, an unknown option, an unknown command or an input that fails validation returns INVALID_ARGUMENTS and exit 2; the detail is the parser’s message, including any “Did you mean …?” suggestion:

{"error":{"code":"INVALID_ARGUMENTS","detail":"unknown command 'recrods'\n(Did you mean records?)"}}

On a terminal, the same failure prints as Error [INVALID_ARGUMENTS]: unknown option '--colour'. --help and --version exit 0.

Destructive commands#

Anything that deletes, removes, resets, bulk-writes — and every non-GET kelta api call — prompts on a TTY and requires --yes otherwise. kelta manifest marks these "dangerous": true.

Query grammar for list commands#

# --filter is field[.op]=value, repeatable, ANDed; --all instead of --page/--size (client cap 10 000 rows)
kelta records list invoices \
  --filter status=open --filter amount.gte=100 \
  --sort -createdAt,number \
  --fields number,amount --include customer \
  --page 1 --size 200

Operators: eq neq gt gte lt lte isnull contains starts ends icontains istarts iends ieq in (any = in).

The escape hatch: kelta api#

Any endpoint, with the profile’s auth and tenant prefix applied and the response returned verbatim:

kelta api GET '/api/collections?page[size]=5'
kelta api POST /api/admin/domains --data '{"domain":"portal.acme.example"}' --yes
kelta api PATCH /api/invoices/<id> --data @patch.json --header 'If-Match: "…"' --yes

--data takes inline JSON, @file or - for stdin; --header Name:value is repeatable. Non-2xx responses surface the full error envelope and exit per the table above.

Gotchas#

  • Flow input is wrapped for you: --input '{"k":"v"}' → the flow reads $.input.k.
  • Validation-rule formulas are error conditions: TRUE rejects.
  • A just-created collection can 404 on another pod for a moment; the CLI retries name lookups, but scripts that chain a create with an immediate write should tolerate one retry.
  • --size is clamped at 200 server-side; the CLI rejects larger values up front.

Edit this page on GitHub