Skip to main content

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.

Start with the operational spec

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:

ModeMeaningBeta guidance
ackRequest accepted at the low-latency boundary.Confirm per account before using the x-bsl-result-mode header.
durableWAL/durable boundary reached.Confirm per account.
fullEngine-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-Nonce ownership 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 + detailed compact path before enabling durable or full result 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, or MARKET_HALTED, fail fast in the strategy rather than retrying blindly.
  • Run node scripts/e2e/mm-docs-contract-audit.cjs before giving a client a new endpoint bundle.