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

# Output and errors

> Which stellar commands support --output and which values each accepts, the token family's typed error envelope, and how every other command reports failure.

> [!NOTE]
>
> A global `--output json` flag that works on every command is coming soon. Until then, `--output` is declared per command, and each command has its own values and default.

The `stellar token` family returns a typed, machine-parseable error envelope. Other commands report failure as text on stderr plus a non-zero exit code. Check the table below before you wire an agent to parse CLI output.

## Commands that support `--output`

Where a command takes all three, `text` is human-readable, `json` is a compact single line, and `json-formatted` is that same JSON indented across multiple lines. Have your agent parse `json` and keep `json-formatted` for output a person reads.

| Command | `--output` values | Default |
| - | - | - |
| `token balance`, `token transfer`, `token name`, `token symbol`, `token decimals`, `token approve`, `token allowance` | `text`, `json`, `json-formatted` | `text` |
| `tx fetch result`, `tx fetch meta`, `tx fetch events` | `json`, `json-formatted`, `xdr` (values vary per subcommand) | varies |
| `tx fetch fee` | `table`, `json`, `json-formatted` | `table` |
| `tx decode` | `json`, `json-formatted` | `json` |
| `tx encode` | `single-base64`, `single` | `single-base64` |
| `network health`, `network info` | `text`, `json`, `json-formatted` | `text` |
| `network settings` | `xdr`, `json`, `json-formatted` | `json` |
| `ledger latest`, `ledger fetch` | `text`, `json`, `json-formatted` | `text` |
| `fees stats`, `fee-stats` (deprecated) | `text`, `json`, `json-formatted` | `text` |
| `contract info interface` | `rust`, `xdr-base64`, `json`, `json-formatted` (no `text`) | `rust` |
| `contract info meta`, `contract info env-meta` | `text`, `xdr-base64`, `json`, `json-formatted` (no `rust`) | `text` |
| `contract read` | `string`, `json`, `xdr` (use `xdr` to parse, see below) | `string` |
| `contract inspect` (deprecated) | `xdr-base64`, `xdr-base64-array`, `docs` | `docs` |
| `xdr decode` | `json`, `json-formatted`, `text`, `rust-debug`, `rust-debug-formatted` | `text` |
| `xdr encode` | `single`, `single-base64`, `stream` | `single-base64` |
| `events` | `pretty`, `plain`, `json`, `raw` | `pretty` |
| `snapshot create` | `json` only | `json` |

Beyond the table:

