# Falcon errors & retries (/falcon/errors)

> Every Falcon rejection by transport: JSON-RPC codes and messages, raw HTTP statuses, QUIC SubmitError codes, UDP silence, and what to retry.



The same rejection looks completely different depending on how you submitted it.

| Transport               | Where the error appears                                                        |
| ----------------------- | ------------------------------------------------------------------------------ |
| JSON-RPC                | `error` object in the body, usually with HTTP `200`                            |
| `/binary`, `/plaintext` | HTTP status + a short plain-text body                                          |
| QUIC                    | `SubmitError` from `falcon-client`, or a connection close with a reason string |
| Native UDP              | Nowhere. Nothing is sent back for any reason                                   |

## JSON-RPC errors

Falcon answers with HTTP `200` for almost every JSON-RPC error. Only two carry a matching status.

|     Code |    HTTP | Message                                              | Cause                                                            | Fix                                                                  |
| -------: | ------: | ---------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------- |
| `-32700` |     200 | `parse error: <detail>`                              | Body is not valid JSON                                           | Send a valid JSON object                                             |
| `-32600` |     200 | `invalid request`                                    | Not a `POST`, body is not a JSON object, or `jsonrpc` is missing | `POST` a JSON-RPC 2.0 object                                         |
| `-32600` |     200 | `unsupported jsonrpc version`                        | `jsonrpc` is not `"2.0"`                                         | Set `"jsonrpc":"2.0"`                                                |
| `-32600` |     200 | `missing method`                                     | No `method` field                                                | Add `sendTransaction` or `getVersion`                                |
| `-32600` |     200 | `request body too large (limit 3072 bytes)`          | Body over 3 KiB                                                  | Shrink the envelope; the transaction itself is capped at 1,232 bytes |
| `-32600` |     200 | `request body read timed out`                        | The body never finished arriving                                 | Resend the same signed transaction                                   |
| `-32601` |     200 | `method not found`                                   | Any method other than the two supported                          | Falcon is not a full RPC; use your Solana RPC                        |
| `-32602` |     200 | See the parameter table below                        | The request or the transaction is invalid                        | Fix and re-sign; do not retry identical bytes                        |
| `-32603` |     200 | `server configuration error: tip accounts not set`   | Server-side tip configuration                                    | Report on [Discord](https://discord.gg/corvus-labs)                  |
| `-32603` |     200 | `preflight check failed: RPC node returned an error` | Upstream simulation RPC errored                                  | Retry, or submit with `skipPreflight: true`                          |
| `-32603` |     200 | `preflight check timed out`                          | Simulation exceeded its timeout                                  | Retry, or submit with `skipPreflight: true`                          |
| `-32000` | **401** | `unauthorized`                                       | `api-key` missing, not a UUID, or not a live key                 | Fix the key; do not retry                                            |
| `-32001` | **429** | `rate limit exceeded`                                | Over your per-second submission allowance                        | Slow down, then resend the same bytes                                |
| `-32002` |     200 | `Transaction simulation failed: <error>`             | Preflight simulation returned an error                           | Read `data`, fix the transaction                                     |

### `-32602` messages

| Message                                                                 | Meaning                                                       |
| ----------------------------------------------------------------------- | ------------------------------------------------------------- |
| `expected params array in request body`                                 | `params` missing or not an array                              |
| `first parameter must be a base58 or base64 encoded transaction string` | `params[0]` is not a string                                   |
| `unsupported encoding`                                                  | `encoding` is neither `base58` nor `base64`                   |
| `transaction string is not valid base58`                                | Usually base64 sent without `"encoding":"base64"`             |
| `transaction string is not valid base64`                                | Malformed base64                                              |
| `transaction exceeds maximum size of 1232 bytes`                        | Decoded transaction over the cap                              |
| `transaction has no valid signature`                                    | Unsigned, or a signature count of 0                           |
| `could not deserialize transaction: verify encoding and format`         | Not a parseable Solana transaction, or extra trailing bytes   |
| `transaction signature count does not match required signatures`        | With `"data":{"expected":n,"actual":n}`                       |
| `transaction does not include required tip`                             | With `"data":{"minimumLamports":n}`; see [Tips](/falcon/tips) |

`-32002` carries the whole simulation result in `data`, including `err`, `logs` and `unitsConsumed`.

## Raw HTTP errors

`/binary` and `/plaintext` answer with a status and a short plain-text body. Several causes share a status, so the body is the only thing that separates them.

| Status | Body                                                             | Cause                                                        | Retry?                 |
| -----: | ---------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------- |
|  `200` | base58 signature                                                 | Accepted and forwarded                                       | —                      |
|  `400` | `failed to read body`                                            | Connection broke mid-body                                    | Yes                    |
|  `400` | `invalid utf8`                                                   | `/plaintext` body is not UTF-8 text                          | No                     |
|  `400` | `invalid base64`                                                 | `/plaintext` body is not valid base64                        | No                     |
|  `400` | `transaction has no valid signature`                             | Unsigned transaction                                         | No                     |
|  `400` | `could not deserialize transaction`                              | Not a parseable transaction                                  | No                     |
|  `400` | `transaction signature count does not match required signatures` | Missing a signer                                             | No                     |
|  `400` | `transaction does not include required tip`                      | Tip missing or below the minimum                             | No                     |
|  `401` | `unauthorized`                                                   | API key missing, malformed or revoked                        | No                     |
|  `405` | `method not allowed`                                             | Used a verb other than `POST`                                | No                     |
|  `408` | `body read timed out`                                            | Body did not arrive in time                                  | Yes                    |
|  `413` | `request body too large`                                         | Over 1,232 bytes on `/binary`, or over 2,048 on `/plaintext` | No                     |
|  `413` | `decoded transaction too large`                                  | `/plaintext` base64 decodes to more than 1,232 bytes         | No                     |
|  `429` | `rate limited`                                                   | Over your per-second allowance                               | Yes, after backing off |

No HTTP route reports a forwarding failure. Once validation passes you get `200` and a signature, whatever the forward does.

## QUIC rejections

Server rejections reach you as a `SubmitError`. On the wire that's two bytes: `0x01`, then the code.

| Code   | `SubmitError`            | Meaning                                                                                                                                                        | Retry?                                                            |
| ------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `0x00` | —                        | Accepted                                                                                                                                                       | —                                                                 |
| `0x01` | `RateLimited`            | Over your per-second allowance                                                                                                                                 | Yes, after backing off                                            |
| `0x02` | `Unsigned`               | Transaction has no valid signature                                                                                                                             | No                                                                |
| `0x03` | `MissingTip`             | Tip missing or below the minimum                                                                                                                               | No                                                                |
| `0x04` | `DeserializeFailed`      | Bytes are not a parseable transaction                                                                                                                          | No                                                                |
| `0x05` | `TooLarge`               | Over 1,232 bytes                                                                                                                                               | No                                                                |
| `0x06` | `ForwardFailed`          | Defined in `falcon-client`, but the server does not currently return it; once validation passes the stream ack is always `Accepted`, whatever the forward does | Yes, same bytes                                                   |
| `0x07` | `SignatureCountMismatch` | Signature count ≠ required signers                                                                                                                             | No                                                                |
| `0x08` | `Unauthorized`           | Key revoked or no longer authorized                                                                                                                            | No                                                                |
| other  | `Unknown(code)`          | Newer server rejection                                                                                                                                         | Log the code and ask on [Discord](https://discord.gg/corvus-labs) |

```rust
use falcon_client::SubmitError;

match client.send_transaction(&transaction).await {
    Ok(()) => {}
    Err(error) => match error.downcast_ref::<SubmitError>() {
        Some(SubmitError::RateLimited) => {
            // Back off, then resubmit the same signed bytes.
        }
        Some(rejection) => {
            // Permanent: fix the transaction.
            eprintln!("falcon rejected the transaction: {rejection}");
        }
        None => {
            // Transport-level: timeout, MTU, connection lost.
            eprintln!("falcon transport error: {error}");
        }
    },
}
```

An `Err` with **no** `SubmitError` inside is a transport problem rather than a rejection, and by the time you see it the SDK has already reconnected and retried once.

### Connection-level failures

When the server closes the QUIC connection, it gives you a readable reason:

| Reason string                                                                                    | Cause                                        | Fix                                                              |
| ------------------------------------------------------------------------------------------------ | -------------------------------------------- | ---------------------------------------------------------------- |
| `Missing API key in client certificate CN. Support: discord.gg/corvus-labs`                      | The client certificate carried no UUID       | Use `falcon-client`, or set the certificate CN to your key       |
| `Invalid or revoked API key. Support: discord.gg/corvus-labs`                                    | Key is not live                              | Check the key on [Discord](https://discord.gg/corvus-labs)       |
| `Connection limit reached. Close unused connections or upgrade. Support: discord.gg/corvus-labs` | Too many concurrent connections for this key | Reuse one long-lived client instead of connecting per submission |

Individual streams also get reset when one connection has too many in flight at once; cap your concurrent sends rather than opening more connections.

## Native UDP

Native UDP has no error surface at all. Bad frame, bad key, oversized transaction, missing tip, forward failure: every one is the same silence. The full drop list is on [Native UDP](/falcon/udp#why-a-datagram-is-dropped).

## Retry decision table

| Condition                                                              | Retry? | How                                                               |
| ---------------------------------------------------------------------- | ------ | ----------------------------------------------------------------- |
| `401` / `-32000` / `SubmitError::Unauthorized`                         | No     | Fix the key                                                       |
| Encoding, size, signature or tip rejection                             | No     | Fix the transaction and re-sign; identical bytes fail identically |
| `-32002` preflight simulation failed                                   | No     | Read the simulation logs and fix the transaction                  |
| `429` / `-32001` / `SubmitError::RateLimited`                          | Yes    | Slow to your allowance, then resend the same bytes                |
| `408`, `-32600 request body read timed out`, `-32603` preflight errors | Yes    | Resend the same signed bytes                                      |
| `SubmitError::ForwardFailed`, connection lost, socket error            | Yes    | Resend the same signed bytes                                      |
| Accepted but not landing                                               | Yes    | Resend the same bytes while the blockhash is valid                |
| Blockhash expired                                                      | No     | Rebuild and re-sign; this is a new signature                      |

Stop on blockhash expiry rather than on an attempt count.

## Duplicates

Falcon keeps a signature-keyed cache of recent submissions and short-circuits a duplicate instead of forwarding it again. That only takes effect **after** a submission of that signature has been delivered, so a copy arriving while the first is still in flight is forwarded too. That's why resending the same signed bytes to chase a landing is expected behaviour. Delivery still isn't exactly-once in either direction.

## Acceptance is not landing

Falcon's answer never depends on the outcome of the forward, on any transport.

| Signal                                       | What it proves                                                                      |
| -------------------------------------------- | ----------------------------------------------------------------------------------- |
| JSON-RPC `result` / HTTP `200` + signature   | Validation passed and the forward was dispatched                                    |
| QUIC stream `Ok(())`                         | The server accepted it, **or** the datagram was already queued and the ack was lost |
| QUIC datagram-mode `Ok(())`                  | A packet was queued on your machine                                                 |
| UDP `send()` returning without error         | Your kernel accepted the datagram                                                   |
| `getSignatureStatuses` showing the signature | It landed                                                                           |

Record both sides. [Telemetry](/falcon/telemetry) has the fields to keep.
