# Submit API reference

> Request path, query parameters, accepted transaction encodings, response fields, leader targeting, and retry behaviour for the crypto submit routes.

The Submit screen in the FirstByte console lands one raw signed
transaction over HTTP/3. It is a thin front end over two routes:
`/bdn/submit` (fan-out) and `/tpu` (direct). This page documents the
request it builds, the encodings it accepts, and what comes back.

| Route in the UI        | Method + path  | Console description                    |
| ---------------------- | -------------- | -------------------------------------- |
| `/bdn/submit · fan-out` | `POST /bdn/submit` | "BDN fan-out — lands on all edges"  |
| `/tpu · direct`         | `POST /tpu`        | "Direct TPU — current+next leader"  |

Both are called over HTTP/3 (`curl --http3`) with a bearer API key.

## Request path, query parameters, and body

The route and the leader target are carried entirely in the query
string. The transaction is the request body.

**Fan-out route**

```
POST /bdn/submit?leader=auto
POST /bdn/submit?leader=<host>:<port>
```

**Direct TPU route**

```
POST /tpu?host=auto
POST /tpu?host=<host>&port=<port>
```

Note the asymmetry: `/bdn/submit` takes a single `leader` parameter with
the host and port joined by a colon, while `/tpu` splits them into
`host` and `port`. In auto mode `/tpu` sends `host=auto` and no `port`
parameter at all. The console labels the port field `port` on the
fan-out route and `quic_port` on the direct route, but the query
parameter is named `port` in both cases.

The body is the signed transaction, sent as raw bytes:

```bash
curl --http3 \
  "$CRYPTO_BASE_URL/bdn/submit?leader=auto" \
  -H "Authorization: Bearer $CLUTCHCALL_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @tx.bin
```

The console's **Equivalent request** card renders this same curl with
the fully-qualified host for your environment and with the route and
leader you have selected, so you can copy it straight out of the screen.

When the console itself submits, it does not post binary. It calls the
`submit` mutation with the transaction as a string plus an encoding
hint:

| Field    | Value the console sends                                            |
| -------- | ------------------------------------------------------------------ |
| `orgId`  | The id of the active org in the org switcher.                       |
| `tx`     | The textarea contents, trimmed.                                     |
| `enc`    | `"hex"` or `"base64"` — derived from `tx`, see below.                |
| `route`  | `"bdn"` or `"tpu"`.                                                 |
| `leader` | `"auto"`, or `"<host>:<port>"` in explicit mode.                    |
| `bytes`  | `tx.length` — the character length of the trimmed string, **not** the decoded byte count. |

`leader` is always sent in the `"<host>:<port>"` form when the mode is
explicit, including on the `/tpu` route where the URL splits the two
halves apart again. That is why both the host and the port field are
mandatory in explicit mode.

## Accepted transaction encodings

The textarea accepts a **base58** or **base64** encoded signed
transaction. Anything outside those two alphabets is rejected in the
browser with `Paste a base58 or base64 encoded signed transaction`
before a request is made.

The encoding actually forwarded is derived from the text, not chosen by
you:

| Pasted text                                   | `enc` sent |
| --------------------------------------------- | ---------- |
| Starts with `0x` (case-insensitive)           | `hex`      |
| Consists only of hex digits (`0-9a-fA-F`)     | `hex`      |
| Anything else that passed validation          | `base64`   |

Two consequences are worth knowing:

- **Hex is forwarded even though the field advertises base58/base64.**
  A `0x`-prefixed string passes validation through the base64 character
  class and is then tagged `hex`, so pasting hex works.
- **Short all-hex strings are ambiguous.** A string made only of
  characters that are both valid hex digits and valid base58 digits is
  tagged `hex`, not base58. If your transaction encoding happens to land
  in that overlap, submit it as base64 to remove the ambiguity.

`base64` is the fallback because it is the Solana RPC default.

## Response fields and errors

The mutation resolves to an object the console reads three fields from:

| Field | Type      | Meaning                                                     |
| ----- | --------- | ----------------------------------------------------------- |
| `ok`  | boolean   | Whether the submit was accepted.                            |
| `sig` | string    | The transaction signature, present when `ok` is true.       |
| `err` | string    | Failure reason, read when `ok` is falsy.                    |

There are three outcomes:

1. **`ok: true`** — the console shows the signature (copyable in full),
   a status line of `accepted · fanning out`, and the route you used.
2. **`ok` falsy** — the console renders a *Submit failed* panel with
   `err` as the detail, falling back to `submit failed` if `err` is
   empty.
3. **The request itself throws** — the thrown error's `message` is
   rendered in the same panel, falling back to `request failed`.

`accepted · fanning out` is a label the console prints locally on
success. It is not a status value returned by the route.

The console's result object also carries `ms`, `fanout`, `bytes`,
`leaders` and `slot`, but it sets all five to `null` on success — they
are placeholders and are not populated from the submit response. Do not
build tooling that expects timing, fan-out counts or a landed slot back
from this call.

## Validation performed before the request is sent

The Submit button is disabled until an org is active, and nothing is
sent while a submit is in flight. On click, three checks run and, if any
fails, the errors are revealed inline and no request is made:

| Field            | When checked          | Rule                                              |
| ---------------- | --------------------- | ------------------------------------------------- |
| Host             | Explicit leader mode  | Required, and must be a valid host.               |
| Port             | Explicit leader mode  | Required, and must be a valid port.               |
| Raw transaction  | Always                | Required, and must be base58 or base64.           |

In auto mode the host and port fields are not rendered and are not
validated.

## leader=auto versus an explicit host and port

**Auto-discover** sends `leader=auto` (fan-out) or `host=auto` (direct)
and lets the route pick the leader target. This is the default and the
mode you want unless you are deliberately pinning traffic.

**Explicit host** requires both a host and a port, and always transmits
them as a single `"<host>:<port>"` value to the control plane. Use it to
reproduce a landing against one known edge or one known leader, for
example when you are comparing two paths or chasing a
host-specific failure. Because the pair is mandatory, a host with an
empty port never reaches the route — you get a field error instead of a
malformed target.

## Which RPC endpoint an org-scoped submit uses

A submit from the console is **org-scoped**. It carries the `orgId` of
the org selected in the switcher, and it lands via that tenant's own RPC
endpoint — the BYO-RPC endpoint configured under **API keys**. Two
orgs submitting byte-identical transactions from this screen therefore
egress through two different RPC endpoints.

This matters when you debug a submit that fails: check the org selected
in the switcher, then check that org's RPC configuration in API keys,
before you suspect the route.

Signing in is only a requirement for the console path. While you are
signed out the Submit button is disabled and the screen says so, but the
API accepts your key regardless — the curl form in
**Equivalent request** works with a bearer key and no portal session.

## Signature dedup and safe retries

On Solana the signature is the **first 64 bytes** of the transaction, and
that signature is what the submit path dedups on. Two implications:

- Resubmitting the *identical* signed bytes produces the identical
  signature, so it collapses onto the same dedup key rather than
  creating a second independent landing attempt. Retrying after a
  transport error is therefore safe in the sense that it cannot
  duplicate the transaction.
- Re-signing the same instruction set produces a *different* signature
  and is a genuinely new submit. If you want to force another attempt,
  that is the difference to be aware of.

The console clears the previous signature and error on every new submit,
so the Result card always reflects the most recent attempt only.

## Related

- **API keys** — where the org's BYO-RPC endpoint and API keys live.
- [Authentication](/concepts/authentication) — how the bearer key on the
  submit request is scoped.
- [Telemetry](/platform/telemetry) — where to look for submit-side
  metrics, since the submit response does not return timings.
