Skip to main content

Sandbox

The Senticore sandbox is a separate, fully centralized, off-chain deployment of the venue with an API surface identical to production. Same routes, same request and response shapes, same signing construction, same error model, same rate limits, same five lanes on the same ports. You switch environments by changing one base URL.

Nothing in the sandbox touches a blockchain. There is no vault, no deposit path, no withdrawal path and no settlement checkpoint. Collateral is mock USDC booked straight into the account ledger by the venue, the way a testnet exchange hands out fake balances. It is not a token, it has no value, and it cannot leave the sandbox.

Not reachable yet

The sandbox is not deployed yet: the hosts on this page do not resolve until it launches. Everything below is the committed configuration it launches with, so you can build against it now. Status announces the launch.

Environments side by side

SurfaceProductionSandbox
REST + WebSocket + SSEapi.sentico-labs.xyz:443api.sentico-sandbox.xyz:443
Web appapp.sentico-labs.xyzapp.sentico-sandbox.xyz
FIX 4.4 (tag=value)fix.sentico-labs.xyz:9878fix.sentico-sandbox.xyz:9878
Binary-FIX / FIXP (SBE)fixp.sentico-labs.xyz:9879fixp.sentico-sandbox.xyz:9879
BSL Direct TCPbsl.sentico-labs.xyz:9001bsl.sentico-sandbox.xyz:9001
Binary market-data gatewayEntitlement-issued per desk; not a publicly listed hostmd.sentico-sandbox.xyz:9002 and :9003
Signing chain id42161 (Arbitrum One)421614 (Arbitrum Sepolia, signing domain only)
SettlementOn-chain, Arbitrum OneNone. Off-chain ledger only

The ports are the same in both environments. Only the hostname and the chain binding differ, so a client that works in one works in the other with a single configuration value changed.

Derived surfaces follow the same substitution:

https://api.sentico-sandbox.xyz/api/v1/...
wss://api.sentico-sandbox.xyz/api/v1/ws/public
wss://api.sentico-sandbox.xyz/api/v1/ws/private/{account}
wss://api.sentico-sandbox.xyz/api/v1/feed/public (plus /a and /b)
https://api.sentico-sandbox.xyz/api/stream/market
https://api.sentico-sandbox.xyz/api/stream/account/{account}
Read the endpoints, do not copy them

GET /api/v1/bsl/connectivity on the sandbox host returns the sandbox lane hosts, ports, SNIs and the action-signing chainId and verifyingContract. Build your configuration from that response in both environments. A client that hardcodes either environment's values is one redeploy away from being wrong. See Connectivity Bundle.

The one-value switch

Point every URL at api.sentico-sandbox.xyz instead of api.sentico-labs.xyz and read the chain binding from the connectivity bundle. With the SDK:

import { SenticoreClient } from "@sentico-labs/sdk";

// Explicit form - works with every SDK version.
const client = new SenticoreClient({
publicHttpBaseUrl: "https://api.sentico-sandbox.xyz",
tradingHttpBaseUrl: "https://api.sentico-sandbox.xyz",
orderEntryHttpBaseUrl: "https://api.sentico-sandbox.xyz",
publicWsUrl: "wss://api.sentico-sandbox.xyz/api/v1/ws/public",
privateWsUrl: "wss://api.sentico-sandbox.xyz/api/v1/ws/private/{account}",
machineAuth: { apiKeyId, apiSecret, apiPassphrase }
});

The SDK release that accompanies the sandbox adds an environment preset, so the same client becomes one value:

const client = new SenticoreClient({
environment: "sandbox",
machineAuth: { apiKeyId, apiSecret, apiPassphrase }
});

Explicit per-field URLs continue to override the preset. The Python and Rust SDKs expose the same switch as SenticoreConfig.for_environment("sandbox") and SenticoreConfig::sandbox(). See SDKs for the version that carries it.

Signing domain: chain id 421614

The sandbox signs with chainId 421614 (Arbitrum Sepolia) and its own non-zero, synthetic verifyingContract. Both are returned in the actionSigning block of the connectivity bundle.

