> ## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.