Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Outcome Integration Guide

Every Outcome endpoint has its own reference page. This guide connects them in the order an integration actually calls them, and flags the three places where the Outcome flow differs from perps.

The short version of those three differences:

  • Discovery metadata comes from a gateway aggregation in snake_case, while the on-chain instrument definition is camelCase. They describe the same market and are not interchangeable.
  • Placing an order is asynchronous. A successful HTTP response means queued, not matched, and the terminal result arrives on a different WebSocket endpoint than the matching result.
  • A position query returns only positions. Account balances live behind a separate call.

Examples use the TypeScript SDK; every step maps to one POST /info, POST /exchange, or WebSocket subscription if you are integrating directly.

The flow at a glance

Fund the Outcome sub-account, place the order, then follow two channels on two endpoints.

A successful order response can mean queued, not matched. Subscribe to both channels in step 4, or a rejected queue entry looks like an order that never filled.

  1. POST /info
    outcomeMeta

    Find the event and market.

    instrument_id — every later step needs it

  2. POST /exchangeAccount-signed
    tokenSend

    Move USDC from perp to outcome. Account-signed; an agent cannot move funds.

  3. POST /exchangeAgent-signed
    order

    a is the instrument_id; b/r encode the leg (buy NO = b: false, r: false) and p is that leg's price. TIF Gtc, Gtd, or Alo — Ioc is rejected.

    Possibly an async acceptance with a traceId

  4. Do all of these
    WebSocket
    outcomeOrderUpdate

    On /api-ws. Pushed only if the queued order fails; match on traceId.

    WebSocket
    outcomeOrderUpdates

    On /ws. What the book did: filled, cancelled, rejected.

  5. POST /info
    outcomePositions

    Positions only — no margin summary. Use clearinghouseState with accountType=outcome for that.

The sections below take each step in turn, with code.

1. Discover markets

outcomeMeta is the discovery feed: events with nested markets, live trading gates, pricing, volume, and settlement state.

const page = await sdk.info.outcomeMeta({
  exchangeId: 1,
  limit: 20,
  sortBy: 'volume',
})

Two response shapes come out of this one query. An eventSlug point lookup returns the event object directly; everything else returns a paged envelope. Narrow before reading events:

import { isOutcomeMetaPage } from 'txflow-typescript-sdk'
 
const events = isOutcomeMetaPage(page) ? page.events : [page]
const market = events[0]?.markets[0]

Page size caps at 50; walk with next_cursor for more. sortBy: 'endingSoon' is an ascending semantic order that carries its own direction, so send it without sortOrder.

Resolving a deep link works the other way round — look the event up by the market slug in your URL:

const result = await sdk.info.outcomeMeta({
  exchangeId: 1,
  marketSlugs: ['btc-up-or-down-5m'],
  markets: 'matched',
})

markets: 'matched' returns only the requested markets; 'event' returns every market in each owning event.

market.instrument_id is the identifier every later step needs.

2. Stream quotes

For a grid or watchlist, outcomeBbos batches best bid and ask across many markets on one subscription:

await sdk.subscriptions.outcomeBbos(
  { instrumentIds: [market.instrument_id], exchangeId: 1 },
  ({ bbos }) => {
    for (const { instrumentId, bbo } of bbos) {
      const [bid, ask] = bbo
      console.log(instrumentId, bid, ask)
    }
  },
)

One subscription carries 1–50 instrument ids and one connection accepts up to 20 active groups. Beyond that, open more subscriptions on the same connection. Either side of bbo is null when the book is empty there.

For a single market's full depth, the shared l2Book channel applies. Outcome markets are addressed by instrument id as a decimal string, not the OUTCOME-<id> name:

await sdk.subscriptions.l2Book(
  { coin: String(market.instrument_id) },
  ({ levels }) => {
    const [bids, asks] = levels
    console.log(bids[0]?.px, asks[0]?.px)
  },
)

The same string rule applies to trades and activeAssetCtx. To follow several markets' tapes at once, use outcomeTrades, which takes numeric instrumentIds instead.

For charts, outcomeProbability returns the historical YES probability series. To keep the chart live, subscribe to candle on /api-ws with the instrument id as coin and append its closes, which are YES probabilities.