Nothing is deployed on Arbitrum Sepolia

421614 is used purely as an EIP-155 signing domain. Senticore does not operate, deploy, fund or contact any contract on Arbitrum Sepolia. Do not send transactions there, do not look for a vault there, and do not expect a sandbox balance to exist anywhere on chain. The sandbox verifyingContract is a stable synthetic 20-byte constant, not a deployed address; it exists because it is part of every signature preimage and must differ from production's.

Why a different chain id at all: the server compares the chainId inside every signed wallet-admin, agent-authorization and session-delegation message against the deployment's own chain id, and the BSL session-key and EIP-712 session delegation hashes bind chainId and verifyingContract into their preimage. A distinct chain id is therefore the mechanism that makes a sandbox signature structurally unusable against production, and a production signature unusable against the sandbox. Reusing 42161 would make every signature you produce in testing replayable against the live venue.

The environment: line inside a typed v2 signed message is a label, not a guard. The server does not validate it. Sign environment: "sandbox" in the sandbox because it keeps your own tooling honest and it is what a wallet shows the user, but do not build a threat model on it. The enforced separators are the chain id, the verifying contract and the credential rows.

Credentials are separate and fail closed

The sandbox has its own control-plane database, its own app registry, its own credential secrets and its own admin plane. Nothing is shared with production.

  • A sandbox account, API key, HMAC credential, session key or app key does not exist in production, and vice versa. Presenting one to the other environment fails closed at authentication.
  • A signature produced under sandbox chain id 421614 is rejected by production, and a signature produced under 42161 is rejected by the sandbox.
  • Sign-up is self-service against the sandbox web app with a wallet signature. There is no whitelist, no KYC and no sales call for sandbox access.

What is not different is the signing construction itself. The message grammar, the HMAC prehash, the FIX Logon RawData, the BSL AuthSidecar layout and the action-payload hash are byte-identical in both environments. That is the point of the sandbox: a client that authenticates here authenticates in production.

Published contracts

The three commitments below are numbers, not intentions. They are what the sandbox does; if one changes, this page changes with it.

Funding contract

Mock USDC is granted by a sandbox-only faucet.

PropertyValue
AssetMock USDC only. It is the settlement asset of every sandbox book
Grant size100,000 USDC per call, fixed. You do not choose the amount
First callMaterialises the account and funds it. No prior deposit, no operator step
Equity gateA grant is permitted only while total account equity is below 25,000 USDC
Quota5 grants per account per rolling 24 hours
IdempotencyPer requestId. A replayed requestId returns the identical response and credits nothing further
Quota introspectionEvery response carries grantsRemaining, grantsTotal and resetAtMs, so a client self-throttles instead of discovering the cap as an error
Balance gate timingChecked on the signed grant request, never on the public quota view
Other assetsBase assets (WETH and any sandbox spot base) are not self-service. Acquire them by trading

Request a grant:

POST /sandbox/v1/faucet
Content-Type: application/json
{
"account": "0xEngineAccount",
"requestId": "ci-run-1043-topup-2",
"amountUsdcMicro": "100000000000",
"issuedAt": 1760000000000,
"expiresAt": 1760000300000,
"signature": "0x..."
}

The wallet signs this text, in this order, with personal_sign — a header line, then key:value lines joined by newlines with no trailing newline, the same grammar as the typed wallet-admin v2 message:

SentiCore Sandbox Faucet v1
type:senticore.sandbox_faucet
version:1
domain:api.sentico-sandbox.xyz
environment:sandbox
chainId:421614
wallet:0xengineaccount
asset:USDC
amountUsdcMicro:100000000000
requestId:ci-run-1043-topup-2
issuedAt:1760000000000
expiresAt:1760000300000

wallet is the same address as the account field of the request body, lowercased: the faucet only ever credits the account that signed for it.

domain, environment, chainId, asset and amountUsdcMicro are server-side constants: the faucet rebuilds the message from its own configuration and never splices a client-supplied value into it. A signature produced for another chain id, another host or a larger amount does not recover to the claimed wallet and is rejected. chainId is the enforced separator — a sandbox faucet signature cannot be replayed anywhere else.

