Apps & Brokers
Senticore is the venue: matching, settlement, custody proofs. An app is any client surface that brings users to that venue — the first-party frontends as well as a third-party broker with its own UI. Every app has the same identity model and the same rules; the first-party frontends sentico and sentipredict (the Sentirlo broker) are simply the first registered apps.
Everything on this page is live, but apps are created, keyed and activated by the operator. There is no self-service registration, key rotation or app dashboard yet. Send your intended appId, display name and browser origins to [email protected]; see Onboarding.
A broker never holds user funds and needs no backend for trading. Users deposit into the vault under their own wallet, every balance is a leaf in the committed exit tree, and forced withdrawals work without the broker. A broker earns on the flow it brings, not on custody.
What is live
| Capability | Status |
|---|---|
| App registry with origins, server keys and status lifecycle | Live |
| Identity binding on every channel: browser origin, server key, BSL / FIX / FIXP logon | Live |
Signed attribution (appId in every order payload, checked against the channel) | Live |
Surcharge (appFee) paid to the app's payout account on each fill | Live |
| Per-app limits by status (order notional, daily notional, order-to-trade ratio, requests per second) | Live |
| Hard suspension: open orders cancelled, private sessions closed, agents revocable | Live |
| Private wallet reads: a user's positions, balances and history are visible only to that user's own session | Live |
| Audit trail for every operator change to an app | Live |
| Sandbox app registry with its own ids, origins and keys | Ships with the sandbox; see Sandbox apps and Status |
| Self-service registration, key rotation, app-level reporting dashboard | Not available |
| Venue fee share paid to apps | By agreement only, operator-configured |
The app record
| Field | Meaning |
|---|---|
appId | Stable identity, lowercase [a-z0-9_-], max 32 bytes. This is the string users sign as app:<appId> and the value carried in attributed order payloads. Choose it once. |
displayName | What a wallet UI shows next to the signature prompt. Also accepted as the signed app: value. |
status | pending → limited → active → suspended. pending and suspended apps cannot trade; limited trades under the onboarding limits, active under the full limits. |
origins | Canonical browser origins (https://trade.example.com). Requests from these origins are attributed to the app and pass CORS. |
ownerWallet | The wallet that owns the app. Recorded for the audit trail today; becomes the self-service identity later. |
payoutAccount | The Senticore account that receives the app's surcharge. Every appFee.payout in an order must equal this value byte for byte. |
limits | Optional explicit limit profile. When absent, the defaults for the current status apply (see Limits). |
embedAllowed | Reserved for the embeddable trade widget. |
One identity on every channel
| Channel | How the app is recognized | Typical user |
|---|---|---|
| Browser | Request Origin ∈ registered origins | Broker frontend where the user's own wallet signs |
| HTTP server | x-sentico-app-key: <keyId>.<secret> header | Broker backend relaying signed actions, scripts, SDKs |
| FIX | App key in the Logon message, tag 9500 | Institutional order routing |
| FIXP | App key in the Establish credentials field | Binary institutional order routing |
| BSL | App key presented after the HELLO; the core binds the session to a capability | Market makers routing for an app |
| Signed | app:<appId> line inside every wallet-signed control-plane message | Agent authorization, session keys, account management, launchpad submit |
The channels must agree. A request whose origin belongs to app A but whose key belongs to app B is rejected with APP_CHANNEL_MISMATCH. A message a user signed for app A cannot be relayed through app B. The signed domain: must be one of the app's registered origin hosts (or a parent domain of one). A request that presents no app at all is simply unattributed; a request that presents an invalid or revoked key is rejected with APP_KEY_INVALID rather than falling back to anonymous. Session-key registration (POST /api/v1/bsl/session-keys/register) follows the same rule: the request Origin and the signed app: line must belong to the same app (a mismatch answers 401), and the wallet-signed request must not carry machine-auth HMAC headers (403 otherwise).
Attributed orders
Order payloads accept an optional appId:
{
"account": "0x…",
"nonce": 17,
"appId": "brokerx",
"ts": 1780000000000,
"action": { "SpotPlaceOrder": { "marketId": 7, "side": "Bid", "price": 998400, "qty": 1000 } }
}
appId is part of the signed canonical bytes: the user attests which app they traded through, and neither the relaying app nor the venue can rewrite it. It must name an active app and the same app the request arrived through, on every lane; otherwise the order is rejected with APP_ATTRIBUTION_REJECTED before it is sequenced. Payloads without appId are accepted and unattributed — every existing signing hash, order id and replay record is unchanged.
An app-attributed spot market order (isMarket: true) is rejected with APP_LIMIT_EXCEEDED, because it has no notional ceiling for the app's limit check. Send a price-bounded limit order with timeInForce: "ioc" instead; the first-party frontends already do this. Binary markets are unaffected because their price is bounded by 1 USDC.
Surcharge (appFee)
An app may add its own fee on top of the venue fee. The surcharge is declared per order, signed by the user, and credited to the app's payout account inside the fill — no statement, no delay.
"appId": "brokerx",
"appFee": { "feePpm": 5000, "payout": "0x27798dbec1cc0f7e3531c51bab3bdb7315c5b32c" }
| Product | Cap | How it is charged |
|---|---|---|
| Spot | 5,000 ppm (50 bps) of notional | flat |
| Prediction | 20,000 ppm rate (2 %) | rate × p × (1 − p); at p = 0.5 a 5,000 ppm rate is 12.5 bps of notional |
| Launchpad markets | 0 | no app surcharge; the deployer's fee applies |
Rules: appFee requires appId; payout must equal the app's registered payoutAccount; a value above the cap is charged at the cap. The venue fee reported on the trade record excludes the surcharge, so tier and programme calculations never include it. The first-party frontends read appId and appFee from their runtime config; a broker frontend does the same.
On prediction markets the surcharge credit currently appears in the payout account's balance but not yet as a separate app_fee line in the balance-events history. Spot fills already produce the line. A backend correction is scheduled.
Limits
Limits are enforced at admission on every lane and keyed by the app, not the user. Defaults follow the app's status; the operator can set an explicit profile.
| Status | Per order | Per UTC day | Orders per trade | Requests per second |
|---|---|---|---|---|
limited | 1,000 USDC | 10,000 USDC | 100 | 10 |
active | 1,000,000 USDC | 100,000,000 USDC | 10,000 | 10,000 |
pending apps are registered but may not trade until the operator moves them to limited. The daily budget counts admitted notional, including orders later cancelled. A breach is rejected with APP_LIMIT_EXCEEDED and the detail names the limit.
Suspension
Setting an app to suspended is a hard stop, applied in one transaction with the audit entry:
- New requests from its origins or keys are rejected with
APP_NOT_ACTIVE; signedapp:lines for it fail. - All open orders of accounts trading through the app are cancelled by the sequencer, and scheduled algo/conditional orders are removed.
- Private WebSocket sessions of the app are closed; the operator may revoke every agent the app created.
- Status changes away from
suspendedare blocked until the cancellations have completed. Reactivation restores nothing.
Privacy
Since September 2026 every wallet-scoped read — positions, balances, orders, fills, balance events, fee standing, statements — requires the read credentials of that wallet's own session and answers 401/403 otherwise. An app therefore sees exactly what each of its users sees in their session, and nothing about other apps or other users. Its own aggregated numbers come from the app reads under Reporting, which pseudonymise traders.
Revenue and reporting
- Surcharge: credited per fill to
payoutAccount. Read it with that account's session via/api/v1/accounts/{account}/balance-events(spot) or the balance itself (prediction, see the known gap above). - Venue fee share: not automatic. Where an agreement exists, the operator configures it and the share is paid through the fee programme statements, readable with the payout wallet's session at
/api/v1/fees/statements. - Volume: attributed volume is recorded per order in the venue's order history and reported through the app reads below.
Reporting
Three private reads give an app its own numbers. They accept either the owner wallet's read credential (the same bearer or HMAC v2 session as /api/v1/accounts/{account}/*) or the app's own key channel (x-sentico-app-key, or a FIX/FIXP/BSL logon) resolved to the same appId; anything else answers 401, a foreign credential 403 FORBIDDEN, an unknown app 404 APP_NOT_FOUND. Responses carry Cache-Control: private, no-store and take no wallet selector.
GET /api/v1/apps/{appId}— the app record: display name, status, origins, owner and payout wallets, limit profile and key metadata. Key secrets and their hashes are never returned.GET /api/v1/apps/{appId}/overview?range=24h|7d|30d|all— surcharge credits and broker fee-programme share (earned, paid, pending), attributed orders and fills with volume, active and new traders, a per-market breakdown, and the limit profile with today's admitted notional.rangedefaults to7d;rejectsis reported as unavailable in v1.GET /api/v1/apps/{appId}/fills?range=&limit=&cursor=— attributed taker fills newest first (limitup to 500, opaquecursor), each with a stable per-app trader pseudonym instead of the account. SendAccept: text/csvfor the same rows as CSV with a header line; the continuation cursor is then in theX-Next-Cursorresponse header.
Errors
| Code | HTTP | Meaning |
|---|---|---|
APP_KEY_INVALID | 401 | The x-sentico-app-key (or logon key) is unknown or revoked |
APP_NOT_ACTIVE | 403 | The app exists but is pending or suspended |
APP_CHANNEL_MISMATCH | 403 | Origin and key identify different apps |
APP_ATTRIBUTION_REJECTED | 403 | Payload appId/appFee does not match the channel's app, or appFee.payout is not the registered payout account |
APP_LIMIT_EXCEEDED | reject | Order notional, daily budget, order-to-trade ratio, request budget, or an unbounded spot market order |
Sandbox apps
The sandbox (launching — see Status) has its own app registry. A sandbox
app is how you exercise the whole broker path — origin binding, app:<appId>
signatures, attributed orders, appFee, per-app limits, APP_CHANNEL_MISMATCH
— before anything you build touches real collateral.
| Sandbox | Production | |
|---|---|---|
| Registry | Separate registry on the sandbox deployment | Live registry |
| Origins | Your sandbox origins, registered against a sandbox appId | Your production origins |
| Server key | Issued for the sandbox only | Issued for production only |
Signed chainId | 421614 | 42161 |
| Payout account | A sandbox account holding mock USDC | Your real payout account |
| Limits | The active profile, so throughput is representative | Profile by app status |
Ask for a sandbox app the same way you ask for a production one (see Onboarding) and say that it is for the sandbox. Registration is not coupled: you can hold a sandbox app while your production application is still under review.
A sandbox appId, its server key and every signature made under it exist only in
the sandbox. The registries are separate rows in separate databases and the
signed chainId differs, so a sandbox key presented to production is rejected as
an invalid key and a sandbox-signed message is rejected on its chain binding. The
reverse holds too. Keep the two configurations apart in your deployment; do not
try to reuse one identity across environments.
Surcharges (appFee) in the sandbox are paid in mock USDC to a sandbox payout
account. They exercise the accounting path; they are not revenue.
Onboarding
- Send
appId, display name, browser origins and, if you want a surcharge, the payout account to [email protected]. Say whether you want the app in the sandbox, in production, or both. - The operator registers the app as
pending, issues a server key if you run a backend or route through FIX/FIXP/BSL, shares the key once, and moves the app tolimited. - Point your frontend at the venue: users sign
app:<appId>with your domain, orders carryappId(andappFee), your backend sends the key. - Verify under the
limitedprofile: a signed order lands in the venue's order history with yourappId; a wrong payout is rejected. - The operator sets the app
active; from then on theactivelimits apply. No venue restart is involved at any step.
Rotate a server key by asking the operator for a new one and revoking the old one. Origin changes are operator changes too.
First-party clients
sentico and sentipredict — the latter being the Sentirlo broker, which kept its original appId after the rename — are registered apps with their own origins and sign their own appId. Clients deployed before the registry signed the fixed name SentiPredict; that name stays accepted as an alias during the migration window and is attributed to the app the request arrived through. New integrations must sign their own appId.