Accepted Does Not Mean Landed
A successful reply means the Apex endpoint holds your transaction and is racing it to the leaders until it lands or its blockhash expires. It does not mean the transaction landed. Confirm every send withgetSignatureStatuses on a Solana RPC. See Best practices.
Why an Accepted Transaction Did Not Land
Your dashboard records how each accepted transaction ended. When it did not land, one of these codes says why:
A transaction that landed but failed on chain shows as failed: its instructions returned an error, so the tip was never paid.
Apex checks a transaction in this order: API key, rate limit, transaction validity and size, then the tip. The first failed check decides the error. Nothing is sent anywhere until every check passes, so a rejected transaction costs nothing.
JSON-RPC Errors
Plain HTTP Errors
On/send and /send-bin, success is HTTP 200 with {"signature": "..."}. /send-batch replies 200 with {"attempted": n, "accepted": n, "rejected": n, "results": [...]}, where each result, in frame order, is either {"signature": "..."} or {"error": "<label>", "message": "..."}. /send-bundle replies 200 with {"bundle_id": "...", "signatures": ["...", "..."]}.
Errors are JSON with a machine-readable label and a human-readable message:
Branch on the HTTP status first. It is the stable part of the contract. Treat a label you do not recognize under
400 as “fix the transaction”./send-batch replies 200 whenever the request itself is well formed, and reports each frame on its own inside results. Check rejected and each entry rather than the status code alone. See POST /send-batch.
The bundle label (HTTP 400) means a bundle was refused as a whole: more than 4 members, a duplicate member, bundles not available for this key or endpoint, or the block engine refused it. When the block engine refuses it, the message says why if Apex recognizes the reason (a member already processed, an expired or unknown blockhash, a member the block engine has already seen) and reads “the block engine rejected the bundle” otherwise; retrying the same bundle will fail again. A problem with one member keeps its own label. A member over its size limit (4096 bytes as v1, 1232 as legacy or v0) comes back as malformed with a message naming the limit, two tipped members as multiple_tips, and no tipped member as no_tip. A bundle the endpoint cannot hand to the block engine right now is answered busy (HTTP 503, JSON-RPC -32603): the endpoint’s block engine allowance for that second is spent, the block engine is rate limiting the endpoint, or it did not answer. Nothing was sent; retry it shortly, or send the transactions singly.
The nonce_used label (HTTP 400, JSON-RPC -32602) refuses a durable nonce transaction signed against a nonce value the endpoint has already seen replaced: another transaction used that nonce, so this one can never land. Nothing is sent. A value the endpoint has not seen is accepted as usual.
Tip Errors in Detail
QUIC Admission Codes
A bidirectional QUIC stream answers with one admission code. A unidirectional stream returns nothing, so use a bidirectional stream while you integrate.
If the QUIC handshake itself is refused, the connection closes with application error
1 (no client certificate), 2 (unknown key), or 3 (too many connections). See Authentication.
In the Rust client, send_transaction_with_response turns a rejection into Error::Rejected { code, message }, and a transaction over 4096 bytes fails locally with Error::TooLarge before anything is sent. In the Go client, SendTransactionWithResponse returns a *apex.RejectedError with the same Code and Message, and an oversized transaction fails locally with *apex.TooLargeError.
Which Errors to Retry
You do not need to resend an accepted transaction. Apex already retries it until it lands or the blockhash expires. If it has not landed by then, rebuild it with a fresh blockhash and send the new transaction.
Rate Limits
Rate limits are per API key and set by the key’s tier. The limit counts transactions per second across every transport: JSON-RPC, the HTTP routes, and QUIC all draw from the same allowance, and each frame of a batch counts as one transaction. The standard tier allows 5 transactions a second with no burst: at most 5 at once. The pro tier allows 50 a second, and 10 more at once on top of that. A limit agreed for your account replaces the tier’s rate and keeps its burst. When you exceed the limit you get-32029, HTTP 429, or admission code 2. The rejected transaction is not queued, so resend it after a moment if it still matters.
Your tier’s limit and tip floor are shown next to the key in the OrbitFlare dashboard under Dashboard > Apex. To raise them, contact the team on Discord.