Never transcribe this grammar by hand. GET /sandbox/v1/faucet/quota returns the exact template for this deployment, and the faucet binary prints it with sandbox-faucet print-message.

Check the quota without spending one:

GET /sandbox/v1/faucet/quota?account=0xEngineAccount

The quota endpoint needs no signature, so it tells you only about the account's faucet quota - grantsRemaining, grantsTotal, resetAtMs, quotaAvailable - and never about its balance: account balances on this venue are private, and a public faucet endpoint must not become a way around that. quotaAvailable: true therefore does not promise that a grant succeeds. The 25,000 USDC balance gate is checked when your signed grant arrives (balanceGateEvaluatedAt: "grant").

Typed errors name the reason and the recovery: an exhausted quota returns the reset time, and a signed grant made while equity is at or above 25,000 USDC returns FAUCET_BALANCE_GATE with your current equity, rather than a generic rejection. Verify the credit the same way you would verify a production deposit:

GET /api/v1/accounts/{engineAccount}/bootstrap?fresh=true

The faucet is a sandbox-only sidecar. It is deliberately not part of the canonical OpenAPI contract, because the route does not exist in production and never will.

Liquidity contract

The venue runs its own market maker in the sandbox. Without it you could test order acknowledgements and nothing else: no fills, no partials, no fee accrual, no attribution, no settlement, no private-stream fill frames, no tape, no candles.

PropertyCommitment
Instruments quotedEvery listed sandbox book, two-sided, continuously. Not a single flagship pair
DepthAt least 10 price levels per side on every listed book
Aggressor flowA second venue wallet crosses the house quotes on a schedule, so every book prints trades, forms candles and populates lastTradeMicro and volume24hMicro
Price anchorMajors track an external reference price; the stable pair is quoted around a fixed anchor
RefreshQuotes are refreshed continuously. All house resting orders are cancelled and re-seeded nightly at 00:00 UTC
UptimeBest effort. See the no-SLA statement below
Fill guaranteeNone. See the warning below

These are the numbers the house quoter is configured with. They are a deliberately wide synthetic spread, not a mark: on the majors the quoted top-of-book spread is about two orders of magnitude wider than a real ETH/USDC or BTC/USDC book, and on the stable pair about fifty times wider.

BookQuoted spreadLevels per sideSize per levelLevel spacing
SPOT:ETH/USDC100 bps (2 x 50 bps)120.25 ETH25 bps
SPOT:BTC/USDC100 bps (2 x 50 bps)120.01 BTC25 bps
SPOT:USDC/USDT50 bps (2 x 25 bps)121,000 USDC5 bps
Binary YES and NO400 bps (2 x 200 bps)12500 shares50 bps
Prediction YES and NO400 bps (2 x 200 bps)12500 shares50 bps

The spread column is a FLOOR: the quoter's minimum half-spread is pinned to its target, so inventory skew only ever widens the quote, never tightens it.

The quoter re-evaluates and re-quotes every 2 seconds. The majors are anchored to an external reference mid; the stable pair is anchored to a fixed 1.0000. The aggressor crosses the touch of every book on a rolling schedule, alternating buy and sell, in slices of 0.05 ETH, 0.002 BTC, 200 USDC and 100 shares, so each book prints at least one trade every few minutes.

The sandbox spread is not a mark

Sandbox marks, spreads, depth and volume are not indicative of production liquidity and must not be used to calibrate a strategy, size a position, price a product or benchmark execution quality. The numbers above are what the venue configures, not a commitment: read the spread and the per-level size from the book rather than assuming them, because the house quoter skews with inventory, can be re-seeding, and can be out of inventory on one side of one book.

The house market maker is a counterparty of convenience, not a fill guarantee. It can be down, it can be re-seeding, it can be out of inventory on one side of one book, and it is subject to the same self-trade prevention and risk rules as every other participant. Build your client to handle an unfilled marketable order, because that is a real production condition too.

