Sideline
SIDELINE / DEVELOPERS

Build with Sideline

HTTP interfaces, account sessions, order lifecycle and implementation notes for the current release.

Updated September 21, 2026Download Markdown

These pages describe the interfaces used by Sideline’s own web client. Market reads and paper trading are available. Wallet authentication is implemented; public real-money operations remain gated. This is not an API-key portal or a generally available third-party trading API.

Base URL
https://sideline-markets.gericardo695.chatgpt.site

Use relative /api paths from the Sideline origin. Stateful requests use HttpOnly, SameSite=Strict cookies; writes require an exact matching Origin and application/json. Cross-site browser writes are rejected. No Sideline WebSocket, webhook, public SDK or API-key issuance endpoint is provided in this release.

Run this read-only example in a same-origin client module. Choose a current ID from the catalog instead of hard-coding a five-minute round that will expire.

JavaScript
async function readJSON(path) {
  const response = await fetch(path, {
    credentials: 'same-origin', cache: 'no-store'
  });
  const body = await response.json();
  if (!response.ok) throw new Error(body.code ?? body.error ?? 'HTTP_ERROR');
  return body;
}

const catalog = await readJSON('/api/markets?category=crypto&asset=BTC&timeframe=5m');
const now = Date.now();
const market = catalog.markets.find(m =>
  m.tradingStatus === 'OPEN' && m.status !== 'RESOLVED' &&
  (!m.startsAt || m.startsAt <= now) && (!m.endsAt || m.endsAt > now)
);
if (market) {
  const detail = await readJSON('/api/markets/' + market.id + '?period=ROUND');
  console.log({ id: detail.market.id, quote: detail.quote, points: detail.points });
}

Expect quote=null, empty points, null prices and partial=true. Preserve these states in the UI rather than substituting sample prices. API error text may be in a different language; map stable codes to your interface language.

Method / routeResponse and parameters
GET /api/marketsInitial feed: markets, observedAt, serverTime, partial, cryptoNextCursor, cryptoAvailable.
GET /api/markets?category=cryptoCrypto page: markets, nextCursor, observedAt. Optional asset, timeframe and after.
GET /api/markets?category=sportsSports page: markets, nextCursor, observedAt, sport. Optional sport and after.
GET /api/markets/:id?period=ROUNDMarket detail: market, relatedMarkets, quote, points, observedAt, serverTime, partial. period accepts ROUND, 1H or 1D.
Query values
asset: all | BTC | ETH | SOL | XRP | DOGE | BNB | HYPE | NEAR | ZEC | others
timeframe: all | 5m | 15m | hourly | 4h | daily | weekly | monthly | annual | one-time
sport: all | basketball | soccer | baseball | football | tennis | hockey | golf | f1 | cricket

Pass cursors back unchanged with URLSearchParams and the same filters. An empty page can still have a nextCursor. Continue until it is null; deduplicate by market.id. The initial mixed feed uses cryptoNextCursor, while category pages use nextCursor. A market ID is a positive decimal integer of at most ten digits.

ROUND uses the BTC five-minute round’s start when applicable; otherwise it uses the last hour. 1H and 1D request recent history. Neither history sampling nor polling is a tick-by-tick stream.

FieldUnit / meaning
FeedMarket.idNumeric live ID. Paper market IDs use the string live-<id>. Keep the original round ID through settlement.
outcomes[].indexSet1 is YES; 2 is NO. Display outcomes[].name, which can be a team name or Up / Down.
quote.bids / quote.asksYES-side [price, shares] pairs in decimal units. NO bids derive from 1 − YES asks; NO asks from 1 − YES bids. Sort the derived sides correctly.
points[].time / points[].valueUnix milliseconds / probability from 0 to 1.
snapshot.cash / position.cost / position.realizedInteger micro-USD: 1,000,000 equals $1.
position.quantity / order.quantityInteger milli-shares: 1,000 equals one share.
order.price / snapshot.books priceInteger ticks: 10,000 equals $1 per share. Do not confuse these with decimal quote prices.

