Rejects, uncertain outcomes and acceptance
A minimum-notional rejection is an ordinary business outcome. It must not stop the venue or make the same session unusable. Durable result identity checks still apply: conflicting financial state, hashes or durable evidence must not be silently replaced with client-facing text.
Read codes, not prose
HTTP error bodies carry code and requestId; normalized business causes may
also appear as details.rejectCode. Retain the HTTP status and request ID.
ORIGIN_FORBIDDEN, DOMAIN_FORBIDDEN, APP_BINDING_MISMATCH,
INVALID_SIGNATURE and INVALID_DELEGATION_SIGNATURE are distinct failures.
Do not change a signed app/domain field without generating a fresh signature.
On the HTTP compact lane (POST /api/v1/bsl/orders/compact) this does not
hold yet: its error body and receipts carry prose without a machine code for
several common rejects. See Where a reject appears.
FIX/FIXP use their corresponding protocol error fields and execution reports,
not HTTP error bodies. BSL order-state reports carry rejectCode independently
of human-readable text. A gateway reject and an engine terminal are different
stages; both need correlation to the original request.
Package 1 preserves asynchronous FIX reject causes in OrdRejReason(103)
(standard value, usually 99), Senticore tag 9101 (stable numeric cause), and
Text(58) (public message). For example, minimum notional is 9101=1014.
FIXP keeps its existing SBE ExecutionReport layout: for a rejected execution,
the variable-length text is JSON with rejectCode and message. This is
distinct from FIXP BusinessReject codes. Historical replay keeps its original
wire bytes and may lack this metadata; do not demand retroactive enrichment.
Direct TCP transport outcomes
| Numeric reason | Meaning | Client action |
|---|---|---|
100 BackpressureFull | Bounded capacity refused admission | Back off; preserve command identity when retrying |
105 WriteLeaseFenced | Writer/session fencing | Reconnect and reconcile; no automatic replay |
106 DurabilityUnknown | Durable admission/execution may have happened; a response was lost or authority is uncertain | Query status/replay for the original order/sequence; never create a fresh order just because the response is missing |
108 IpcTransportUnavailable | Proven transport failure before this request was submitted | Reconnect/back off; retry only the identical request |
200 AuthFailed | Order authorization failed | Correct key, signature or policy; do not label as overload |
202 SessionViolation | Session identity, sequence or protocol mismatch | Inspect the specific diagnostic and reconcile session state |
Version boundary: reason 108 is live in the current release: IPC transport
failures that earlier gateways reported as 100 BackpressureFull now map to
108 IpcTransportUnavailable. Decoders must accept 108; a 100 from an older
gateway is not sufficient proof that the order never ran. Reason 106 must not
trigger automatic resubmission. Unknown numeric codes must remain visible and
must not default to an automatic retry policy.
HTTP 202 Accepted is unrelated to BSL numeric reason 202. An HTTP acceptance
or COMMAND/Core ACK does not mean that matching or terminal delivery finished.
MM replace: retry before admission, reconcile after uncertainty
The candidate distinguishes two boundaries for an MM cancel/replace request:
- If authoritative nonce acquisition fails before admission and before a COMMAND is published, the resident pre-admission idempotency claim is released. The exact request may acquire that key again after backoff.
- Once COMMAND publication or durable admission may have occurred, a missing response is an uncertain outcome. Keep the original request, nonce, and idempotency identity; reconcile receipts, execution/drop-copy events, and account/order state. Do not submit a newly identified replacement merely because the first response was lost.
This is a narrow MM pre-admission recovery rule. It does not change FIX/FIXP nonce semantics, and a COMMAND ACK remains distinct from terminal execution. It remains candidate behavior until the backend is deployed and the exact failure boundary is verified through the customer lane.
Batch atomicity
A compact request is not all-or-nothing at apply. Ingress checks reject the
whole HTTP request before anything is sequenced; after admission each action
succeeds or is dropped on its own. A QuoteReplace / SpotQuoteReplace is a
single action, so all of its legs succeed or are dropped together. Details and
formats follow.
Where a reject appears
A compact request passes two stages. Where a reject shows up, and what it contains, depends on the stage.
Stage 1: ingress, whole HTTP request
Checks run over every action before anything is sequenced: body decoding and size, authentication, batch size, shape and timestamp skew, the single-account rule, the market lifecycle and expiry, signature, the nonce window, rate limits, and an ingress risk pre-check. The first failing action rejects the whole request:
{ "ok": false, "seqs": [], "acceptedActions": 0,
"error": "market 10 lifecycle=resolved_no is not accepting new orders",
"actionResults": null }
erroris the raw text; there is nocodefield on this lane, andX-SC-Error-Format: envelope-v1does not change the compact response body.- Typical HTTP statuses: 400 for validation (including lifecycle and expiry) and bad signatures, 409 for nonce and ingress risk rejects, 429 for rate limits and backpressure, 503 for a trading halt, ledger recovery or an unavailable write runtime.
- Only the ingress risk and trading-restriction rejects fill
actionResults, one entry per action withstatus: "rejected"andnonceConsumed: false. - No sequence number is assigned, so there is no receipt to poll.
- No action nonce is consumed. Every action of the request can be re-signed
with the same nonce after the cause is fixed. There is no top-level
nonceConsumedfield on this lane;acceptedActions: 0together withseqs: []is the signal.
Stage 2: apply, per action
An admitted request returns ok: true with one sequence number per compact
frame. Each action is then applied on its own. An action rejected in the engine
is dropped; the other actions of the request are not affected. Its receipt
from GET /api/v1/bsl/receipts reads:
{ "seq": 918273, "status": "dropped", "applied": false, "durable": false,
"dropped": true, "batchId": 2090681, "finishedAtMs": 1790169000123,
"detail": "price above binary cap: PriceMicros(1005000)" }
detailis the engine text with line breaks removed, truncated to 240 characters. Depending on the internal path it can carry a prefix such asengine apply failed:orfast precheck failed:; match on a substring.- The receipt has no reject-code field and no
nonceConsumedfield. A coarserejectCode(for exampleENGINE_REJECTED,POST_ONLY_WOULD_CROSS,INSUFFICIENT_BALANCE) with a fixed publicrejectReasonis available inactionResults[](full response mode) and on drop-copy. - A dropped action does not consume its nonce; its terminal result reports
nonceConsumed: false. - A
QuoteReplace/SpotQuoteReplaceis one action. It is sequenced as a group of cancel and place frames; if any leg fails, the group is rolled back and every frame's receipt showsdroppedwith the samedetail. This is why a replace that carries both bids and asks loses all of them when one ask lacks YES/NO shares. See Cancel-replace failure behavior.
Size asks below free inventory (mint YES/NO complete sets with split first),
keep outcome prices in (0, 1000000], align to tick and lot, respect
maxOrderQtyMicro and the minimum notional, and keep post-only legs passive.
Put bids and asks in separate actions when inventory is uncertain.
Rejects without a stable code
These rejects have no stable machine code assigned yet. Until codes exist, match on the listed substrings, which are the current engine and ingress texts:
| Text (substring to match) | Where it appears | Meaning | Client action |
|---|---|---|---|
is not accepting new orders (full text market N lifecycle=S is not accepting new orders) | Ingress error, HTTP 400, whole request rejected | Market status is not open | Stop quoting the market; re-read GET /api/v1/public/markets/{id} |
expired for trading at (market N expired for trading at T) | Ingress error, HTTP 400; in the engine as market MarketId(N) expired for trading at T | Action ts is at or after expiryTsMs; status may still be open | Stop quoting the market |
price above binary cap (price above binary cap: PriceMicros(N)) | Receipt detail; actionResults[].rejectCode: ENGINE_REJECTED | YES/NO price above 1000000 | Clamp outcome prices to at most 1000000; do not retry unchanged |
is not aligned to tick / is not aligned to lot | Receipt detail; ENGINE_REJECTED | Price or quantity off the market grid | Re-read tick/lot and correct |
exceeds max (qty Qty(N) exceeds max Qty(M)) | Receipt detail; ENGINE_REJECTED | Quantity above maxOrderQtyMicro | Split or reduce the order |
post-only order would cross the book | Receipt detail; POST_ONLY_WOULD_CROSS | Post-only leg is marketable | Reprice passively |
InsufficientShares, insufficient YES balance, insufficient NO balance, insufficient USDC balance, RiskWouldExceedAvailableUsdc | Receipt detail; INSUFFICIENT_BALANCE or ENGINE_REJECTED | Free inventory or USDC too low | Refresh balances; reduce size |
Rejects seen on production
| Reject | Lane | Meaning and client action |
|---|---|---|
TRADING_HALTED — "Trading is halted. Wait for trading to resume before retrying." | HTTP / BSL | Not load. Pause quoting and watch tradingHalted and expectedReopenMs on GET /api/v1/status; do not back off and retry. |
403 AGENT_AUTH_FAILED — "This credential lacks permission" | HTTP / BSL | Legacy or deployed routes may use this broad wrapper for a missing scope or other authorization failure. Fix the credential or policy; retrying unchanged credentials does not help. |
403 API_CREDENTIAL_MISMATCH | HTTP / BSL candidate | The authenticated credential snapshot, agent, account, or API-key ownership no longer matches the active principal. The candidate keeps this cause distinct and sanitizes the public body; do not diagnose it as a generic scope failure. Deployment/readback is pending. |
205 — "above replay window" | BSL | The action nonce is above nonceFloor + 256. The reject carries nonceHoles, and those nonces are usable: re-sign with one of them or with any unused in-window nonce. nonceFloor, nonceHoles and nonceUsedInWindow are also on GET /api/v1/accounts/{account}. |
Per-account release gate
On an authorized isolated fixture first, then the approved live canary:
- Authenticate on the actual customer lane (HTTP, BSL TCP, FIXP, FIX).
- Submit a below-minimum order and require its correlated reject. Check that it did not reserve funds or leave a live order; a consumed nonce may be valid.
- Submit a valid order on the same session, then cancel it. Require terminal confirmation, not just PendingNew or a queue ACK.
- Retry/reconnect using original identities; verify no duplicate order/fill, no lost reject, no resurrected cancel and consistent durable replay.
- Exercise connection failure before send and after send/before response. Only the former permits a proven-not-submitted response.
These lane tests alone do not prove DMS cancellation. The Core-owned
HTTP/BSL DMS is configured in the September 12
private-beta deployment for explicitly marked orders. Arm it and maintain its
signed heartbeats; session-key cancel-on-disconnect is a separate mechanism.
The current DMS supports one shard and Core restart recovery, not hot-standby
promotion. A bounded live BSL Spot expiry canary passed September 12 after a
reader replay correction. It checked actual enrollment, exact terminal evidence,
and absence from both openOrders and the tested book level. Completion of an
empty epoch or an ACK alone cannot pass this check.
Publish ACK, durable result and terminal-at-client latency separately, with offered load, completed OPS, session count, network location and errors. A successful handshake or a short localhost smoke is not an end-to-end SLA.