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

# Errors & exit codes

Every CLI invocation that fails surfaces three things: a stable **exit code**, a structured **error envelope on stderr**, and a **request ID** you can quote when reporting the issue.

## Exit codes

The CLI maps HTTP status to a small set of stable exit codes. Conditional shell logic (`bron tx get $id || handle "$?"`) and CI gating both rely on these.

| Exit code | Meaning      | Source                                                       |
| --------- | ------------ | ------------------------------------------------------------ |
| `0`       | success      | request returned 2xx                                         |
| `3`       | unauthorized | HTTP 401 / 403                                               |
| `4`       | not found    | HTTP 404                                                     |
| `5`       | bad request  | HTTP 400                                                     |
| `6`       | conflict     | HTTP 409 (e.g. duplicate `externalId` with a different body) |
| `7`       | rate limited | HTTP 429                                                     |
| `8`       | server error | HTTP 5xx                                                     |
| `1`       | other        | non-API error (network, file I/O, malformed flag)            |

These won't shift across `0.x`. New status codes get a new mapping; existing mappings stay put.

## Error envelope

A 4xx / 5xx response prints to **stderr** in this shape:

```
error: <human-readable message>
  status: <http-status>
  code:   <error-code-slug>
  id:     <correlation-id>
```

Concrete example — a withdrawal with a too-low amount:

```text theme={"system"}
error: Amount is below the network minimum
  status: 400
  code:   AMOUNT_BELOW_MIN
  id:     sdk-a19eef801c5d88d789b7b13e764ddb99
```

* **`status`** is the raw HTTP status (also reflected in the exit code).
* **`code`** is a stable error-code slug (`AMOUNT_BELOW_MIN`, `WITHDRAWAL_LIMIT_EXCEEDED`, `INVALID_KEY`, …). Branch on this in scripts; not on the human message.
* **`id`** is the per-request correlation ID (header `Correlation-Id`). The Bron team can pull the exact ES log line for your call with this. Quote it when reporting issues. Programmatic surfaces — Go SDK `APIError.RequestID`, MCP error payload `requestId` — expose the same value under those longer names.

The CLI sanitises ANSI escape sequences out of error output, so it's safe to redirect stderr to a log file.

## Why a request ID matters

The frontend calls this the **Error ID**. Same value: it's the per-request correlation ID generated server-side, attached to every log line for that request across every service it touches (public-api, the worker that picked it up, MPC signers, blockchain writer). Quoting it lets us reproduce the failure exactly:

```bash theme={"system"}
# In a bug report, paste this:
id: sdk-a19eef801c5d88d789b7b13e764ddb99
```

If you're integrating Bron into a larger system, log this `id` alongside whatever request ID you generate yourself — when something goes wrong, the join across logs is trivial.

## Common error codes

A small reference of the most-seen codes; the canonical list is on [Errors](/api-reference/errors).

| `code`                                        | Typical cause                                             |
| --------------------------------------------- | --------------------------------------------------------- |
| `INVALID_KEY` / `KEY_REVOKED`                 | API key not registered, or `kid` was revoked              |
| `INSUFFICIENT_BALANCE`                        | Transfer amount exceeds the on-chain balance              |
| `AMOUNT_BELOW_MIN`                            | Amount under the network's documented minimum             |
| `EXTERNAL_ID_CONFLICT`                        | An `externalId` is being reused with a different body     |
| `INVALID_ADDRESS` / `ADDRESS_NOT_WHITELISTED` | Destination not on the workspace allow-list               |
| `RATE_LIMITED`                                | You've hit the per-key/per-workspace rate limit; back off |
| `WORKSPACE_NOT_FOUND`                         | `--workspace` doesn't match the registered key            |

## Idempotency contract

Every `tx <type>` / `tx create` call accepts `--externalId <key>`. Bron de-duplicates by `(workspaceId, externalId)`:

* **Same `externalId`, same body** → returns the previously-created transaction. No duplicate. Exit code `0`.
* **Same `externalId`, different body** → exit code `6` (conflict), `code: EXTERNAL_ID_CONFLICT`.
* **Without `externalId`** → every retry is a brand-new transaction. Don't do this on transaction-creation calls.

For shell pipelines or agent flows that retry on perceived failure, this is the only safe path. Generate `--externalId` from something stable (task ID, request hash, timestamp + nonce); keep it in scope across retries.

```bash theme={"system"}
EXTID="payout-$(date +%Y%m%d)-$(openssl rand -hex 4)"

# Retry-safe — third attempt returns the same tx as the first.
for i in 1 2 3; do
  bron tx withdrawal --externalId "$EXTID" --accountId <a> --params.amount=100 ... \
    && break
  sleep 5
done
```

## Retry strategy

The CLI itself retries idempotent reads (`GET`) on transient 5xx with linear backoff up to 3 attempts; you don't have to wrap reads in a retry loop. **Writes are not retried automatically** — that's the caller's responsibility (with `--externalId` so retries are safe).

If you hit `429` (`RATE_LIMITED`), respect the `Retry-After` header in the response. The CLI prints it as `details.retryAfter`.

## Reporting an issue

When the error isn't obviously self-inflicted (config / bad input):

1. Re-run with `--debug` to capture envelope + ping + dial logs to stderr.
2. Note the `id:` value from the error envelope.
3. File the issue at [https://github.com/bronlabs/bron-cli/issues](https://github.com/bronlabs/bron-cli/issues) with: command (redact secrets), CLI version (`bron --version`), error envelope, `id`, `--debug` log if relevant.

Inside Bron, that ID is enough to pull the exact ES log entries for the call across every service it touched.