ORDER request price and quantity are decimal strings, not the integer snapshot units. Convert only at the boundary and use integer arithmetic for accounting. Live-account monetary fields use decimal integer strings; do not cast arbitrary on-chain values to JavaScript Number.

GET /api/paper creates or loads the browser’s paper session. An optional market=<numeric ID> selects a specific real market. The response contains snapshot, catalog, selectedId and partial. snapshot.runId identifies the current reset generation and must accompany every action.

  • The sideline_demo cookie is HttpOnly, SameSite=Strict, Secure over HTTPS, and expires after seven days. Do not read or fabricate it in JavaScript; send credentials: same-origin.
  • The endpoint synchronizes the selected market and up to five active held or open-order markets. It can persist expiry, fills and confirmed payouts. Catalog discovery is not a full refresh of every position.
  • Poll about every eight seconds while visible, avoid overlapping requests, and refresh on return. There is no offline background matcher. GET /api/dashboard is a legacy snapshot endpoint and does not replace /api/paper synchronization.

POST /api/action requires Content-Type: application/json, Idempotency-Key (16–80 letters, digits or hyphens), X-Paper-Run-Id, and the paper session cookie. The browser supplies Origin. The response is { message, replayed, snapshot }; accepted orders may still be open or partial.

JavaScript · same-origin helper
function paperIntent(action, runId) {
  return { key: crypto.randomUUID(), body: JSON.stringify(action), runId };
}
async function submitPaper(intent) {
  const response = await fetch('/api/action', {
    method: 'POST', credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'Idempotency-Key': intent.key,
      'X-Paper-Run-Id': intent.runId
    },
    body: intent.body
  });
  return { status: response.status, result: await response.json() };
}
// Build once per user action; retain this exact intent for a retry.
// marketId comes from a current catalog, runId from /api/paper.
const makeBuy = (marketId, runId) => paperIntent({
  type: 'ORDER', marketId: 'live-' + marketId,
  outcome: 'YES', side: 'BUY', strategy: 'LIMIT',
  quantity: '10', price: '0.40'
}, runId);
ActionJSON fields
PAPER_CREDIT{ "type": "PAPER_CREDIT" }
ORDERtype, marketId: "live-<id>", outcome: YES | NO, side: BUY | SELL, strategy: LIMIT | MARKET, quantity: string, price: string
CANCEL{ "type": "CANCEL", "orderId": "<original order ID>" }
PAPER_RESET{ "type": "PAPER_RESET" }

PAPER_RESET clears the old run and credits $1,000; always adopt the returned runId. MARKET uses price as a protective boundary and cancels its unfilled remainder. LIMIT can rest until filled, canceled or expired. A live-market paper order rejects client MATCH and RESOLVE operations.

Paper prices accept $0.01–$0.99 with at most four decimals; quantities accept at most three decimals and a maximum of 100,000 shares. Minimum notional is $1. Fills charge the current 1% simulation fee. Available balance, share reserves and risk checks can impose lower limits.

Retain the same key, exact serialized body and runId when an action times out or returns an uncertain result. The server fingerprints the raw request body. Even a whitespace change with the same key can cause a conflict. New keys represent new user actions, not automatic retries.

HTTP / codeClient handling
400 / 413 / 415Correct input, request size or JSON content type. Paper request text is limited to 4,096 characters.
401Paper session is missing or expired: load /api/paper again.
403 / ORIGINUse the correct origin; do not bypass browser origin checks.
409 / IDEMPOTENCY_CONFLICTThe key belongs to different request bytes. Inspect the original action.
409 / STALE_PAPER_RUNRefresh the account and discard old-run intents. Do not silently replay them into the new run.
422Business-rule rejection. Interpret code; never assume this status means authentication alone. Live LOGIN_REQUIRED also uses 422.
503 / RECONCILE_REQUIREDRead the account and retry the same saved paper intent if needed. Keep the UI pending until the result is known.
503 / MARKET_DATA_UNAVAILABLEBack off and keep the last quote visibly stale. Do not fill from fabricated data.