Instrument catalog

The sandbox lists a deliberately small catalog, and lists it explicitly rather than as "a subset":

Instrument typeCountBooks
Spot3ETH/USDC, BTC/USDC, USDC/USDT
Binary option1One permanent binary book
Prediction1One permanent prediction book

Market ids and symbols match production for the same symbol, so integrator code is portable across environments; the separation is carried by the host, the chain id and the credentials, not by renumbering markets. Additional short-dated test markets are created at runtime through the normal market lifecycle and come and go; enumerate markets with GET /api/v1/public/markets rather than hardcoding a list.

Persistence and reset contract

Published verbatim:

Sandbox balances, accounts, API keys and session keys persist indefinitely. The house market maker's resting orders are cancelled and re-seeded nightly at 00:00 UTC. Accounts with no authenticated request for 90 days are deleted, along with their orders, balances and credentials. The sandbox has no SLA: it may go offline at any time without notice, and maintenance is not announced in advance.

There is no scheduled global wipe and no periodic balance reset. If you run out of mock USDC, call the faucet; that is the recovery path. If your account has been idle for 90 days, sign in again and call the faucet, exactly as on day one.

The nightly sweep covers the house market maker's own orders. There is currently no mechanism to cancel a third party's resting orders, so a fat-finger order left on a sandbox book by another integrator can persist. Treat any extreme resting level you did not place as junk, not as a price.

Rate limits

The sandbox enforces production's limit profiles exactly - the same per-IP and per-account HTTP budgets, the same market-maker and per-lane hotpath limits, and the same behaviour on exhaustion, including session termination on the binary lanes. A looser sandbox would hide precisely the backoff bug the sandbox exists to catch. See Rate Limits.

What differs from production

The complete list. If something is not on it, it behaves as production does.

Liquidity and volume. Books are quoted by a venue-operated market maker with a deliberately wide spread. Depth, spread, volume, candles and last price are synthetic and are not indicative of production.

Instrument availability. 3 spot books, 1 binary book, 1 prediction book, plus short-dated test markets. Production lists more.

Synthetic funds. Balances are mock USDC booked into the ledger by the venue. They are not a token, have no value, cannot be deposited, cannot be withdrawn and do not exist on any chain.

Funding and withdrawals. There is no vault, no deposit address, no WithdrawRequest, no WithdrawAssetRequest and no proof-backed exit. Those actions are rejected in the sandbox on every lane. The faucet replaces them.

Signing domain and identity. Chain id 421614 and a synthetic verifyingContract, both read from the connectivity bundle. Separate control plane, separate app registry, separate credentials; sandbox credentials fail closed in production and production credentials fail closed in the sandbox.

Topology. A separate deployment on a separate host from production. It shares no database, no secret, no certificate and no operator credential with the live venue. It carries no SLA and is not covered by production's operational processes.

Not in the sandbox today

These are known gaps, stated plainly rather than left silent:

  • No balance reduction. The faucet only adds. There is no way to debit an account, so insufficient-balance, margin-call and rejection paths cannot be reached by lowering a balance. Reach them by placing an order larger than your equity instead.
  • No per-account reset. You cannot wipe your own sandbox account back to a clean state. The faucet plus the 90-day inactivity deletion is the recovery path.
  • No forced error injection. There is no request header that makes the venue produce a specific reject on demand. Reject paths must be provoked with real conditions.
  • No numbered conformance script. There is no published case-by-case certification sheet with pass/fail evidence fields, and passing in the sandbox is not today a formal precondition for production credentials. The Institutional Conformance Kit is base-URL parameterised and runs against the sandbox in the meantime.
  • No second, static, unauthenticated mock tier. There is exactly one sandbox and it is a real matching engine. Anything that returns canned responses is not it.

If a sandbox endpoint is ever retired

A commitment: a retired sandbox endpoint will return 410 Gone with a typed JSON error naming its replacement, the documentation and the SDKs will be updated in the same release, and the change will appear in the changelog. A sandbox host will not be silently redirected to a marketing page while the docs continue to show working examples against it.