BSL Order Entry
BSL order entry is the professional submit surface for quote engines and other latency-sensitive clients. In private beta, separate the tested submit path from the target result-mode contract.
If you are wiring an institutional or market-maker account for the first time,
start with
Institutional Operational Spec
and
Institutional Hotpath Latency
for ACK boundaries, nonce rules, and sub-50 ms measurement discipline, then
Place Your First Institutional BSL Quote.
The most common production blocker is using a 32-byte accountIdHex where the
order-entry and balance routes expect the 20-byte engine account.
Tested Submit Paths
Use the direct institutional binary route when you want the canonical binary HTTP submit surface:
POST /api/order-entry/binary
Content-Type: application/x-senticore-order-entry-batch
Accept: application/x-senticore-order-entry-batch-response, application/json
X-BSL-Result-Mode: ack
X-Senticore-Response-Mode: detailed
Idempotency-Key: strategy-42-batch-9001
X-Senticore-Order-Entry-Key: <optional provisioned lane key>
Or use an institutional_agent HMAC credential:
POST /api/v1/bsl/orders/compact
Content-Type: application/x-senticore-order-entry-batch
Accept: application/x-senticore-order-entry-batch-response, application/json
X-BSL-Result-Mode: ack
X-Senticore-Response-Mode: detailed
Idempotency-Key: strategy-42-batch-9001
SC-Auth-Version: 2
SC-Key: <institutional apiKeyId>
SC-Nonce: <monotonic nonce>
SC-Timestamp: <unix ms>
SC-Passphrase: <apiPassphrase>
SC-Signature: <hmac over method/path/query/body>
The direct trading-plane compatibility route is:
POST /api/v1/mm/orders/batch.bin
The canonical BSL compact facade is:
POST /api/v1/bsl/orders/compact
Read the live order-entry contract before starting a quote loop:
GET /api/v1/bsl/limits
The response includes nonceModel and errorGuidance. Use those fields as the
machine-readable contract for SC-Nonce, action payload.nonce,
Idempotency-Key, nonce-window rejects, queue limits, and risk rejects. This is
the runtime companion to the static Error Model.
Use the BSL facade with an institutional_agent HMAC credential that has
quote scope, or with a dedicated lane key sent as
X-Senticore-Order-Entry-Key when the connectivity bundle provisions one. A
standard api_agent is rejected for BSL submit even if it can use normal HTTP
trading routes.
Payload
The body is SDK JSON encoded as bytes:
{
"version": 1,
"actions": [
{
"payload": {
"account": "0x1111111111111111111111111111111111111111",
"nonce": 4810,
"ts": 1781715570085,
"action": {
"PlaceOrder": {
"market": 1,
"book": "YES",
"side": "Bid",
"price": 100000,
"qty": 1000,
"time_in_force": "gtc",
"stp_mode": "skip_self"
}
}
},
"signature": {
"scheme": "EcdsaSecp256k1",
"bytes": [1, 2, 3]
}
}
],
"idempotencyKey": "strategy-42-batch-9001"
}
Every mutating action still needs a valid account or delegated signature. The institutional HMAC or optional order-entry key authorizes the submit surface; it does not replace the signed action.
Use the 20-byte engine account in payload.account. For subaccounts, obtain it
from GET /api/v1/trading/accounts?owner=... as engineAccountHex. Do not use
the 32-byte accountIdHex in the signed action payload.
Transport headers
Prefer X-Senticore-Order-Entry-Key for dedicated low-latency lane keys. Legacy
headers X-MM-Key, X-API-Key, and bearer compatibility may remain accepted on
some deployments, but new clients should not document or monitor those as their
primary auth path.
The direct binary route accepts application/x-senticore-order-entry-batch.
Legacy application/x-senticore-mm-batch and application/octet-stream are
compatibility content types. Unsupported content type is a 415; malformed
batches are 400; oversized bodies are 413.
Beta Response
With X-BSL-Result-Mode: ack and X-Senticore-Response-Mode: detailed, a
successful response includes:
{
"ok": true,
"seqs": [123],
"derivedOrderIds": ["0x..."],
"acceptedActions": 1,
"responseMode": "detailed",
"ackMode": "ingress_wal",
"durableLsn": 22376075,
"bsl": {
"reconciliation": {
"ackIsTerminalState": false,
"bodyIncludesSeqs": true,
"bodyIncludesDerivedOrderIds": true,
"missingDerivedOrderIdCount": 0,
"missingDerivedOrderIdsRecoveredLocally": 0,
"missingDerivedOrderIdsFinalMissingAfterLocalDerivation": 0,
"quoteReplaceSummary": {
"source": "request_payload",
"scope": "explicit_legs_only",
"quoteReplaceActions": 1,
"legs": 2,
"cancelLegs": 1,
"placeLegs": 2,
"requestedLegsReturned": 2,
"requestedLegsTruncated": false,
"requestedLegsOmitted": 0,
"requestedLegsTruncationScope": "response_summary_only_not_execution",
"submitLegsTruncatedByFacade": false,
"quoteFamilyIdSource": "deterministic_account_market_book_side_hash",
"quoteFamilyScope": "account_market_book_side",
"quoteFamilyScopeCount": 1,
"quoteFamilyPersistedServerTag": false,
"quoteFamilyScopes": [
{
"quoteFamilyId": "qf_...",
"account": "0x...",
"instrument": "binary",
"market": 1,
"book": "YES",
"side": "Bid",
"scope": "account_market_book_side",
"requestedCancel": 1,
"requestedPlace": 2,
"requestedPriceLevels": 2,
"maxPriceLevelsPerSidePerBook": 1000,
"exceedsMaxPriceLevelsPerSidePerBook": false,
"priceLevelOverflowBehavior": "prune_worst_price_level",
"terminalCountsAvailableInAck": false,
"recovery": "cancel_all_by_market_book_side_then_seed_fresh_quote_family"
}
],
"priceLevelLimit": {
"maxPriceLevelsPerSidePerBook": 1000,
"maxPriceLevelsUnlimited": false,
"overflowBehavior": "prune_worst_price_level",
"overflowRejectsRequest": false,
"warningCount": 0,
"warnings": [],
"ackIsTerminalPublicBookRetentionProof": false
},
"requestedLegs": [
{
"parentActionIndex": 0,
"parentActionKind": "QuoteReplace",
"actionNonce": 4810,
"clientOrderId": "quote-yes-4810",
"legIndex": 0,
"market": 1,
"book": "YES",
"side": "Bid",
"quoteFamilyId": "qf_...",
"quoteFamilyScope": "account_market_book_side",
"price": 490000,
"qty": 1000,
"cancelOrderId": null,
"intent": "place",
"status": "requested_not_terminal",
"derivedOrderIdOrdinal": 0,
"expectedDerivedOrderId": "0x...",
"expectedDerivedOrderIdSource": "local_signed_payload_derivation",
"derivedOrderId": "0x...",
"derivedOrderIdSource": "response.derivedOrderIds",
"responseDerivedOrderIdMissing": false,
"derivedOrderIdMissing": false,
"terminalStatusAvailableInAck": false
}
],
"replacementCounts": {
"requestedCancel": 1,
"requestedPlace": 2,
"retained": null,
"rejected": null,
"terminalCountsAvailableInAck": false
},
"terminalCountsRequire": "full_result_or_execution_stream_reconciliation"
},
"receiptPath": "/api/v1/bsl/receipts?account={account}&seqs={seqs}",
"missingDerivedOrderIdsAction": "derive_locally_from_signed_payload_or_reconcile_by_seq"
},
"timing": {
"durationsUs": {
"coreAck": 3830,
"facadeAugment": 380
}
}
}
}
This is an acceptance and acknowledgement response. For final order state, reconcile from private streams, FIX drop-copy, account/order reads, or gap-fill where provisioned.
The bsl.reconciliation object is the machine-readable recovery contract. In
ack mode, ackIsTerminalState is false: persist seqs[], lastSeq,
seqChecksum, derivedOrderIds[], clientOrderId, and the HTTP
Idempotency-Key, then reconcile by private stream/drop-copy or
receiptPath. If derivedOrderIds[] is missing or contains nulls, derive the
expected order id locally from the signed payload when possible and otherwise
reconcile by seqs[] before sending a cancel/replace that depends on the
order id.
Read GET /api/v1/bsl/executions at startup for the stream/replay contract.
On private stream disconnect, sequence gap, or checksum mismatch, pause quoting
for the affected engine account and call
GET /api/v1/bsl/accounts/{account}/executions?fromSeq={last_contiguous_seq+1}.
The fromSeq cursor is inclusive; apply events in ascending seq, dedupe by
seq, then resume only after the local stream is contiguous again.
If local order-id tracking is lost, do not send a broad QuoteReplace and hope
old orders disappear. Call POST /api/v1/bsl/orders/cancel-all for the affected
engine account and optional market, book, and side, then wait for private
stream, drop-copy, receipts, or account order reads to confirm terminal cancel
state before seeding a fresh quote family.
For JSON BSL submits containing QuoteReplace or SpotQuoteReplace,
bsl.reconciliation.quoteReplaceSummary is request-derived and cheap to return
in ACK mode. It counts the requested cancel/place legs and states that terminal
retained/rejected counts require full mode or stream/drop-copy
reconciliation. requestedLegs[] echoes the submitted quote legs with
parentActionIndex, legIndex, market/book/side/price/qty, optional
cancelOrderId, and the request intent (place, cancel, cancel_and_place,
or noop). derivedOrderIdOrdinal maps place legs to the ordinal position in
derivedOrderIds[] when the response mode includes those IDs. The facade also
adds expectedDerivedOrderId for every returned place leg by deriving the child
order id from the signed parent payload and leg index. If the response array is
absent, shorter, or null at that ordinal, responseDerivedOrderIdMissing is
true and derivedOrderId falls back to the locally derived expected id with
derivedOrderIdSource: "local_signed_payload_derivation". Only when neither
response nor local derivation is available does derivedOrderIdMissing become
true. The ordinal and ID are null for cancel-only/noop legs.
Each returned leg also carries quoteFamilyId, a deterministic scope id for
account + market + book + side (book is null for spot). The top-level
quoteFamilyScopes[] array aggregates requested cancel/place counts per scope.
This is a reconciliation and recovery handle, not a persisted engine-side order
tag: if local child-order tracking is lost, use the affected scope with
POST /api/v1/bsl/orders/cancel-all, wait for terminal cancel evidence, then
seed a fresh quote family.
The summary also compares requested place price levels per quote-family scope
against orderBook.maxPriceLevelsPerSidePerBook. If a request exceeds the
configured visible depth, priceLevelLimit.warningCount is nonzero and the
warning tells the client to shrink the quote grid or expect worst-price-level
pruning. ACK mode is not terminal public-book-retention proof.
acceptedActions counts signed parent actions, not quote legs. Top-level
missingDerivedOrderIdsRecoveredLocally and
missingDerivedOrderIdsFinalMissingAfterLocalDerivation summarize that fallback
for bots that do not inspect every leg. If requestedLegsTruncated is true,
the truncation applies only to the response echo; submitLegsTruncatedByFacade
remains false, and aggregate counts still cover the full submitted
QuoteReplace request.
In full mode, terminal per-action state appears under actionResults[].
Detailed early validation or risk rejects can also include actionResults[]
with status: "rejected", rejectCode, rejectReason, and optional risk.
Treat rejected entries as terminal action state even when the HTTP status is
200.
Rejected BSL JSON responses also include bsl.reject when the facade can
classify the failure. That object is meant for bots:
{
"bsl": {
"reject": {
"code": "nonce_below_floor",
"domain": "action_nonce",
"scope": "engine_account",
"field": "payload.nonce",
"observedNonceFloor": 42,
"nonceFloor": 42,
"nonceWindow": 256,
"nextUsableNonce": 42,
"validRange": "[nonceFloor, nonceFloor + nonceWindow)",
"retriable": false,
"resignRequired": true,
"clientAction": "resign_with_nonce_floor_or_any_unused_in_window_nonce"
}
}
}
Do not parse these numbers out of rejectReason prose — read the fields.
nonceFloor is the lowest nonce the engine will still accept, and
nextUsableNonce is a convenience hint equal to it — the same semantics the
HTTP lane's SubmitResponse.nextUsableNonce already publishes. Any unused nonce
in validRange is equally acceptable.
The floor is computed from applied state, so it does not know about your own
in-flight submissions: if another of your batches already holds the floor you
will get nonce_replayed back, whose clientAction is
choose_different_unused_in_window_nonce_and_resign. Pipelining clients should
therefore treat nextUsableNonce as a resync anchor after a reject, not as an
allocator.
Machine-auth replay rejects use domain: "machine_auth_nonce",
scope: "api_credential_or_agent_context", and header: "SC-Nonce".
Credential rejects use domain: "authentication" and tell the client to verify
the institutional_agent type, quote scope, or dedicated order-entry key.
Target Result Modes
The BSL result boundary is selected with X-BSL-Result-Mode:
| Mode | Meaning | Beta guidance |
|---|---|---|
ack | Request accepted at the low-latency boundary. | Confirm per account before using the x-bsl-result-mode header. |
durable | WAL/durable boundary reached. | Confirm per account. |
full | Engine-applied result returned in the response. | Target contract for IOC/FOK/cancel/replace, but not the default beta happy path until conformance passes. |
For market makers, full is valuable only if it is actually enabled and tested
on the live path. A late or inconsistent full response is worse than a fast
accepted response plus a reliable private stream/drop-copy.
Response payload verbosity is a separate axis. Use
X-Senticore-Response-Mode: detailed when the client needs seqs, derived
order ids, ack metadata, and per-action errors in the body. The compatibility
alias X-MM-Response-Mode: detailed is still accepted by the beta gateway, but
new BSL integrations should use X-Senticore-Response-Mode.
Nonce and error contract
GET /api/v1/bsl/limits returns:
nonceModel.actionNonce: action nonce owner, valid window, and re-sign rule.nonceModel.machineAuthNonce:SC-Nonceownership and replay rule.nonceModel.idempotencyKey: retry scope and conflict behavior.errorGuidance.nonceCodes: stable nonce reject classes and client action.errorGuidance.authCodes: auth replay and credential recovery guidance.errorGuidance.requestCodes: queue, duplicate nonce, and risk-limit actions.
For any nonce reject, do not replay the same signed payload with only a new
Idempotency-Key. Re-read account/bootstrap state, allocate an unused in-window
action nonce, re-sign, and then submit with a new strategy idempotency key.
Client Order IDs
Use clientOrderId when the path carries it into the signed action payload
and the engine result. FIX maps ClOrdID(11) to the same strategy identity.
Do not use clientOrderId as an HTTP retry dedup key. Use Idempotency-Key
for retry safety and clientOrderId for strategy reconciliation.
Client Rules
- Use the tested
ack+detailedcompact path before enablingdurableorfullresult modes. - Treat HTTP 200 as an accepted boundary, not terminal order truth.
- Persist idempotency key, nonce, market, derived order id, and client order id before sending.
- On
SHARD_BUSY,QUEUE_LIMIT,RISK_CREDIT_LIMIT,KILL_SWITCH, orMARKET_HALTED, fail fast in the strategy rather than retrying blindly. - Run
node scripts/e2e/mm-docs-contract-audit.cjsbefore giving a client a new endpoint bundle.