Paper actions and synchronization use database revision checks so concurrent requests cannot overwrite one another’s credited balance. A replay returns its recorded receipt. Do not build an unbounded retry loop; inspect state, back off and preserve the pending intent.

  1. Store the market’s exact ID, outcome names, start and end times. Quote refreshes can match resting orders only while the market is open and the observation is fresh.
  2. At endsAt, cancel unfilled remainders and release reserves, even when the quote provider is unavailable. Keep filled positions pending.
  3. Read and verify the conditional-token payout at a finalized BNB Chain block. Verify outcome token identity, condition identity, denominator and numerators; an API status alone is not settlement proof.
  4. Apply the payout ratio once, clear quantity and cost, record realized P&L, and persist paperResults atomically with the balance.
PaperResolution
{
  denominator: string,
  yesNumerator: string,
  noNumerator: string,
  blockNumber: number,
  blockHash: string,
  conditionId: string
}
// micro-USD payout = floor(milliShares * 1000 * numerator / denominator)
// numerator(YES) + numerator(NO) must equal a positive denominator.

These are server-generated proof fields, not client inputs. A payout can be fractional. If proof is missing, leave the position pending. Paper settlement only changes virtual account records and sends no chain transaction.

Maker fills have no trading fee. Taker fees follow the executed share price and quantity: base fee rate × min(price, 1 − price) × filled shares. The documented standard base rate is 2%; the order uses the current market’s feeRateBps (200 basis points = 2%).

An order is not automatically a maker order just because it is a limit order. The quantity matched immediately can incur taker fees; a resting quantity later executed as maker is fee-free. Fees apply to fills, not the canceled or expired unfilled remainder.

Price per shareFee for 100 taker shares at 2%
$0.05$0.10
$0.20$0.40
$0.40$0.80
$0.50$1.00
$0.80$0.40
$0.95$0.10

Sideline charges the full taker fee at the market’s base rate, with no fee discount. Maker fills remain free. Any execution-provider fee rebate is retained by Sideline and does not reduce the customer’s taker fee.

The fee table expresses the collateral-equivalent value. Taker buys credit shares after the full fee; taker sells credit proceeds after the full fee. The displayed fee is already included in these net amounts and is not charged again in cash.

Use lib/live-fees.ts for full-rate integer estimates and reserves. Read feeRateBps from validated market data and sign that unmodified rate; a missing value is unavailable, not zero. For new orders, feePolicy is FULL_TAKER. Verify the maker/taker role from finalized OrderFilled and OrdersMatched events. fullFeeAmount is the customer fee, while feeAmount records the execution provider’s net fee. Retain the difference in retainedTradingFees, in 18-decimal share or USDT units. Credit customers only after the full fee. Orders created before this policy retain their recorded terms.

Reserve bound · collateral equivalent
r = feeRateBps / 10000
BUY fee bound  = shares * r * min(limitPrice, 0.5)
SELL fee bound = shares * r * min(1 - limitPrice, 0.5)
BUY cash budget = shares * limitPrice + BUY fee bound
// Round reserves upward; customer credits deduct the full verified taker fee.
// SELL reserves existing shares, not an additional cash debit.
// Fee bounds use the full rate and cover all prices within the limit.
Method / routeRequest → response
POST /api/live/auth/challenge{ address } → { message, expiresAt }
POST /api/live/auth/verify{ signature } → { address, expiresAt }
GET /api/live/auth/sessionRead the wallet login state.
DELETE /api/live/auth/sessionInvalidate the current wallet session; same-origin required.
GET /api/live/accountRead only the signed-in wallet’s account.

Ask the wallet to sign the exact SIWE message returned by the challenge endpoint. The challenge is single-use and expires after five minutes; a verified session lasts 24 hours. The server verifies domain, URI, chain and signature. The current BSC chain ID is 56. Read the service configuration when integrating rather than changing networks based on an example.