3. Fund the sub-account

Outcome trades settle in a dedicated sub-account. Move USDC into it with an owner-signed tokenSend:

await sdk.exchange.tokenSend({
  token: 0,
  amount: '100',
  fromAccountType: 'perp',
  toAccountType: 'outcome',
})

4. Place an order, and learn what happened

Orders use the shared order action with the instrument id as asset:

const ack = await sdk.exchange.placeLimitOrder({
  asset: market.instrument_id,
  side: 'buy',
  px: '0.55',
  sz: '10',
})
ChannelEndpointAnswers
outcomeOrderUpdate (singular)/api-wsDid the queued order fail before reaching the node? Pushed only on failure.
outcomeOrderUpdates (plural)/wsWhat did the book do — filled, cancelled, rejected?
// Delay-queue result, correlated by traceId.
await sdk.apiSubscriptions.outcomeOrderUpdate({ user }, (result) => {
  if (result.done && !result.success) {
    console.error('never reached the node', result.traceId, result.body?.message)
  }
})
 
// Matching-engine lifecycle.
await sdk.subscriptions.outcomeOrderUpdates({ user }, (updates) => {
  for (const { order, status } of updates) {
    console.log(order.oid, order.outcome, status)
  }
})

Watching only the plural channel makes a rejected queue entry look like an order that silently never filled. The singular channel stays silent when the order goes through, so it never tells you whether it traded.

order.outcome is the displayed leg (Yes or No); order.side (B or A) and reduceOnly are the wire b and r (see Outcome orders).

Cancelling uses the shared cancel action.

5. Read positions and balances

This is the second place the Outcome flow surprises people: outcomePositions returns only the assetPositions array. There is no margin summary and no withdrawable.

const [positions, account] = await Promise.all([
  sdk.info.outcomePositions({ user }),
  sdk.info.clearinghouseState({ user, accountType: 'outcome', exchangeId: 1 }),
])
NeedCall
Open positionsoutcomePositions
Account value, margin, and withdrawable balanceclearinghouseState with accountType: 'outcome'
Active ordersoutcomeFrontendOpenOrders

The YES and NO legs of one market are two separate records; the sign of position.szi distinguishes them.

One asymmetry to keep in mind: outcomePositions accepts an optional exchangeId, outcomeFrontendOpenOrders does not.

History endpoints need an explicit opt-in. Add 'outcome' to accountType on userFills and historicalOrders, or to accountTypes on userNonFundingLedgerUpdates — note the plural field name and the singular 'perp' spelling on that last one.

6. Split and merge complete sets

outcomeOperate converts between collateral and a complete share set, without touching the book:

// Lock collateral, receive symmetric YES and NO positions.
await sdk.exchange.outcomeOperate({
  split: [{ asset: market.instrument_id, sz: '10' }],
})
 
// Return a symmetric pair, release the collateral.
await sdk.exchange.outcomeOperate({
  merge: [{ asset: market.instrument_id, sz: '10' }],
})

Exactly one of split or merge is present per action. This is useful for market making one leg while hedging the other, and for exiting when one side has no bid.

7. Settlement

Settlement state travels on the market object from step 1:

const settlement = market.settlement
 
if (settlement?.resolution_status === 'resolved') {
  console.log('winner:', settlement.winning_outcome)
}

resolution_status is prepared or resolved. winning_outcome is derived from outcomes and settlement_fraction after resolution — it is the protocol's result, not the metadata provider's oracle_outcome. Poll outcomeMeta for the market slug to refresh it.

Recurring series

Crypto up/down markets repeat on a fixed cadence. outcomeSeriesTimeline returns the past, current, and upcoming windows around an anchor event so you can build a period navigator, and cryptoPrice / cryptoPriceHistory supply the price-to-beat and the chart series.

Unlike outcomeMeta, the timeline response is camelCase.

What is out of scope

  • Creating markets. outcomeDeploy is permissioned — see Exchange API.
  • Risk-notice acknowledgement. The first-party app records this through application-session endpoints that are not part of this API surface, as noted in TIP 3. Present disclosures appropriate to your own product and jurisdiction.

Next steps