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.
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
| Surface | Production | Sandbox |
|---|---|---|
| REST + WebSocket + SSE | api.sentico-labs.xyz:443 | api.sentico-sandbox.xyz:443 |
| Web app | app.sentico-labs.xyz | app.sentico-sandbox.xyz |
| FIX 4.4 (tag=value) | fix.sentico-labs.xyz:9878 | fix.sentico-sandbox.xyz:9878 |
| Binary-FIX / FIXP (SBE) | fixp.sentico-labs.xyz:9879 | fixp.sentico-sandbox.xyz:9879 |
| BSL Direct TCP | bsl.sentico-labs.xyz:9001 | bsl.sentico-sandbox.xyz:9001 |
| Binary market-data gateway | Entitlement-issued per desk; not a publicly listed host | md.sentico-sandbox.xyz:9002 and :9003 |
| Signing chain id | 42161 (Arbitrum One) | 421614 (Arbitrum Sepolia, signing domain only) |
| Settlement | On-chain, Arbitrum One | None. 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}
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.
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.
| Property | Value |
|---|---|
| Asset | Mock USDC only. It is the settlement asset of every sandbox book |
| Grant size | 100,000 USDC per call, fixed. You do not choose the amount |
| First call | Materialises the account and funds it. No prior deposit, no operator step |
| Equity gate | A grant is permitted only while total account equity is below 25,000 USDC |
| Quota | 5 grants per account per rolling 24 hours |
| Idempotency | Per requestId. A replayed requestId returns the identical response and credits nothing further |
| Quota introspection | Every response carries grantsRemaining, grantsTotal and resetAtMs, so a client self-throttles instead of discovering the cap as an error |
| Balance gate timing | Checked on the signed grant request, never on the public quota view |
| Other assets | Base 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.
| Property | Commitment |
|---|---|
| Instruments quoted | Every listed sandbox book, two-sided, continuously. Not a single flagship pair |
| Depth | At least 10 price levels per side on every listed book |
| Aggressor flow | A second venue wallet crosses the house quotes on a schedule, so every book prints trades, forms candles and populates lastTradeMicro and volume24hMicro |
| Price anchor | Majors track an external reference price; the stable pair is quoted around a fixed anchor |
| Refresh | Quotes are refreshed continuously. All house resting orders are cancelled and re-seeded nightly at 00:00 UTC |
| Uptime | Best effort. See the no-SLA statement below |
| Fill guarantee | None. 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.
| Book | Quoted spread | Levels per side | Size per level | Level spacing |
|---|---|---|---|---|
SPOT:ETH/USDC | 100 bps (2 x 50 bps) | 12 | 0.25 ETH | 25 bps |
SPOT:BTC/USDC | 100 bps (2 x 50 bps) | 12 | 0.01 BTC | 25 bps |
SPOT:USDC/USDT | 50 bps (2 x 25 bps) | 12 | 1,000 USDC | 5 bps |
Binary YES and NO | 400 bps (2 x 200 bps) | 12 | 500 shares | 50 bps |
Prediction YES and NO | 400 bps (2 x 200 bps) | 12 | 500 shares | 50 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.
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 type | Count | Books |
|---|---|---|
| Spot | 3 | ETH/USDC, BTC/USDC, USDC/USDT |
| Binary option | 1 | One permanent binary book |
| Prediction | 1 | One 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.