Trading API
The Order Management System’s REST + WebSocket surface: orders, positions, margin, account management, withdrawals, settlements.
Market Data API
Public klines, depth, trades, and tickers over REST and WebSocket.
Programmatic access
The/api-reference pages are client-rendered Scalar apps: the returned HTML is a shell that loads the spec via JavaScript; fetching it without a browser yields no endpoints. Fetch the machine-readable OpenAPI document instead:
- Trading API:
https://oms.tplus.cx/api-doc.json - Market Data API:
https://mds.tplus.cx/api-doc.json
GET https://oms.tplus.cx/markets, not /api/markets.
Quickstart
The Python SDKtpluspy handles the auth handshake, Ed25519 signing, and per-field amount encoding.
success: false, and a positive ack is still not final confirmation; see Finality.
After authenticating, the safest first state-read call is POST /account/simulate/{user_id}. It is non-mutating: OMS validates the request, reads inventory, books, registry, prices, protocol values, and fee state, then returns a projected margin result without forwarding a state change to the orderbook or clearing engine.
Authentication
User IDs are hex-encoded Ed25519 public keys. Session auth is a three-step flow:GET /nonce/{user_id}- returns a random nonce bound to the user, valid 5 minutes.- Sign the nonce’s
valueas raw UTF-8 bytes with the Ed25519 private key. POST /auth- exchange the signed nonce for a bearer token, valid 24 hours.
POST /auth consumes the nonce, so the same signed nonce cannot be exchanged again. A second GET /nonce/{user_id} returns the existing valid nonce when it has more than 10 seconds of viability left; otherwise OMS generates a replacement.
Send Authorization: Bearer <token> and User-Id: <hex public key> on REST requests. On WebSocket, you may send those same headers or use Sec-WebSocket-Protocol. For v1 envelopes in browser clients, pass protocols in this order so the server echoes the version subprotocol: ["tplus.ws.v1", "Bearer-<token>", "User-<id>"]. The auth parser accepts Bearer- and User- in any comma-separated position, and tplus.ws.v1 opts the stream into the v1 envelope.
There is no refresh endpoint today. To refresh a session, request a nonce and authenticate again. POST /logout revokes all bearer tokens for the authenticated user; after revocation, requests that reuse those tokens fail auth before reaching the handler. Tokens are keyed by user public key and token value, and token validation checks only that stored token and its expiry; multisig config changes do not revoke existing tokens in the current OMS code path.
REST auth failures use the normal error envelope. Missing or invalid bearer credentials on protected routes return HTTP 401 with code UNAUTHORIZED; invalid signatures return 401 with code INVALID_SIGNATURE; nonce failures return 400 with NONCE_NOT_FOUND, NONCE_EXPIRED, or INVALID_NONCE; auth rate limits return 429 with RATE_LIMITED. WebSocket upgrade auth failures pass through the same REST rejection path before upgrade. After upgrade, user/path mismatches send a channel error (unauthorized on subscription streams; UNAUTHORIZED for the control-channel invalid-auth payload) and return from that stream without defining an auth-specific close code. V1 lag/resync uses close code 1013, which is not an auth signal.
There is no public master-key rotation flow today, so post-rotation bearer-token semantics do not apply.
Request signatures
Independent of session auth, signed actions such as orders, withdrawals, settlements, and transfers carry an Ed25519 signature from the account’s master key. The OMS checks that the authenticated user matches the signer; the clearing engine re-verifies clearing-engine actions before finalizing. Replacing an order requires a new signature. The signed payload is the action-specific signable object serialized to compact JSON: whitespace stripped, fields in struct-declaration order, signed as raw UTF-8 bytes, not a hash. For example, withdrawal, settlement, transfer, and multisig-add flows sign theirinner payload, while order create/cancel/replace use the order action’s own signable object. The exact byte layout (including how optional fields are serialized) must match or verification fails; use tpluspy unless you are reproducing it deliberately.
Optional multisig registers weighted co-signers (secp256k1 / P-256 / WebAuthn) with per-action-category thresholds; manage them via the /multisig/* endpoints. See Multisig.
Accounts
Balances and positions live in sub-accounts (spot, cross-margin, or isolated; see account types). Orders target a sub-account index plus a spot-vs-margin flag. GET endpoints return data across all of the caller’s sub-accounts and accept an optional sub-account filter.Identifying assets and markets
Markets and assets are identified byasset_id, not by a ticker symbol. An asset_id is either a protocol index ("18") or, for isolated chain-specific assets, "<token address>@<chain id>". The reserved index 0 is USD: the wire value is "0"/Index(0), and client symbol resolution should display it as USD.
GET /markets- tradeable markets keyed byasset_id(price/quantity decimals, leverage, tick and min size, fees).GET /registry/assets- returns the cached CE asset config map for index assets. It returns503until the first CE snapshot; the JSON is nested by asset index and chain-address key, and each row includes the token address and deposit caps.
GET /registry/assets (the chain-address key encodes the chain id; the row address is the token address), or use the address@chain already inside an isolated asset_id. Then read the token contract’s symbol/name onchain, for example ERC-20 symbol() / name() on EVM chains. An index asset absent from /registry/assets has no onchain token to read and cannot be mapped to a symbol from the API alone; do not infer one.
Identifiers by API surface
Amounts
Internally, all balances are normalized to 18 decimals. On the wire, amount encoding depends on the field. Check each field’s type, or usetpluspy, which encodes for you:
- Settlement and withdrawal request amounts (
amount,amount_in,amount_out) are hexadecimal integer strings with no0x.1000000is"f4240"; the decimal-looking"1000000"is read as0x1000000=16,777,216. (The OpenAPI examples render decimal-looking strings, but they are parsed as hex.) - Transfer and simulate amounts are decimal strings (e.g.
"1000000000000000000").
Conventions
- Pagination: zero-based
pagepluslimit. Defaults and caps vary per endpoint; see the endpoint reference. User orders are sorted newest first bytimestamp_ns, thenorder_iddescending; user trades are sorted newest first bytimestamp_ns, thentrade_iddescending; positions are sorted bysub_account_index, thenasset_id; funding history is sorted bytimestamp_nsdescending with no secondary tie-breaker defined today. - Rate limits return HTTP
429with codeRATE_LIMITEDandretryable: true. OMS currently does not attachRetry-Afteror rate-limit reset headers. - Client deadlines: use more than 5 seconds for orderbook/clearing-engine proxy writes such as orders, withdrawals, and settlements; use more than 15 seconds for
GET /withdrawal/queue/{user_id}. For pure OMS reads, pick a shorter deadline that fits your latency budget and retry policy. - Per-asset risk parameters:
GET /registry/risk-parameters; vault addresses:GET /registry/vaults. - Three independent nonce families, never interchangeable: session-auth - the
valuefromGET /nonce/{user_id}, signed and exchanged atPOST /auth; chain actions (deposits, withdrawals, settlements) - independent per user, per chain, and per action type, read from the onchain vault counters; multisig config -config_nonce, returned asce_noncebyGET /nonce/{user_id}and bumped on each successful/multisig/*change. - Order-level rules (ID format, batch limits, cancel semantics) are in Orders.
- Deprecation/versioning:
GET /margin/user/{user_id}currently returnsDeprecation: true. Tplus does not currently publish a formal API versioning or deprecation policy.
Errors
REST errors share one envelope:code is a stable SCREAMING_SNAKE_CASE string, message is human-readable, retryable says whether the same request might succeed later, and details is present on some codes. Common codes: INVALID_REQUEST, UNAUTHORIZED, INSUFFICIENT_MARGIN, INSUFFICIENT_INVENTORY, REDUCE_ONLY_WOULD_INCREASE, WOULD_CROSS, ORDER_EXPIRED, ORDER_NOT_FOUND, MARKET_NOT_FOUND, ALREADY_CANCELLED, REQUEST_TOO_OLD; the full set is in the spec. A malformed body is rejected with 400 before auth runs; rate-limited requests return 429.
Finality
Positive acknowledgments are optimistic: accepted for processing does not mean settled. Check endpoint response bodies first because some CE-proxy endpoints can return HTTP200 with success: false. An accepted order or matched fill stays pending until the clearing engine confirms it, and may still roll back. The WebSocket streams carry pending, confirmed, and rolled-back states with rollback reasons (the at-fault party sees its own reason; counterparties see CounterpartyAtFault). REST trade history returns confirmed trades only.
SDKs
Python:tpluspy on PyPI. Async REST/WS clients, typed models, Ed25519 signing; optional [evm] extra for onchain workflows.