Skip to main content

Institutional Operational Spec

This page is the contract a market-maker or institutional integrator should read before writing production code. The API reference lists routes; this page explains which identifiers, credentials, nonces, balances, and replay rules must be used together.

Integration invariants

  1. Use the engine account for balances, nonces, signed actions, BSL order entry, FIX Account(1), FIXP credentials, private streams, and risk checks.
  2. Use the trading account id only for control-plane trading-account management routes.
  3. Use GET /api/v1/accounts/:engineAccount/bootstrap?fresh=true before the first quote and after any risk rejection. balances.freeUsdcMicro is the operational free USDC field for order admission; risk.freeUsdcMicro should match when the matching-engine view is current.
  4. Use an institutional_agent HMAC credential for BSL, FIX, FIXP, private reads, and drop-copy. A normal api_agent is not the institutional order-entry credential.
  5. Every mutating order action still carries a signed action payload. HMAC/FIXP authenticates the transport; it does not replace the action signature unless the lane explicitly defines a session-auth execution model.
  6. Treat fast acknowledgements as admission only. Final state comes from private streams, drop-copy, account/order reads, receipts, or gap-fill.

Account model

Senticore uses several account identifiers. They are not interchangeable.

NameShapeSourceUse forDo not use for
Owner wallet20-byte EVM address, 0x + 40 hex charsWallet that signs owner/admin authorizationsAgent creation, trading-account owner operations, human custody identityBSL signed action account when trading from a subaccount
Engine account20-byte engine AccountId, 0x + 40 hex charstradingAccount.engineAccount, main account defaults to owner wallet/api/v1/accounts/:account/*, /api/v1/trading/accounts/:account/balances, signed action payload account, FIX Account(1), FIXP account, private streams, nonce windowsTrading-account control-plane route ids
Trading account id32-byte accountIdHex, 0x + 64 hex charsGET /api/v1/trading/accounts?owner=.../api/v1/trading/accounts/:account_id, patch/archive/cancel-all/transfer control-plane operations, optional agent authorization bindingBalance/bootstrap/order-entry routes that parse AccountId
Agent idagt_...POST /api/agents/createCredential lifecycle, audit events, revocationOrder payload account
API key idspk_...Agent HMAC/Ed25519 credential creationSC-Key, FIX Username(553), FIXP negotiate credentialsSigned action account
Order id32-byte 0x + 64 hex charsDerived from signed payload or response derivedOrderIdsCancel, replace, reconciliationAccount reads

The main-account case hides the difference because the owner wallet and engine account are the same 20-byte value. Subaccounts make the difference explicit: the control plane returns a 32-byte accountIdHex and a 20-byte engineAccount.

Correct sequence:

GET /api/v1/trading/accounts?owner=0xOwnerWallet
Authorization: Bearer <read token>
{
"ownerWalletHex": "0x1111111111111111111111111111111111111111",
"items": [
{
"accountIdHex": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"ownerWalletHex": "0x1111111111111111111111111111111111111111",
"kind": "sub",
"subaccountIndex": 7,
"label": "MM YES book",
"status": "active",
"engineAccountHex": "0x2222222222222222222222222222222222222222"
}
]
}

Use engineAccountHex from that response for balances and order entry:

GET /api/v1/accounts/0x2222222222222222222222222222222222222222/bootstrap?fresh=true
Authorization: Bearer <read token>

Do not call the balance/bootstrap routes with 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; those routes parse a 20-byte engine AccountId and will fail or point at the wrong concept.

Endpoint auth matrix

Endpoint or laneAccount parameterRequired transport authRequired action authScopeNotes
GET /api/v1/public/*, public WSnonenonenonenonePublic market data.
POST /api/agents/createowner wallet in signed messagenone beyond wallet signatureEIP-191 owner wallet authorizationownerCreates api_agent or institutional_agent; returned HMAC secret material is shown once.
POST /api/agents/:id/credentials/createagent owner walletwallet admin signatureEIP-191 wallet admin messageownerRotate or issue HMAC/Ed25519 credentials.
GET /api/v1/bsl/connectivity, GET /api/v1/fix/connectivitycredential-bound accountSC-Auth-Version: 2 HMAC from institutional_agentnoneread plus business-line accessReturns BSL/FIX hosts, SNI, CompIDs, action signing chain binding.
GET /api/v1/accounts/:engineAccount/bootstrap20-byte engine accountBearer private token or SC-Auth-Version: 2 HMACnonereadUse fresh=true before first quote and after rejects. Treat public-style reads as deployment-specific exceptions only when the beta bundle explicitly says so.
GET /api/v1/accounts/:engineAccount/balances20-byte engine accountBearer private token or HMAC middleware contextnonereadReturns balance section only.
GET /api/v1/accounts/:engineAccount/orders, /fills, /balance-events20-byte engine accountBearer private token or HMAC middleware contextnonereadReconciliation endpoints.
POST /api/v1/ws/token20-byte engine accountBearer/HMAC or account signature depending requestoptional signed token requestread/drop-copyIssues private WS token (spws1...).
POST /api/v1/trading/actionssigned payload accountBearer/HMAC optional by deployment policysecp256k1 signed ActionPayloadtradeGeneral REST action submit.
POST /api/order-entry/binarysigned payload accountX-Senticore-Order-Entry-Key, institutional_agent HMAC where deployed, or approved beta authsecp256k1 signed ActionPayloadquoteCanonical institutional binary HTTP submit.
POST /api/v1/bsl/orders/compactsigned payload accountinstitutional_agent HMAC or provisioned lane key where allowedsecp256k1 signed ActionPayloadquoteCanonical BSL compact facade. Use ack + detailed first.
POST /api/v1/bsl/orders/batchsigned payload accountinstitutional_agent HMAC or provisioned lane key where allowedsecp256k1 signed ActionPayloadquoteJSON BSL facade for wrapped action batches.
POST /api/v1/mm/orders/batch.binsigned payload accountdirect trading-plane compatibility authsecp256k1 signed ActionPayloadquoteCompatibility alias for compact batches.
FIX 4.4Account(1) = engine accountHMAC credential in 553 and 554session-auth lanetrade/cancel/drop_copyRaw TLS TCP, not HTTPS. Use bundle CompIDs.
FIXP/SBEcredentials include engine accountFIXP Negotiate credentialssession-auth lanetrade/cancel/drop_copyRaw TLS TCP, not HTTPS.
POST /api/v1/trading/nonce-reservations/:engineAccount20-byte engine accountBearer/HMACnonetradeDeprecated for most clients; windowed nonces are preferred.
/api/v1/institutional/* reportsroute or query accountBearer/HMACnonereadReporting/provisioning surface, not the self-serve order-entry path.

Order-entry HTTP contract

For direct institutional binary submit, prefer:

POST /api/order-entry/binary
Content-Type: application/x-senticore-order-entry-batch
Accept: application/x-senticore-order-entry-batch-response, application/json
X-Senticore-Order-Entry-Key: <optional dedicated lane key>
Idempotency-Key: <strategy request key>
X-BSL-Result-Mode: ack
X-Senticore-Response-Mode: detailed

The server also accepts legacy application/x-senticore-mm-batch and application/octet-stream bodies on the direct binary route for compatibility. New clients should send application/x-senticore-order-entry-batch. Unsupported content type returns 415, malformed payloads return 400, and oversized payloads return 413; current default max body size is 8 MiB.

Header preference for dedicated lane keys is:

  1. X-Senticore-Order-Entry-Key
  2. X-MM-Key (legacy)
  3. X-API-Key (legacy)
  4. Authorization: Bearer ... (compatibility path)

Document and monitor the primary header in new client code. Treat legacy headers as migration support, not as the first-choice integration contract.

Response payload verbosity is selected with X-Senticore-Response-Mode. Use detailed during integration so seqs, derivedOrderIds[], ack metadata, and early validation/risk actionResults[] are returned when the route can produce them. Successful terminal per-order actionResults[] require X-BSL-Result-Mode: full. X-MM-Response-Mode is still accepted by the current beta gateway as a compatibility alias; new SDKs and OpenAPI examples use X-Senticore-Response-Mode.

Nonce model

There are two nonce families.

NonceHeader or fieldOwnerUnitsPersistenceReplay behavior
Machine-auth nonceSC-NonceAPI credential / agent contextdecimal unsigned integerserver replay cache for HMAC/Ed25519 transport authReuse is rejected. Use a process-wide monotonic counter per API key.
Action noncepayload.nonceengine accountdecimal unsigned integerengine/account nonce windowReuse in the active window is rejected; below floor is stale; above window waits for floor advance.
Owner/admin nonceauthorizationNonce, ownerNonce, wallet nonceowner walletstring or u64 by routecontrol-plane storeReuse is rejected.
Idempotency keyIdempotency-Keyclient strategy/requestopaque stringidempotency store, TTL scoped by endpoint/accountSame key plus same body can replay response; same key plus different body returns conflict.

Action nonces use a windowed admission model. When the account is new, the effective floor is 0. A client may use any unused nonce in:

[nonceFloor, nonceFloor + nonceWindow)

Current code uses a 256-wide window in the engine admission path. Rejections include stable fields where the server can extract them:

{
"accepted": false,
"ok": false,
"error": "nonce below replay window: nonceFloor=42 nonceWindow=256 ...",
"code": "nonce_below_floor",
"nextUsableNonce": 42,
"nonceFloor": 42,
"nonceWindow": 256
}

Client strategy:

CodeMeaningClient action
nonce_below_floorThe signed action is permanently stale.Re-sign with nonceFloor or any unused in-window nonce.
nonce_outside_windowThe nonce is too far ahead.Hold the action until the floor advances, or re-sign closer to floor.
nonce_replayedThat nonce was already used in the current replay window.Pick a different unused nonce and re-sign.
nonce_mismatchLegacy strict or reservation conflict.Read bootstrap/account, resync floor, and re-sign.

Do not retry a signed action by only changing Idempotency-Key. The action nonce is inside the signed payload, so a nonce fix requires re-signing.

Funding and risk debugging

Before quoting, require a funded beta credential bundle or a funded institutional test account. An auth-only bundle is not enough: it can pass connectivity, HMAC, FIX Logon, and schema tests while every order fails at risk admission.

Use this preflight:

GET /api/v1/accounts/:engineAccount/bootstrap?fresh=true
Authorization: Bearer <read token>

Minimum fields to log before the first order:

{
"account": "0x2222222222222222222222222222222222222222",
"balanceSource": "matching_engine",
"syncStatus": "synced",
"balances": {
"freeUsdcMicro": "250000000",
"spotUsdcMicro": "250000000",
"lockedUsdcMicro": "0",
"inOrdersUsdcMicro": "0",
"totalUsdcMicro": "250000000"
},
"risk": {
"freeUsdcMicro": "250000000",
"lockedUsdcMicro": "0",
"inOrdersUsdcMicro": "0"
}
}

For USDC checks, balances.freeUsdcMicro is the operational field. Some bootstrap responses also expose balances.spotUsdcMicro; when balanceSource is matching_engine, that view is authoritative for order admission. If free USDC is 0, a bid or quote requiring USDC should be expected to fail.

When risk rejects, log the structured response plus the preflight snapshot. BSL full responses, and detailed early-reject responses where the route can produce them, expose best-effort structured context on the rejected per-action item:

{
"ok": false,
"error": "risk approval rejected request: account would exceed available usdc under spot risk: current_required=0 projected_required=10000 available=0",
"actionResults": [
{
"seq": 0,
"status": "rejected",
"rejectCode": "INSUFFICIENT_BALANCE",
"rejectReason": "risk approval rejected request: account would exceed available usdc under spot risk: current_required=0 projected_required=10000 available=0",
"risk": {
"asset": "USDC",
"free": "0",
"locked": "0",
"required": "10000",
"account": "0x2222222222222222222222222222222222222222",
"market": 7,
"book": null
}
}
]
}

For pre-queue risk rejects, seq can be 0 because the action never entered the sequencer WAL. For older compatibility routes or terminal feeds that do not carry risk, derive the same fields from the signed action plus the fresh bootstrap snapshot:

Debug fieldSource
assetFor bids: USDC; for sell legs: YES, NO, or spot asset.
freeBootstrap balances.freeUsdcMicro or asset free balance.
lockedBootstrap balances.lockedUsdcMicro, inOrdersUsdcMicro, or share/asset locked fields.
requiredFor bids: price * qty / 1_000_000 in micro USDC, plus configured fees/margin if any.
accountEngine account from signed payload.
marketAction market id.
bookYES / NO for outcome markets; omit for spot.
nonceAction nonce from signed payload.
idempotencyKeyRequest retry key.

locked may be null when a compatibility path can infer free and required but did not include the matching-engine hold snapshot. In that case, the fresh bootstrap response remains the authoritative funding snapshot.

Institutional happy path

This is the path to validate before a strategy runs unattended.

StepCommandSuccess signal
1. Create an institutional_agentPOST /api/agents/create with issueInitialCredential: trueResponse contains apiKeyId, apiSecret, apiPassphrase, scopes include read, trade, cancel, quote, drop_copy.
2. Fetch connectivityGET /api/v1/bsl/connectivity with HMACResponse contains BSL/FIX/FIXP endpoint data and actionSigning.chainId + verifyingContract.
3. Resolve account mappingGET /api/v1/trading/accounts?owner=...Persist accountIdHex and engineAccountHex; use engine account for trading.
4. Confirm fundingGET /api/v1/accounts/:engineAccount/bootstrap?fresh=truebalances.freeUsdcMicro covers first quote's required notional.
5. Submit one BSL QuoteReplacePOST /api/order-entry/binary or POST /api/v1/bsl/orders/compactok: true, acceptedActions: 1, derivedOrderIds[0] present, ackMode: ingress_wal or configured boundary.
6. Persist reconciliation keyslocal storeidempotency key, action nonce, clientOrderId, seq, derived order id, market, side/book/price/qty.
7. Read open orders/api/v1/accounts/:engineAccount/orders or drop-copy/private streamDerived order id appears as open or terminal event arrives.
8. Cancel/replacesigned QuoteReplace, FIX G, FIXP CancelReplace, or signed cancelAck accepted and final state reconciled.
9. Read fills and balances/fills, private stream, bootstrap freshStrategy inventory and balance model match server.

Beta credential bundle checklist

For institutional beta, ops should give every integrator a bundle that can place a known-good order. The bundle should contain:

{
"environment": "beta",
"apiBaseUrl": "https://api.sentico-labs.xyz",
"ownerWallet": "0x...",
"engineAccount": "0x...",
"accountIdHex": "0x...",
"agentId": "agt_...",
"apiKeyId": "spk_...",
"apiSecret": "sps_...",
"apiPassphrase": "spp_...",
"scopes": ["read", "trade", "cancel", "quote", "drop_copy"],
"expectedFunding": {
"asset": "USDC",
"freeUsdcMicroAtLeast": "100000000"
},
"knownGoodQuote": {
"route": "/api/order-entry/binary",
"market": 7,
"book": "YES",
"side": "Bid",
"price": 100000,
"qty": 100000,
"timeInForce": "post_only"
}
}

If the account is not funded, stop after auth/conformance tests. Do not treat risk rejections from an unfunded account as an order-entry integration failure.

QuoteReplace semantics

QuoteReplace is one signed parent action with one or more legs. For outcome markets each leg includes book; for SpotQuoteReplace legs do not include book.

Leg fieldMeaning
cancel_order_idExisting order to cancel first; null means place-only for that leg.
bookYES or NO for outcome-market QuoteReplace; not present for SpotQuoteReplace.
sideBid or Ask.
priceMicro price, 100000 = 0.100000.
qtyMicro quantity. 0 means cancel-only; nonzero quantity places a deterministic child order.
time_in_forceUsually post_only for quote refreshes.

The child order id is derived from the signed parent payload plus the leg index. Persist derivedOrderIds[] from the response and verify it against your local derivation in conformance tests.