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
- Use the engine account for balances, nonces, signed actions, BSL order entry, FIX Account(1), FIXP credentials, private streams, and risk checks.
- Use the trading account id only for control-plane trading-account management routes.
- Use
GET /api/v1/accounts/:engineAccount/bootstrap?fresh=truebefore the first quote and after any risk rejection.balances.freeUsdcMicrois the operational free USDC field for order admission;risk.freeUsdcMicroshould match when the matching-engine view is current. - Use an
institutional_agentHMAC credential for BSL, FIX, FIXP, private reads, and drop-copy. A normalapi_agentis not the institutional order-entry credential. - 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.
- 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.
| Name | Shape | Source | Use for | Do not use for |
|---|---|---|---|---|
| Owner wallet | 20-byte EVM address, 0x + 40 hex chars | Wallet that signs owner/admin authorizations | Agent creation, trading-account owner operations, human custody identity | BSL signed action account when trading from a subaccount |
| Engine account | 20-byte engine AccountId, 0x + 40 hex chars | tradingAccount.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 windows | Trading-account control-plane route ids |
| Trading account id | 32-byte accountIdHex, 0x + 64 hex chars | GET /api/v1/trading/accounts?owner=... | /api/v1/trading/accounts/:account_id, patch/archive/cancel-all/transfer control-plane operations, optional agent authorization binding | Balance/bootstrap/order-entry routes that parse AccountId |
| Agent id | agt_... | POST /api/agents/create | Credential lifecycle, audit events, revocation | Order payload account |
| API key id | spk_... | Agent HMAC/Ed25519 credential creation | SC-Key, FIX Username(553), FIXP negotiate credentials | Signed action account |
| Order id | 32-byte 0x + 64 hex chars | Derived from signed payload or response derivedOrderIds | Cancel, replace, reconciliation | Account 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 lane | Account parameter | Required transport auth | Required action auth | Scope | Notes |
|---|---|---|---|---|---|
GET /api/v1/public/*, public WS | none | none | none | none | Public market data. |
POST /api/agents/create | owner wallet in signed message | none beyond wallet signature | EIP-191 owner wallet authorization | owner | Creates api_agent or institutional_agent; returned HMAC secret material is shown once. |
POST /api/agents/:id/credentials/create | agent owner wallet | wallet admin signature | EIP-191 wallet admin message | owner | Rotate or issue HMAC/Ed25519 credentials. |
GET /api/v1/bsl/connectivity, GET /api/v1/fix/connectivity | credential-bound account | SC-Auth-Version: 2 HMAC from institutional_agent | none | read plus business-line access | Returns BSL/FIX hosts, SNI, CompIDs, action signing chain binding. |
GET /api/v1/accounts/:engineAccount/bootstrap | 20-byte engine account | Bearer private token or SC-Auth-Version: 2 HMAC | none | read | Use 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/balances | 20-byte engine account | Bearer private token or HMAC middleware context | none | read | Returns balance section only. |
GET /api/v1/accounts/:engineAccount/orders, /fills, /balance-events | 20-byte engine account | Bearer private token or HMAC middleware context | none | read | Reconciliation endpoints. |
POST /api/v1/ws/token | 20-byte engine account | Bearer/HMAC or account signature depending request | optional signed token request | read/drop-copy | Issues private WS token (spws1...). |
POST /api/v1/trading/actions | signed payload account | Bearer/HMAC optional by deployment policy | secp256k1 signed ActionPayload | trade | General REST action submit. |
POST /api/order-entry/binary | signed payload account | X-Senticore-Order-Entry-Key, institutional_agent HMAC where deployed, or approved beta auth | secp256k1 signed ActionPayload | quote | Canonical institutional binary HTTP submit. |
POST /api/v1/bsl/orders/compact | signed payload account | institutional_agent HMAC or provisioned lane key where allowed | secp256k1 signed ActionPayload | quote | Canonical BSL compact facade. Use ack + detailed first. |
POST /api/v1/bsl/orders/batch | signed payload account | institutional_agent HMAC or provisioned lane key where allowed | secp256k1 signed ActionPayload | quote | JSON BSL facade for wrapped action batches. |
POST /api/v1/mm/orders/batch.bin | signed payload account | direct trading-plane compatibility auth | secp256k1 signed ActionPayload | quote | Compatibility alias for compact batches. |
| FIX 4.4 | Account(1) = engine account | HMAC credential in 553 and 554 | session-auth lane | trade/cancel/drop_copy | Raw TLS TCP, not HTTPS. Use bundle CompIDs. |
| FIXP/SBE | credentials include engine account | FIXP Negotiate credentials | session-auth lane | trade/cancel/drop_copy | Raw TLS TCP, not HTTPS. |
POST /api/v1/trading/nonce-reservations/:engineAccount | 20-byte engine account | Bearer/HMAC | none | trade | Deprecated for most clients; windowed nonces are preferred. |
/api/v1/institutional/* reports | route or query account | Bearer/HMAC | none | read | Reporting/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:
X-Senticore-Order-Entry-KeyX-MM-Key(legacy)X-API-Key(legacy)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.
| Nonce | Header or field | Owner | Units | Persistence | Replay behavior |
|---|---|---|---|---|---|
| Machine-auth nonce | SC-Nonce | API credential / agent context | decimal unsigned integer | server replay cache for HMAC/Ed25519 transport auth | Reuse is rejected. Use a process-wide monotonic counter per API key. |
| Action nonce | payload.nonce | engine account | decimal unsigned integer | engine/account nonce window | Reuse in the active window is rejected; below floor is stale; above window waits for floor advance. |
| Owner/admin nonce | authorizationNonce, ownerNonce, wallet nonce | owner wallet | string or u64 by route | control-plane store | Reuse is rejected. |
| Idempotency key | Idempotency-Key | client strategy/request | opaque string | idempotency store, TTL scoped by endpoint/account | Same 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:
| Code | Meaning | Client action |
|---|---|---|
nonce_below_floor | The signed action is permanently stale. | Re-sign with nonceFloor or any unused in-window nonce. |
nonce_outside_window | The nonce is too far ahead. | Hold the action until the floor advances, or re-sign closer to floor. |
nonce_replayed | That nonce was already used in the current replay window. | Pick a different unused nonce and re-sign. |
nonce_mismatch | Legacy 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 field | Source |
|---|---|
asset | For bids: USDC; for sell legs: YES, NO, or spot asset. |
free | Bootstrap balances.freeUsdcMicro or asset free balance. |
locked | Bootstrap balances.lockedUsdcMicro, inOrdersUsdcMicro, or share/asset locked fields. |
required | For bids: price * qty / 1_000_000 in micro USDC, plus configured fees/margin if any. |
account | Engine account from signed payload. |
market | Action market id. |
book | YES / NO for outcome markets; omit for spot. |
nonce | Action nonce from signed payload. |
idempotencyKey | Request 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.
| Step | Command | Success signal |
|---|---|---|
1. Create an institutional_agent | POST /api/agents/create with issueInitialCredential: true | Response contains apiKeyId, apiSecret, apiPassphrase, scopes include read, trade, cancel, quote, drop_copy. |
| 2. Fetch connectivity | GET /api/v1/bsl/connectivity with HMAC | Response contains BSL/FIX/FIXP endpoint data and actionSigning.chainId + verifyingContract. |
| 3. Resolve account mapping | GET /api/v1/trading/accounts?owner=... | Persist accountIdHex and engineAccountHex; use engine account for trading. |
| 4. Confirm funding | GET /api/v1/accounts/:engineAccount/bootstrap?fresh=true | balances.freeUsdcMicro covers first quote's required notional. |
| 5. Submit one BSL QuoteReplace | POST /api/order-entry/binary or POST /api/v1/bsl/orders/compact | ok: true, acceptedActions: 1, derivedOrderIds[0] present, ackMode: ingress_wal or configured boundary. |
| 6. Persist reconciliation keys | local store | idempotency 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 stream | Derived order id appears as open or terminal event arrives. |
| 8. Cancel/replace | signed QuoteReplace, FIX G, FIXP CancelReplace, or signed cancel | Ack accepted and final state reconciled. |
| 9. Read fills and balances | /fills, private stream, bootstrap fresh | Strategy 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 field | Meaning |
|---|---|
cancel_order_id | Existing order to cancel first; null means place-only for that leg. |
book | YES or NO for outcome-market QuoteReplace; not present for SpotQuoteReplace. |
side | Bid or Ask. |
price | Micro price, 100000 = 0.100000. |
qty | Micro quantity. 0 means cancel-only; nonzero quantity places a deterministic child order. |
time_in_force | Usually 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.
Related pages
- Place your first institutional BSL quote
- Institutional Conformance Kit
- Sandbox and Funding
- API Reference: Account Bootstrap
- API Reference: Trading Accounts
- API Reference: BSL Compact Submit
- API Reference: Direct Order Entry
- API Reference: BSL Receipts
- Authentication & Request Signing
- API Agents
- BSL Order Entry