* `strkey decode` and `strkey encode` have no `--output` flag, and they are not symmetric. `strkey decode` emits JSON: a decoded `G...` address comes back as `{"public_key_ed25519": "<hex>"}`. `strkey encode` takes that JSON as its input argument and emits a bare strkey string with no JSON wrapper at all.
* **`tx send` has no `--output` flag.** Passing one exits `2`. It always writes the indented multi-line shape, around 11 KB for a one-operation payment, so the compact `json` this page tells you to prefer is not available on the one command that returns a submission receipt. Parse the indented form, or take the hash from `token transfer` or the stderr signing line instead.
* `tx fetch fee` defaults to `table`, not `json`, unlike every other `tx fetch` subcommand. Pass `--output json` explicitly if your agent needs to parse it.
* **On `tx fetch`, use `--output json` to parse.** `json-formatted` on `result`, `meta`, `fee`, and `events` adds a human-readable header (`Transaction Status` and `Transaction Ledger`) above the JSON, so a JSON parser will not accept it. On the `token` family and `ledger`, `json-formatted` is plain indented JSON.
* None of the 22 `stellar tx new <operation>` commands has an `--output` flag at all, and a successful submission writes nothing to stdout. See [`tx new` has no machine-readable receipt](#tx-new-has-no-machine-readable-receipt) below.
* To parse `contract read` output, use `--output xdr`. See [Parsing `contract read`](#parsing-contract-read) below.

## Parsing `contract read`

`contract read --output json` currently writes CSV rows with a JSON value in one field, not a single JSON document. For a machine-readable result, use `--output xdr` and decode it with `stellar xdr decode`, or parse the output as CSV.

## `tx new` has no machine-readable receipt

`stellar tx new payment --help`, and every other `tx new <operation> --help`, has no `--output` flag. A submitted operation writes nothing to stdout on success. The transaction hash appears only on an `ℹ️  Signing transaction: <HASH>` stderr line, and `--quiet` removes that line along with everything else on stderr.

Contrast this with `token transfer`, which prints the bare hash as its last stdout line and supports `--output json` directly. `tx new` has no equivalent of either.

To get a parseable result from a `tx new` operation, split the pipeline and read the result from `tx send`, which does return JSON:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new payment --source <IDENTITY> --destination <ADDRESS> --amount <N> --build-only --network <NET> \
  | stellar tx sign --sign-with-key <IDENTITY> --network <NET> \
  | stellar tx send --network <NET>
```

`tx send`'s JSON has top-level keys `status`, `ledger`, `application_order`, `fee_bump`, `tx_hash`, `created_at`, `envelope`, `result`, `result_meta`, and `events`. `status` is `SUCCESS` on success. The hash field is `tx_hash`, not `hash`.

## The token family's error envelope

`stellar token` commands wrap a failed call in a single JSON object with a `type` discriminator, so an agent can branch on the failure class without parsing prose:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar token balance --id nonexistent_bad_id --account agent-1 --network testnet --output json
{"error":{"type":"config","message":"contract not found: nonexistent_bad_id"}}
```

A well-formed but nonexistent `C…` address returns the same `config` type, not `contract_not_found`:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar token balance --id CBIELTK6…QDAAA --account agent-1 --network testnet --output json
{"error":{"type":"config","message":"contract not found: CBIELTK6…QDAAA"}}
```

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar token transfer --id USDC:GBBD47IF… --from agent-1 --to doc-probe \
    --amount 500000 --network testnet --output json
{"error":{"type":"invoke","message":"transaction simulation failed: HostError: Error(Contract, #13)\n\nEvent log (newest first):\n   0: [Diagnostic Event] … data:[\"trustline entry is missing for account\", GCBNIXOH…]\n…"}}
```

Known `type` values, from the `token` command source:

| `type` | Meaning |
| - | - |
| `sac_not_deployed` | The Stellar Asset Contract for this classic asset has not been deployed yet. The error carries a hint pointing at `stellar contract asset deploy --asset <ASSET> --source-account <IDENTITY>`. `--source-account` is required on that command; the hint does not run without it. |
| `contract_not_found` | Defined in the `token` command source, but not reached through the `token` commands in testing. A nonexistent contract address returned `config` instead (see above). Do not rely on this type to detect a missing contract. |
| `config` | A resolution or configuration problem: an unparseable `--id`, an unknown alias, or a well-formed but nonexistent `C…` contract address. This, not `contract_not_found`, is the type you actually get for a missing contract, with `message: "contract not found: <ID>"`. |
| `network` | Reserved for network-level failures. In testing, those returned `invoke`, so do not key retry logic on this value. |
| `invalid_address` | A `--from`, `--to`, `--spender`, or `--account` value is not a valid address. `--account` is the one that matters for reads: an unknown alias returns `Account alias "<NAME>" not Found`. |
| `invoke` | The contract call itself failed during simulation or submission. Check the `message` field's embedded diagnostic event log for the actual cause. Two you will meet often: `Error(Contract, #13)`, `"trustline entry is missing for account"`, for a classic asset with no trustline or no account at all; and `Error(Contract, #6)`, `"account entry is missing"`, for the native asset with no account at all. Both are `invoke`, so you cannot branch on `type` alone to tell them apart. |
| `internal` | An unexpected CLI-internal error. |

Both example calls above exit `1`.

## Everything else prints unstructured stderr text

Outside the `token` family, a failing command prints a plain `❌ error: ...` line to stderr. There is no `type` field and no stable schema to parse. Two real examples:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar strkey decode <MALFORMED_INPUT>
❌ error: Encoded text cannot have a 6-bit remainder.
```

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar message verify "tampered message" --public-key GDCINM7O… --signature Z34PcX58…
❌ Signature invalid
❌ error: Signature verification failed
```

An agent working against the rest of the CLI has to rely on the exit code and on matching substrings in stderr text. There is no discriminated error type outside `token`. This is the main obstacle to programmatic error handling in the CLI today, and it is worth flagging to your agent's error-handling logic explicitly rather than assuming every command behaves like `token`.

## A containerized build borrows stderr for the engine's output

`stellar contract build --image <IMAGE>` shells out to the container engine, and `--pull` makes it pull first. The engine writes pull progress to its own stdout, and the CLI redirects that to stderr so the CLI's stdout stays clean. Measured against Docker 29.8.0, stdout was zero bytes in every case below.

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar contract build --image docker.io/library/alpine:latest --pull   # 1>out 2>err
latest: Pulling from library/alpine          # all of this on stderr
a9986cd6f37d: Pull complete
Digest: sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6
Status: Downloaded newer image for alpine:latest
```

A pull that fails exits `1` with the engine's own message followed by the CLI's, both on stderr:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar contract build --image docker.io/stellar/definitely-not-a-real-image:v0 --pull
Error response from daemon: pull access denied for stellar/definitely-not-a-real-image, ...
❌ error: could not pull image docker.io/stellar/definitely-not-a-real-image:v0
```

Without `--pull` there is no pull output at all: the build uses the local image and goes straight to the container, matching `docker run`.

`--quiet` behaves here exactly as the section below describes, and this is the case where it costs most. A failed containerized build under `-q` writes zero bytes to stdout *and* zero bytes to stderr, so neither `could not pull image` nor any later build error reaches you. The exit code is the entire result. Confirmed live on both the failed-pull and failed-toolchain paths.

## Exit codes

`0` means success. Non-zero means failure, and the CLI separates two classes of failure:

| Exit | Means | What an agent should do |
| - | - | - |
| `0` | Success | Continue |
| `1` | Runtime failure. The invocation was well formed and something about the world caused it: an RPC error, a missing account, a failed simulation, a rejected submission | Inspect, and retry only where the retry rules allow it |
| `2` | Malformed invocation. An unrecognized subcommand, an unknown flag, an invalid value for a flag | Stop and escalate. Retrying the same command verbatim cannot succeed |

Measured across 104 error cases. The runtime direction held without exception: every runtime failure exited `1`. The malformed direction held in 24 of 26 cases, and there is one known counterexample.

**`xdr decode --type <invalid>` and `xdr encode --type <invalid>` exit `1`, not `2`, and print the `❌` prefix.** That single case defeats both discriminators at once. Every other invalid-flag-value case exits `2`, including `--input bogus` on the same command.

Otherwise the two classes are distinguishable in the text as well: exit `2` prints `error: ...` with no emoji, a runtime failure prints `❌ error: ...`.

Treat all of this as current observed behavior rather than a guaranteed contract, since it comes from the argument parser rather than from anything the CLI promises. Branch on it, and still handle the general non-zero case.

`stellar message verify` is a clean, verified example of both paths:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar message verify "agent-session-2026-09-09" \
    --public-key GDCINM7OFENANN2Y73MSU74DWDZXLAXX7KMP3J4NH67IPKWWIUF33KAH \
    --signature Z34PcX58nb+1CwrwM53uqYKk+/jcb5o5RfhhtsLEOpVQbiumbactxQpEnyTgDwMeUQbHMAqKb/StewzBhW7wDg==
ℹ️  Verifying signature against: GDCINM7OFENANN2Y73MSU74DWDZXLAXX7KMP3J4NH67IPKWWIUF33KAH
✅ Signature valid
```

Exit code `0`. On a tampered message, the same command exits `1` with `❌ Signature invalid` followed by `❌ error: Signature verification failed`. Check the exit code first. Don't rely on matching the emoji-prefixed line alone, since that line's exact text is not guaranteed stable across versions.

## Success shapes

A mutating `token` command in JSON mode returns a fixed two-field object:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{"tx_hash":"cdbfa12f54d40d3c1f3b3a4c64cecd93f7a3dcc16574fe00bf1f2f557f65e78a","result":null}
```

`token approve` returns the same shape. `result` is `null` on these calls; it is not populated with contract return data by `transfer` or `approve`.

A read-only `token` command in JSON mode returns an object keyed by the command:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{"balance":"99999988251"}
{"decimals":7}
{"allowance":"250000000"}
{"name":"native"}
{"symbol":"native"}
```

Adding `--decimal` adds a second key rather than replacing the first, so do not write a parser that assumes exactly one:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{"balance":"9999.9988251","decimals":7}
{"allowance":"0.00001","decimals":7}
```

In text mode, the same reads return a bare value with no wrapping:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar token decimals --id USDC:GBBD47IF… --network testnet
7
```

`--decimal` on `token balance` divides by the token's own `decimals` before printing. Without it, you get the raw smallest-unit integer, which is what you want when feeding the value straight back into `--amount` on a later command.

## Practical guidance

`--quiet` deletes stderr on a non-token command, error message included. It is not just quieter logging: on a failing non-token command, `--quiet` leaves you with the exit code and nothing else, zero bytes on stderr, confirmed live. Use `--quiet` only when the exit code is all you need. If you need to know why something failed, omit `--quiet` and capture stderr instead.

The `token` family is safe either way, because its error is JSON on stdout, not stderr, and `--quiet` does not touch stdout. In JSON mode the `token` family writes nothing to stderr at all, measured on both a successful and a failing `token transfer`, so `--quiet` is redundant there rather than necessary. Combine `--quiet` with `--output json` on `token` commands for clean, parseable stdout with no informational logging (`ℹ️  Simulating transaction…`, `🌎 Sending transaction…`, `✅ Transaction submitted successfully!`, the `🔗` explorer link) mixed in. Do not reach for `--quiet` as a default on every command; it is a tradeoff, not a free clean-up.

**Never merge stderr into stdout when you intend to parse the result.** This is the same class of hazard as `--quiet`, and it is the one that actually causes damage. `stellar tx send` writes an informational line to stderr (`ℹ️  Transaction hash is <HASH>`, or `ℹ️  Signing transaction: <HASH>` when the command is `token transfer`) and its JSON receipt to stdout, separately. `2>&1`, the default habit of most agent tooling, interleaves the two streams and breaks the JSON:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ … | stellar tx send --network testnet 2>/dev/null
{"status":"SUCCESS", …}

$ … | stellar tx send --network testnet 2>&1
ℹ️  Transaction hash is d9a2ae81f886c9b5cde637a282424eb1a8eb88c741aee5857a744cd29994ba…
{"status":"SUCCESS", …}
```

The second form does not parse as JSON. An agent that treats that as a submit failure and retries resubmits a transaction that already succeeded, which spends twice. A failed parse is not evidence the transaction failed.

One exception to the recovery route, and it is the mode most agents run in: `token transfer --output json` writes **nothing at all to stderr**, on success and on failure alike, measured on testnet. There is no `ℹ️  Signing transaction:` line to fall back on, so the hash exists only in stdout's `tx_hash`. If that stdout is empty or unparseable, you have no local record of the hash. Do not retry. Re-read both balances, and go to Horizon if you need the hash itself.

Redirect stderr separately, never merged, whenever you intend to parse stdout. Capture it to a file rather than discarding it, because for `tx send` the hash you need to confirm onchain state lives on that stderr line:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx send --network <NET> 2>send.stderr
```

Before retrying any submit that appears to have failed, confirm it did not already land:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx fetch result --hash <HASH> --network <NET>
```

Only retry once you have confirmed the transaction is not already onchain.

In text mode, a submitted transfer prints the bare transaction hash as the last line of stdout:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
$ stellar token transfer --id native --from agent-1 --to doc-probe --amount 10000000 --network testnet
ℹ️  Simulating transaction…
ℹ️  Signing transaction: 11659a52651dc9ba60dc422dc890abe17ab11c68ec4b976348398987c873df7e
🌎 Sending transaction…
✅ Transaction submitted successfully!
🔗 https://stellar.expert/explorer/testnet/tx/11659a52651dc9ba60dc422dc890abe17ab11c68ec4b976348398987c873df7e
11659a52651dc9ba60dc422dc890abe17ab11c68ec4b976348398987c873df7e
```

Capture it directly with command substitution:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
TX=$(stellar token transfer --id native --from agent-1 --to <ADDRESS> --amount 10000000 --network testnet)
```

## Telling `#13` from `#6`

`#6` `"account entry is missing"` comes only from a native query and means the account does not exist **on the network you queried**. It says nothing about any other network. A live, funded mainnet account queried against testnet returns `#6` byte-identically to an address that has never existed anywhere; this was measured against a mainnet account holding 4.3800772 XLM.

`#13` `"trustline entry is missing for account"` comes from a classic-asset query and is ambiguous in a second way: a funded account with no trustline and a nonexistent account both produce it.

To disambiguate `#13`, query the native balance of the same address, and pass `--network` explicitly, because an unset network resolves to testnet silently:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar token balance --id native --account <ADDRESS> --network <NET> --output json
```

A number means the account exists and only needs a trustline.

`#6` means check the network before you do anything else. Re-run the same query against the network the account is supposed to be on. Only once the address returns `#6` on the network you actually meant is it a missing account, and only then does funding it make sense:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar keys fund <NAME> --network testnet
```

Running `keys fund` on a `#6` you have not scoped creates a real, separate account on the wrong network. It then returns a balance, which reads as the problem being fixed. It is not: the funds you were looking for are still on the other network, and you have made a decoy.

## Related pages

* [Quickstart](https://developers.stellar.org/docs/tools/cli/agent-cli/quickstart.md)
* [Troubleshooting](https://developers.stellar.org/docs/tools/cli/agent-cli/reference/troubleshooting.md)
* [Authority model](https://developers.stellar.org/docs/tools/cli/agent-cli/reference/authority-model.md)