Live state-changing calls require the wallet session. Send X-Wallet-Address with the currently displayed address so an account switch is detected. Keep wallet sessions separate from paper runId and cookies. Never request a private key in the frontend.

Method / routeRequest body / purpose
GET /api/live/statusConfiguration readiness, enabled and acceptance checks; no credentials returned.
POST /api/live/orders{ id, marketId, side, outcome, strategy: "LIMIT" | "MARKET", quantity, price? /* LIMIT */, slippageBps? /* MARKET, 0-500 */ }
POST /api/live/orders/cancel{ id: "<original order ID>" }
POST /api/live/orders/reconcile{ id, after? } — internal client refresh of a user’s existing order; never a new order submission.
POST /api/live/settlement{ after? } — paginated processing of the signed-in user’s eligible positions.
POST /api/live/fundsDEPOSIT, QUOTE, WITHDRAW or RECONCILE. See the funding payloads below.
Funding payloads · reference
DEPOSIT:   { type: 'DEPOSIT', asset: 'USDT', hash: '<deposit transaction hash>' }
QUOTE:     { type: 'QUOTE', asset: 'USDT', amount: '<integer micro-USD string>' }
WITHDRAW:  { type: 'WITHDRAW', id: '<intent ID>', quoteId: '<server quote ID>',
             asset: 'USDT', amount: '<same integer micro-USD string>' }
RECONCILE: { type: 'RECONCILE', id: '<existing withdrawal ID>' }

Live order and withdrawal id values identify a financial intent and must be retained on retries. They are distinct from paper Idempotency-Key headers. A pending operation stays pending while the client refreshes its state automatically; do not ask the user to submit another order to check a fill.

BSC USDT has 18 decimals; balances.USD and cash entries use six-decimal micro-USD. Deposits divide native units by 10^12 and round down; withdrawals multiply micro-USD by 10^12. A 30-second quote binds wallet, asset and both amounts. Only finalized USDT Transfer logs from the pinned token can credit deposits. Native transfers are rejected. BNB balances pay gas and do not increase trading capacity.

The source uses React, TypeScript and Vinext, with a Cloudflare Worker and D1 persistence. Use Node.js 22.13 or later, the checked-in lockfile and the existing migrations. In a source checkout, install and start with the commands below. Windows users can use npm.cmd.

Shell
npm ci
npm run db:local
npm run dev

Market credentials, signing keys and RPC values belong in ignored local environment files or server-side hosting secrets. Use .env.live.example as the configuration inventory in the source checkout. Never expose these values through public environment variables or client bundles. Keep money-movement switches disabled during ordinary development.

ModuleResponsibility
lib/market-data.ts · lib/market-feed.tsRead and normalize market catalogs, books and history.
lib/engine.ts · lib/server-store.tsPaper ledger, matching, receipts and atomic database updates.
lib/paper-market-data.ts · lib/paper-position.tsRead-only real market adapter and position valuation.
lib/live/Wallet auth, real ledger, funding, fills, settlement and activation gates.
lib/docs-user.ts · lib/docs-developer.tsEnglish, Chinese and French documentation; Markdown exports use the same content.
Shell
npm run typecheck
npm run lint
npm test
npm run test:api
npm run docs:generate
npm run build

API tests require local D1 migrations and the development server. Run financial tests with isolated fixtures or virtual accounts. Required invariants include no duplicate credits, no fills from stale quotes, no matching after expiry, original market IDs across BTC rollover, and no lost updates during simultaneous settlement and user actions.

The live acceptance flags remain false until the required funded flow is demonstrated. Changing an environment switch alone is insufficient. Paper acceptance proves virtual accounting with real data; it does not prove deposits, actual execution, redemption or withdrawals with real funds.

The current client’s polling interval is not an API rate-limit guarantee. No public request quota or uptime SLA is published. Avoid parallel polling loops, honor retry guidance and keep upstream or transport errors visible.