# Markets and categories (/concepts/markets) Everything you trade on Predict is a **market**: one Yes/No question with its own order book. Markets are grouped into **categories**. ## Categories and markets [#categories-and-markets] A category is one event or question, for example a football match, an election or "Will BTC be up at 6 PM?". It holds one or more markets. - **One market:** the category is a single Yes/No question. - **Several markets:** each market is one possible outcome of the event, asked as its own Yes/No question. In a tournament winner category, every team has a market such as "Will Arsenal win?". When exactly one of a category's markets can resolve Yes, the category is negative-risk (`isNegRisk: true`). Those markets are linked on-chain, which lets you convert positions between them. See [Negative-risk markets](/concepts/negative-risk). | Field | On | What it tells you | | :-- | :-- | :-- | | `id`, `slug` | category | How you look the category up. | | `markets` | category | The category's markets. | | `marketVariant` | category, market | The kind of event, such as `DEFAULT`, `SPORTS_MATCH` or `CRYPTO_UP_DOWN`. It decides how the event is shown and what `variantDetails` holds. | | `isNegRisk` | category, market | Whether the markets are linked as a [negative-risk](/concepts/negative-risk) set. It decides which exchange and approvals you need. | | `isYieldBearing` | category, market | Whether the collateral earns yield while it is locked. It decides which set of contracts the market uses. See [Positions and outcome tokens](/concepts/positions). | | `decimalPrecision` | market | How many decimals a price can have. See [Prices](#prices-and-precision). | | `feeRateBps` | market | The market's taker fee. See [Fees](/trading/fees). | | `resolutionProvider` | category | Who decides the outcome. See [Resolution](/concepts/resolution). | | `tags` | category | The topics the category is filed under. | Use [Get categories](/api/categories/get-categories) and [Get category by slug](/api/categories/get-category-by-slug) to browse events, and [Get markets](/api/markets/get-markets) or [Get market by ID](/api/markets/get-market-by-id) to read a single market. ### Series [#series] Recurring up/down events, such as Bitcoin up or down every 5 minutes, belong to a series. Each round of a series is an ordinary category with its own markets, and its `seriesSlug` names the series it belongs to. [Get series](/api/series/get-series) lists every series, active or retired, and [Get series by slug](/api/series/get-series-by-slug) reads one, for example `btc-updown-5m`. [Get series rounds](/api/series/get-series-rounds) lists a series' rounds. By default it returns the rounds that haven't ended, in start order, so the first is the live round and the rest are the rounds it rolls over to. Rounds are created ahead of time, so one page covers several rollovers. With `status=ENDED`, it returns past rounds, newest first. ### Tags [#tags] Tags form a tree up to three levels deep, for example Sports, then Soccer, then EPL. Each tag has a `level` (1, 2 or 3) and the `parentId` of the tag above it. Use [Get all tags](/api/categories/get-all-tags) to read the tree, and the `tagIds` parameter of [Get categories](/api/categories/get-categories) to filter by it. ### Translations [#translations] Categories, markets and outcomes can be translated. [Get available languages](/api/translations/get-available-languages) lists the language codes in use, such as `zh-CN`. [Get category translations](/api/categories/get-category-translations) returns the translations of a category, its markets and their outcomes, for the codes you pass in `languages`, for example `zh-CN,ja-JP`. A language with no completed translations is left out of the response. In the WebSocket API, [Set language](/websocket/requests/set-language) picks the language of the outcome names in your wallet events. ## Outcomes [#outcomes] Each market has two outcomes, Yes and No. You find them in the market's `outcomes` array. | Field | Meaning | | :-- | :-- | | `name` | The outcome's label, usually "Yes" or "No". Some markets use other labels, such as "Up" and "Down". | | `indexSet` | `1` for the first outcome (Yes, or Up) and `2` for the second (No, or Down). | | `onChainId` | The ERC-1155 token ID of the outcome's shares. This is the `tokenId` you sign in an order. | | `status` | `WON` or `LOST` once the market resolves, otherwise `null`. | Use `indexSet` rather than `name` to tell the outcomes apart. The labels change from market to market, but `indexSet` is always `1` for the first outcome and `2` for the second. Prices and the order book are quoted for the outcome whose `indexSet` is `1`. ## Prices and precision [#prices-and-precision] A price is in USDT per share and runs from 0 to 1. A winning share pays 1 USDT, so a price of 0.62 means the market puts a 62% chance on that outcome. `decimalPrecision` is `2` or `3`. With `2`, prices move in steps of 0.01 (62¢). With `3`, they move in steps of 0.001 (62.5¢). An order whose price has more decimals than the market allows is rejected. See [Place orders](/trading/place-orders). ## Market status [#market-status] `status` follows the market from creation to resolution. | `status` | Meaning | New orders | | :-- | :-- | :-- | | `REGISTERED` | The market is live. | Accepted | | `PRICE_PROPOSED` | An outcome was proposed and resolution is under way. | Accepted | | `PRICE_DISPUTED` | The proposed outcome was disputed. | Accepted | | `PAUSED` | The market is paused. | Rejected with `create_order_market_not_trading` | | `UNPAUSED` | The market was paused and is live again. | Accepted | | `RESOLVED` | The outcome is final. | Rejected | | `REMOVED` | The market was withdrawn. | Rejected | The `status=OPEN` filter of [Get markets](/api/markets/get-markets) returns the markets that accept orders: `REGISTERED`, `PRICE_PROPOSED`, `PRICE_DISPUTED` and `UNPAUSED`. ## Trading status [#trading-status] `tradingStatus` says what you can do with orders right now. It can change while `status` stays the same, so check both before you quote. | `tradingStatus` | What you can do | | :-- | :-- | | `OPEN` | Place, match and remove orders. | | `MATCHING_NOT_ENABLED` | Matching is switched off. Only post-only limit orders are accepted, and they rest on the book without matching. Market orders fail with `create_order_market_orders_disabled`, and other limit orders with `create_order_trading_disabled`. When matching is switched back on, any orders that cross are matched. | | `CANCEL_ONLY` | The market is hidden. New orders are rejected, but you can still remove your open orders. | | `CLOSED` | The market is resolved or removed. | The [trading status](/websocket/topics/trading-status) topic of the WebSocket API pushes each `tradingStatus` change. The [market status](/websocket/topics/market-status) topic sends only `REGISTERED`, `PRICE_DISPUTED` and `RESOLVED`, so read the other statuses from [Get market by ID](/api/markets/get-market-by-id). ## Pagination [#pagination] List endpoints, such as [Get categories](/api/categories/get-categories) and [Get markets](/api/markets/get-markets), return one page at a time. 1. Send `first` to set the page size. For categories and markets it defaults to 20, and it can be at most 100. 2. Read `cursor` from the response. 3. Send it back as `after` to get the next page. When there is no next page, `cursor` is `null`. # Negative-risk markets (/concepts/negative-risk) Some categories ask which of several outcomes happens, for example who wins a tournament. Each candidate gets its own Yes/No market, and only one of them can resolve Yes. When a category's markets are linked this way, it is a **negative-risk** category, and its markets have `isNegRisk: true`. ## Why the markets are linked [#why-the-markets-are-linked] Because only one candidate can win, a No share on one candidate is worth the same as a Yes share on every other candidate. The `NegRiskAdapter` contract links the markets of the category so that you can swap between the two. That swap is called **converting** positions. Each market of the category is one question of the adapter's market. Its `questionIndex` is its position in the category, starting from 0. ## Converting positions [#converting-positions] `convertPositions(negRiskOnChainId, indexSet, amount)` on the `NegRiskAdapter` converts positions in the category whose `negRiskOnChainId` you pass. It takes `amount` No shares of each question you pick, and gives you: - `amount` Yes shares of every question you didn't pick, and - `(n − 1) × amount` USDT, where `n` is the number of questions you picked. `indexSet` is a bitmask of the questions you pick: bit `i` stands for the question whose `questionIndex` is `i`. The market can charge a convert fee, which is taken from what you receive. [Converting complementary positions](https://docs.predict.fun/knowledge-base/converting-complementary-positions-no-yes) in the product docs shows how to convert in the web app. ### Example [#example] A category asks who wins, with three candidates A, B and C. You hold 1 No share on A and 1 No share on B, so `indexSet` is `0b011` (3) and `n` is 2. Converting gives you 1 Yes share on C and 1 USDT. Both holdings pay the same whoever wins: | Winner | No on A + No on B pay | Yes on C + 1 USDT pay | | :-- | :-- | :-- | | A | 0 + 1 = 1 | 0 + 1 = 1 | | B | 1 + 0 = 1 | 0 + 1 = 1 | | C | 1 + 1 = 2 | 1 + 1 = 2 | So converting doesn't change your payout. It frees the USDT that your No shares had locked up across the category. Only categories with more than one market can convert. See [Manage positions](/trading/manage-positions) for the SDK call. ## What changes for trading [#what-changes-for-trading] Negative-risk markets trade on their own exchange, the `NegRiskCtfExchange`. The SDK picks it when you pass `isNegRisk` to `buildTypedData` (`build_typed_data` in Python), and the order's signature is only valid on that exchange. They also need one more approval than binary markets. Besides approving the exchange for your USDT and shares, you approve the `NegRiskAdapter` for your shares, because the adapter moves them when your sell orders fill. Without it, the API rejects your sell orders with `create_order_neg_risk_adapter_not_approved`. See [Approvals](/trading/approvals). Read each outcome's token ID from its `onChainId`. Don't compute it from the condition ID with USDT as the collateral: the adapter derives negative-risk token IDs from its own wrapped collateral token, so the result would be wrong. ## Resolution [#resolution] Each question of a negative-risk category resolves on its own, but at most one of them can resolve Yes. A question can't resolve 50/50. Resolving a question takes two steps. First the oracle reports the result. Then Predict records it on the adapter, about an hour later, and only after that can you redeem. See [Resolution](/concepts/resolution). # Prices and order book (/concepts/order-book) Each market has one order book. Buyers and sellers of both outcomes trade in it, and Predict matches their orders off-chain and settles the matches on-chain. [Understanding the order book](https://docs.predict.fun/the-basics/understanding-the-order-book) in the product docs explains the basics, such as limit and market orders and the spread. This page covers how the API sends the book. **The order book is priced in the first outcome, Yes** (the outcome whose `indexSet` is `1`). A No order appears in it at its Yes-equivalent price. The sections below show how to get the No side. ## Reading the book [#reading-the-book] The [Get market orderbook](/api/markets/get-market-orderbook) endpoint returns the current book: ```json { "success": true, "data": { "marketId": 1, "updateTimestampMs": 1727910141000, "asks": [ [0.492, 30192.26], [0.493, 20003] ], "bids": [ [0.491, 303518.1], [0.49, 1365.44] ] } } ``` Where: | Field | Meaning | | :-- | :-- | | `asks` | Offers to sell Yes, as `[price, shares]`. The best (lowest) ask comes first. | | `bids` | Offers to buy Yes, as `[price, shares]`. The best (highest) bid comes first. | | `updateTimestampMs` | When the book last changed, in epoch milliseconds. | | `lastOrderSettled` | The last order whose match settled on-chain: its `id`, `price`, `kind` (`LIMIT` or `MARKET`), `side` and `outcome`. | Prices are in USDT per share, from 0 to 1, and read as probabilities: a Yes price of 0.62 means a 62% chance. A price never has more decimals than the market's [`decimalPrecision`](/concepts/markets#prices-and-precision). ### Only the best prices [#only-the-best-prices] When you only need the current price, use [Get market quote](/api/markets/get-market-quote) instead of the whole book. It returns the best bid and the best ask, each with its `price` and `size`, priced in Yes like the book. To follow many markets, [Get quotes for multiple markets](/api/markets/get-quotes-for-multiple-markets) returns the quotes of up to 500 markets in one request, and [Get orderbooks for multiple markets](/api/markets/get-orderbooks-for-multiple-markets) returns the whole books of up to 100. Pass the market IDs as `ids`, for example `?ids=123&ids=456`. To stream the best prices, subscribe to the [top of book topic](/websocket/topics/top-of-book) of the WebSocket API, `predictTopOfBook/{marketId}`. It sends the best bid and the best ask each time either changes. The topic sends each as a `[price, shares]` pair, like the order book, while the quote endpoints send an object with `price` and `size`. ## Getting the No side [#getting-the-no-side] A Yes order and a No order at complementary prices are the same trade seen from the two sides. Buying No at 0.38 takes the same liquidity as selling Yes at 0.62. So the No book is the Yes book with its sides swapped and each price replaced by its complement: - **No bids** come from the Yes asks. The best No bid, the most you can sell No for right now, is `1 − best Yes ask`. - **No asks** come from the Yes bids. The best No ask, the least you can buy No for right now, is `1 − best Yes bid`. - The share amounts stay the same. Compute the complement at the market's `decimalPrecision`, so that the Yes and No prices add up to exactly 1. A plain `1 - price` can give values such as `0.37999999999999995`, which don't match the prices the backend uses. ```typescript type Level = [price: number, shares: number]; // The No price of a Yes price, at the market's decimal precision. function getComplement(price: number, decimalPrecision = 2): number { const factor = 10 ** decimalPrecision; return (factor - Math.round(price * factor)) / factor; } // The No book from the Yes book the API sends. function toNoBook(asks: Level[], bids: Level[], decimalPrecision: number) { const flip = ([price, shares]: Level): Level => [ getComplement(price, decimalPrecision), shares, ]; return { asks: bids.map(flip), bids: asks.map(flip) }; } ``` ```python def get_complement(price: float, decimal_precision: int = 2) -> float: """The No price of a Yes price, at the market's decimal precision.""" factor = 10**decimal_precision return (factor - round(price * factor)) / factor def to_no_book(asks, bids, decimal_precision): """The No book from the Yes book the API sends.""" def flip(level): price, shares = level return [get_complement(price, decimal_precision), shares] return {"asks": [flip(b) for b in bids], "bids": [flip(a) for a in asks]} ``` With the book above and a precision of 3, the best No bid is `getComplement(0.492, 3) = 0.508` and the best No ask is `getComplement(0.491, 3) = 0.509`. If one side of the Yes book is empty, the opposite side of the No book is empty too. Check for that before you read `asks[0]` or `bids[0]`. ## Live updates [#live-updates] For a live book, subscribe to the [order book topic](/websocket/topics/order-book) of the WebSocket API, `predictOrderbook/{marketId}`. The latest book usually arrives right after you subscribe, then a new copy every time it changes. The WebSocket copy has a few more fields than the REST response: | Field | Meaning | | :-- | :-- | | `orderCount` | The number of orders on the book. | | `settlementsPending` | Matches that are waiting to settle on-chain, as `[price, amount]` levels in `asks` and `bids`. The price is on the Yes axis, and the amount is the USDT value of the shares being settled at that price. | | `lastOrderSettled` | As in the REST response. | Liquidity in `settlementsPending` is already matched. It can come back to the book if the settlement fails. See [Order lifecycle](/trading/order-lifecycle). # Positions and outcome tokens (/concepts/positions) A position is the shares you hold of one outcome of a market. Shares are ERC-1155 tokens of a conditional tokens contract, and each outcome has its own token ID: the outcome's `onChainId`. ## Collateral and shares [#collateral-and-shares] Markets settle in USDT on BNB Chain. USDT there has 18 decimals, and so do shares: an amount of `1000000000000000000` (1018) is 1 USDT or 1 share. The REST API returns share amounts in these units, for example `amount` in [Get positions](/api/positions/get-positions). **1 USDT always backs one Yes share and one No share.** That one fact explains every operation below. | Operation | You give | You get | When | | :-- | :-- | :-- | :-- | | Split | 1 USDT | 1 Yes share and 1 No share | Any time | | Merge | 1 Yes share and 1 No share | 1 USDT | Any time | | Trade | USDT or shares | Shares or USDT, from another trader | While the market trades | | Redeem | Your shares | Their payout in USDT | After resolution | Most traders only trade and redeem. When you buy Yes and another trader buys No, the exchange can split your combined USDT into the two shares. When you sell Yes and another trader sells No, it can merge the two shares back into USDT and pay you both. You don't need to split or merge yourself to trade. [Splitting and merging shares](https://docs.predict.fun/knowledge-base/splitting-merging-shares) in the product docs shows both operations in the web app. ## Payouts [#payouts] When a market resolves, each share pays out: | Result | Each winning share | Each losing share | | :-- | :-- | :-- | | Yes wins | 1 USDT per Yes share | 0 per No share | | No wins | 1 USDT per No share | 0 per Yes share | | 50/50 | 0.5 USDT per Yes share | 0.5 USDT per No share | A 50/50 result happens when a question can't be decided either way. A [negative-risk](/concepts/negative-risk) question can't resolve 50/50. It resolves Yes or No, and at most one question of its category resolves Yes. For example, if you buy 100 Yes shares at 0.40, you pay 40 USDT. If Yes wins, you redeem them for 100 USDT, a profit of 60 USDT. If No wins, they pay nothing and you lose the 40 USDT. Redeeming has no deadline. See [Resolution](/concepts/resolution) for when it becomes possible. ## Which contract holds your shares [#which-contract-holds-your-shares] Where your shares live depends on two market fields, `isNegRisk` and `isYieldBearing`. The [Deployed contracts](/resources/deployed-contracts) page lists each contract's address. | Market | Shares are held by | Split, merge and redeem go through | | :-- | :-- | :-- | | Binary | `ConditionalTokens` | `ConditionalTokens` | | Binary, yield-bearing | `YieldBearingConditionalTokens` | `YieldBearingConditionalTokens` | | Negative-risk | `NegRisk ConditionalTokens` | `NegRiskAdapter` | | Negative-risk, yield-bearing | `NegRisk ConditionalTokens` | `YieldBearingNegRiskAdapter` | The SDKs pick the right contract for you. See [Manage positions](/trading/manage-positions) for the code. ### Binary markets [#binary-markets] You call the conditional tokens contract directly, with the market's `conditionId`: - `splitPosition(collateralToken, parentCollectionId, conditionId, partition, amount)` - `mergePositions(collateralToken, parentCollectionId, conditionId, partition, amount)` - `redeemPositions(collateralToken, parentCollectionId, conditionId, indexSets)` For a Predict market, `collateralToken` is USDT, `parentCollectionId` is `0x0`, and `partition` and `indexSets` are `[1, 2]`: Yes and No. ### Negative-risk markets [#negative-risk-markets] You call the negative-risk adapter, which handles the linked questions for you: - `splitPosition(conditionId, amount)` - `mergePositions(conditionId, amount)` - `redeemPositions(conditionId, amounts)`, where `amounts` is `[yesAmount, noAmount]` - `convertPositions(negRiskOnChainId, indexSet, amount)`, which only negative-risk markets have. `negRiskOnChainId` is the category's field of that name. See [Negative-risk markets](/concepts/negative-risk). ## Yield-bearing markets [#yield-bearing-markets] In a yield-bearing market (`isYieldBearing: true`), the USDT backing the shares is deposited into the Venus lending protocol while it is locked. Your shares work the same way as in any other market: each one still splits, merges and redeems 1:1 against USDT. [Yields](https://docs.predict.fun/the-basics/yields) in the product docs explains how the yield is paid out. ## Your positions [#your-positions] [Get positions](/api/positions/get-positions) returns the positions of the wallet you are logged in with, and [Get positions by address](/api/positions/get-positions-by-address) those of any wallet. Each position has: | Field | Meaning | | :-- | :-- | | `market`, `outcome` | The market and the outcome you hold. | | `amount` | Your shares, in units of 10-18. | | `valueUsd` | What the shares are worth at the current price. | | `averageBuyPriceUsd` | The average price you paid per share. | | `pnlUsd` | Your profit or loss: the current value minus what you paid. | # Resolution (/concepts/resolution) A market resolves when its outcome is final. The result is reported on-chain as a payout for each outcome: 1 for the winner and 0 for the loser, or 0.5 each for a 50/50 result. After that, you can redeem your shares for USDT. See [Positions and outcome tokens](/concepts/positions#payouts). Each category's `description` holds the rules its market resolves by, including the source of truth and how ties are handled. Read them before you trade. ## Who resolves a market [#who-resolves-a-market] A category's `resolutionProvider` says who decides its outcome. | `resolutionProvider` | How the outcome is decided | | :-- | :-- | | `PREDICT_DOT_FUN` | Most of these markets follow a question on UMA's Optimistic Oracle, on Polygon. The outcome is proposed there, and it can be challenged and disputed. Once UMA's answer is final, it is proposed to Predict's oracle on BNB Chain, the `UmaCompatibleOptimisticOracle`. When that proposal is final, the `UmaCompatibleCtfAdapter` reports the payouts. | | `THREE_PO` | A third-party resolver signs the outcome. The `ThreePoAdapter` checks the signature against its list of approved signers, then proposes and resolves the result in one transaction. | | `CHAINLINK` | Up/down markets resolved by the `ChainlinkUpDownAdapter` from Chainlink price reports. The market resolves Up if the price at the end of the round is higher than at the start, Down if it is lower, and 50/50 if it is the same. [Chainlink price markets](https://docs.predict.fun/developers/chainlink-price-markets) in the product docs explains the rounds. | Predict's oracle on BNB Chain accepts proposals only from approved addresses. It takes no bonds and has no dispute step of its own. For a market that follows UMA, the challenge period and any disputes happen on UMA, on Polygon. [How resolution works on Predict](https://docs.predict.fun/knowledge-base/how-resolution-works-on-predict) in the product docs explains UMA's proposals, challenge period and disputes. Predict can also resolve a market directly in an emergency. ## Status while a market resolves [#status-while-a-market-resolves] A market's `status` shows where resolution stands: 1. `REGISTERED`: the market is live and nothing has been proposed yet. 2. `PRICE_PROPOSED`: an outcome was proposed. Orders are still accepted. 3. `RESOLVED`: the payouts are on-chain. The market stops trading, and its `resolution` field and each outcome's `status` (`WON` or `LOST`) show the result. `PRICE_DISPUTED`, `PAUSED` and `UNPAUSED` are also part of the status list. See [Markets and categories](/concepts/markets#market-status) for what each one allows. The [market status topic](/websocket/topics/market-status) of the WebSocket API tells you when a market resolves. It doesn't send `PRICE_PROPOSED`, so read that status from [Get market by ID](/api/markets/get-market-by-id). For a market's full history, [Get market status logs](/api/markets/get-market-status-logs) returns each on-chain status change, oldest first, with its time and transaction hash. One request covers up to 100 markets. ## Negative-risk markets [#negative-risk-markets] A question of a [negative-risk](/concepts/negative-risk) category resolves in two steps: 1. The oracle reports the result to the `NegRiskOperator`. 2. About an hour later, Predict resolves the question on the operator, which reports it to the `NegRiskAdapter` and records the payouts. You can redeem after the second step. At most one question of a category can resolve Yes, and no question can resolve 50/50. ## When you can redeem [#when-you-can-redeem] You can redeem a market's shares once its payouts are recorded on its conditional tokens contract. That is when the market's `status` becomes `RESOLVED`. Redeeming has no deadline. - A winning share pays 1 USDT. - A losing share pays nothing. - After a 50/50 result, each share pays 0.5 USDT. See [Manage positions](/trading/manage-positions) to redeem with the SDK. # FAQs (/faqs) Join our [Discord server](https://discord.gg/predictdotfun) to report any issue. ## Where do I get an API key? [#where-do-i-get-an-api-key] In the [developer console](https://developers.predict.fun). Mainnet needs a key for every request. Testnet works without one. ## Which networks are supported? [#which-networks-are-supported] BNB Mainnet (`https://api.predict.fun`) and BNB Testnet (`https://api-testnet.predict.fun`). See [Environments](/introduction#environments) for the WebSocket URL and chain IDs. ## Where are your servers? [#where-are-your-servers] In AWS `ap-northeast-1` (Tokyo). It is also the closest region that is not geo-restricted. ## What are the rate limits? [#what-are-the-rate-limits] Keys from the developer console allow 500 requests per minute on trading routes and 250 per minute on most other routes. Order placement and removal also have a per-second cap. On testnet without a key, the limits are 500 and 240 per minute for each IP address. See [Rate limits](/resources/rate-limits) for every limit and the response headers. ## Is there an SDK? [#is-there-an-sdk] Yes, for TypeScript ([npm](https://www.npmjs.com/package/@predictdotfun/sdk), [GitHub](https://github.com/PredictDotFun/sdk)) and Python ([PyPI](https://pypi.org/project/predict-sdk/), [GitHub](https://github.com/PredictDotFun/sdk-python)). Both sign orders, set approvals and manage positions. ## Can an AI assistant help me with the API? [#can-an-ai-assistant-help-me-with-the-api] Yes. These docs are available as [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt), and our SDKs and docs are on Context7. See [Docs for AI tools](/introduction#docs-for-ai-tools). ## How do I authenticate my requests? [#how-do-i-authenticate-my-requests] Send your API key in the `x-api-key` header. Requests for your own wallet, such as placing orders, also need a JWT. See [Authentication](/trading/authentication). ## How do I create orders? [#how-do-i-create-orders] See [Place orders](/trading/place-orders). Before your first order, set your [approvals](/trading/approvals). ## Why was my order rejected? [#why-was-my-order-rejected] The response's `error` field names the reason. [Errors](/resources/errors) lists every code with its fix. ## Is the order book priced in Yes or No? [#is-the-order-book-priced-in-yes-or-no] In Yes. The No prices are the complement of the Yes prices. See [Prices and order book](/concepts/order-book). ## How do I redeem a winning position? [#how-do-i-redeem-a-winning-position] See [Manage positions](/trading/manage-positions). ## Can I use my Predict account through the API or SDK? [#can-i-use-my-predict-account-through-the-api-or-sdk] Yes. Predict works with a regular wallet (an externally owned account, or EOA) and with a smart wallet called a Predict account. If you use the web app, a Predict account is created for you. [Wallets](https://docs.predict.fun/knowledge-base/wallets) in the product docs explains how Predict accounts work. To use your Predict account programmatically, you need: - The Predict account's address, also called its deposit address. - The private key of its Privy wallet, which signs for the Predict account. You can export it from your [account settings](https://predict.fun/account/settings). Fund the Privy wallet with some BNB to pay the gas of on-chain actions, such as setting approvals and cancelling orders on-chain. [Authentication](/trading/authentication) and [Place orders](/trading/place-orders) cover both kinds of wallet. You can also use another account abstraction SDK, such as the ZeroDev SDK. That takes more effort and technical knowledge, and we can offer little support for third-party SDKs. # Introduction (/introduction) Predict is a prediction market on BNB Chain. Its order book runs off-chain, and every trade settles on-chain through Predict's exchange contracts. You can read market data, trade from your own wallet, and follow both as they change. ## The APIs [#the-apis] | Interface | What it is for | | :--- | :--- | | [REST API](/api) | Market data, your account and positions, and placing and removing orders. | | [WebSocket API](/websocket) | Live order books, market status changes, and events for your own orders. | | SDKs | Signing orders, setting approvals, and managing positions on-chain. Available for TypeScript and Python. | Report anything that does not work as described in our [Discord server](https://discord.gg/predictdotfun). ## Environments [#environments] | | BNB Mainnet | BNB Testnet | | :--- | :--- | :--- | | REST API | `https://api.predict.fun` | `https://api-testnet.predict.fun` | | WebSocket API | `wss://ws.predict.fun/ws` | Not available yet | | Chain ID | `56` | `97` | | API key | Required | Not needed | Every REST path starts with `/v1`, for example `https://api.predict.fun/v1/markets`. On mainnet, send your API key in the `x-api-key` header of every REST request. The WebSocket connection takes it as the same header or as an `apiKey` query parameter (see the [WebSocket overview](/websocket)). Testnet works without a key, so you can try the REST API there before you have one. Predict's servers run in AWS `ap-northeast-1` (Tokyo). Deploy close to that region for the lowest latency. ## Get an API key [#get-an-api-key] Create an API key in the [developer console](https://developers.predict.fun). Each key has rate limits, which you can see in the console. See [Rate limits](/resources/rate-limits) for the defaults and how to read them from responses. ## Install an SDK [#install-an-sdk] ```bash npm install @predictdotfun/sdk ethers ``` The SDK uses [ethers v6](https://docs.ethers.org/v6/) for signing. Source: [github.com/PredictDotFun/sdk](https://github.com/PredictDotFun/sdk). ```bash pip install predict-sdk ``` Source: [github.com/PredictDotFun/sdk-python](https://github.com/PredictDotFun/sdk-python). ## Where to start [#where-to-start] How markets, outcomes and categories fit together. How prices work, and how the Yes and No sides share one book. Get a JWT for your wallet, as an EOA or a Predict account. Build, sign and submit limit and market orders. ## Docs for AI tools [#docs-for-ai-tools] These docs are available in forms that coding assistants and language models can read: - [`/llms.txt`](/llms.txt) lists every page with a one-line summary. - [`/llms-full.txt`](/llms-full.txt) holds the full text of every page in one file. It's over 1 MB, more than most AI tools can read at once, so prefer `/llms.txt` and the pages you need. - Every page has a Markdown copy at its URL plus `.md`, for example [`/introduction.md`](/introduction.md). The buttons at the top of each page copy it or open it in an AI tool. - **Ask AI**, at the bottom right of every page, answers questions from these docs. Our SDKs and docs are also on Context7, which you can use through its MCP server or its website: - [Predict TypeScript SDK](https://context7.com/predictdotfun/sdk?tab=chat) - [Predict Python SDK](https://context7.com/predictdotfun/sdk-python?tab=chat) - [Predict REST API](https://context7.com/llmstxt/dev_predict_fun_llms_txt?tab=chat) # Deployed contracts (/resources/deployed-contracts) These are the contracts Predict's markets trade and settle on. Before your first order, approve the exchange contracts for your market type, as [Approvals](/trading/approvals) explains. The product docs describe what each contract does in the [Technical overview](https://docs.predict.fun/developers/technical-overview), and list the security audits in [Audits](https://docs.predict.fun/developers/audits). ## BNB Mainnet [#bnb-mainnet] ### Shared [#shared] | Name | Address | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | | USDT | [0x55d398326f99059fF775485246999027B3197955](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955#code) | | UmaCompatibleOptimisticOracle | [0x76F42e5520E62AD88f8fE583cBb4BfF27eeC2531](https://bscscan.com/address/0x76F42e5520E62AD88f8fE583cBb4BfF27eeC2531#code) | | Vault | [0x09F683d8a144c4ac296D770F839098c3377410c5](https://bscscan.com/address/0x09F683d8a144c4ac296D770F839098c3377410c5#code) | | ZeroDevWithdrawalHelper | [0xf4aA30B537882ECa7E69dEfb68d6f631cdA77B00](https://bscscan.com/address/0xf4aA30B537882ECa7E69dEfb68d6f631cdA77B00#code) | | RewardDistributor (Referral Fee) | [0x14e3cB02F48818a8FeF6BC257059767cA9d436Ae](https://bscscan.com/address/0x14e3cB02F48818a8FeF6BC257059767cA9d436Ae#code) | | RewardDistributor (Yield) | [0x54870Aa3697b5756F5F1102B68e2B71968F77a3C](https://bscscan.com/address/0x54870Aa3697b5756F5F1102B68e2B71968F77a3C#code) | | ThreePoAdapter | [0xe82aE4c6678f4D967Eadac93Ac323fa72806f28c](https://bscscan.com/address/0xe82aE4c6678f4D967Eadac93Ac323fa72806f28c#code) | | ChainlinkUpDownAdapter | [0x77620FAb2969dF2D5696F1f081940E416Cf17cA5](https://bscscan.com/address/0x77620FAb2969dF2D5696F1f081940E416Cf17cA5#code) | | ChainlinkReceiverProxy | [0xf3B45D966E2C285876c66d4a27A7fB23F3D8ABD9](https://bscscan.com/address/0xf3B45D966E2C285876c66d4a27A7fB23F3D8ABD9#code) | | AdminAdapter | [0x69e6BEF6C94ee41E09dB3E828eB9560E34868c4A](https://bscscan.com/address/0x69e6BEF6C94ee41E09dB3E828eB9560E34868c4A#code) | | CTFAutoRedemptionHelper | [0xfca64d34859b120C36426D41f941E708EA47CBec](https://bscscan.com/address/0xfca64d34859b120C36426D41f941E708EA47CBec#code) | ### Yield-bearing markets [#yield-bearing-markets] | Name | Address | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------ | | YieldBearingConditionalTokens | [0x9400F8Ad57e9e0F352345935d6D3175975eb1d9F](https://bscscan.com/address/0x9400F8Ad57e9e0F352345935d6D3175975eb1d9F#code) | | UmaCompatibleCtfAdapter | [0x947cc06D38d3cB0a2BB5AdFB668b99B4FF53d7B4](https://bscscan.com/address/0x947cc06D38d3cB0a2BB5AdFB668b99B4FF53d7B4#code) | | CTFExchange | [0x6bEb5a40C032AFc305961162d8204CDA16DECFa5](https://bscscan.com/address/0x6bEb5a40C032AFc305961162d8204CDA16DECFa5#code) | | NegRisk ConditionalTokens | [0xF64b0b318AAf83BD9071110af24D24445719A07F](https://bscscan.com/address/0xF64b0b318AAf83BD9071110af24D24445719A07F#code) | | YieldBearingNegRiskAdapter | [0x41dCe1A4B8FB5e6327701750aF6231B7CD0B2A40](https://bscscan.com/address/0x41dCe1A4B8FB5e6327701750aF6231B7CD0B2A40#code) | | YieldBearingWrappedCollateral | [0xCfb9beF5F7B748aC72311F057f3a888BC73334D9](https://bscscan.com/address/0xCfb9beF5F7B748aC72311F057f3a888BC73334D9#code) | | NegRiskCtfExchange | [0x8A289d458f5a134bA40015085A8F50Ffb681B41d](https://bscscan.com/address/0x8A289d458f5a134bA40015085A8F50Ffb681B41d#code) | | NegRiskOperator | [0xBB7250101e0e3611D7e136fFE73Bc24b98E3e175](https://bscscan.com/address/0xBB7250101e0e3611D7e136fFE73Bc24b98E3e175#code) | | NegRisk UmaCompatibleCtfAdapter | [0x26B366Ab634C43BdA6D784fDCe34F24A37DF8172](https://bscscan.com/address/0x26B366Ab634C43BdA6D784fDCe34F24A37DF8172#code) | | FeeModuleV3 | [0xF291a67165D751A2E7B4da4F0A012449Eef0a279](https://bscscan.com/address/0xF291a67165D751A2E7B4da4F0A012449Eef0a279#code) | | NegRiskFeeModuleV3 | [0x0989942f8E5b778E804858A0cC791b4469A5fD63](https://bscscan.com/address/0x0989942f8E5b778E804858A0cC791b4469A5fD63#code) | | ConditionalTokensFeesHandlerV2 | [0x13ba3d9F7DA3F8aE955dF4f01f01d7218f81D8fa](https://bscscan.com/address/0x13ba3d9F7DA3F8aE955dF4f01f01d7218f81D8fa#code) | | RegisterTokenHelper | [0xa48C26abd9024a5CC5a869bBd97A6a3D6B9C2089](https://bscscan.com/address/0xa48C26abd9024a5CC5a869bBd97A6a3D6B9C2089#code) | ### Non-yield-bearing markets [#non-yield-bearing-markets] | Name | Address | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------ | | ConditionalTokens | [0x22DA1810B194ca018378464a58f6Ac2B10C9d244](https://bscscan.com/address/0x22DA1810B194ca018378464a58f6Ac2B10C9d244#code) | | UmaCompatibleCtfAdapter | [0x242E1Ba24f6fC524bfb410062Ca5689A9622613d](https://bscscan.com/address/0x242E1Ba24f6fC524bfb410062Ca5689A9622613d#code) | | CTFExchange | [0x8BC070BEdAB741406F4B1Eb65A72bee27894B689](https://bscscan.com/address/0x8BC070BEdAB741406F4B1Eb65A72bee27894B689#code) | | NegRisk ConditionalTokens | [0x22DA1810B194ca018378464a58f6Ac2B10C9d244](https://bscscan.com/address/0x22DA1810B194ca018378464a58f6Ac2B10C9d244#code) | | NegRiskAdapter | [0xc3Cf7c252f65E0d8D88537dF96569AE94a7F1A6E](https://bscscan.com/address/0xc3Cf7c252f65E0d8D88537dF96569AE94a7F1A6E#code) | | WrappedCollateral | [0x66239b70133773A72A0D589E5564E88a50Cd39e7](https://bscscan.com/address/0x66239b70133773A72A0D589E5564E88a50Cd39e7#code) | | NegRiskCtfExchange | [0x365fb81bd4A24D6303cd2F19c349dE6894D8d58A](https://bscscan.com/address/0x365fb81bd4A24D6303cd2F19c349dE6894D8d58A#code) | | NegRiskOperator | [0x56020F5024641d577Cb54032aF70a23a986ECfFD](https://bscscan.com/address/0x56020F5024641d577Cb54032aF70a23a986ECfFD#code) | | NegRisk UmaCompatibleCtfAdapter | [0xf61198a64C2e4CAD8CCAf218f3f2ECeFb017902F](https://bscscan.com/address/0xf61198a64C2e4CAD8CCAf218f3f2ECeFb017902F#code) | | FeeModuleV3 | [0xdcFFeB0c30263888A48485a664Ec9563A54891a1](https://bscscan.com/address/0xdcFFeB0c30263888A48485a664Ec9563A54891a1#code) | | FeeModuleV2Adapter | [0xC6346768b256b1C3f4a6c521B5f466F9a849A7B6](https://bscscan.com/address/0xC6346768b256b1C3f4a6c521B5f466F9a849A7B6#code) | | ConditionalTokensFeesHandlerV2 | [0x7Bd1a130c2206071121c0f1eF9666d4584252b84](https://bscscan.com/address/0x7Bd1a130c2206071121c0f1eF9666d4584252b84#code) | | RegisterTokenHelper | [0x89f92C3c27F18080AF1361024C6a892144fD8e5E](https://bscscan.com/address/0x89f92C3c27F18080AF1361024C6a892144fD8e5E#code) | ## BNB Testnet [#bnb-testnet] ### Shared [#shared-1] | Name | Address | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | | USDT | [0xB32171ecD878607FFc4F8FC0bCcE6852BB3149E0](https://testnet.bscscan.com/address/0xB32171ecD878607FFc4F8FC0bCcE6852BB3149E0#code) | | UmaCompatibleOptimisticOracle | [0xb5ae73B1dB6659b741d14DEDCeF6B73c88A8fe4c](https://testnet.bscscan.com/address/0xb5ae73B1dB6659b741d14DEDCeF6B73c88A8fe4c#code) | | Vault | [0x415bdd0F4e5eE9A50B2394ff8B6b20319e77255d](https://testnet.bscscan.com/address/0x415bdd0F4e5eE9A50B2394ff8B6b20319e77255d#code) | | ZeroDevWithdrawalHelper | N/A | | RewardDistributor (Referral Fee) | N/A | | RewardDistributor (Yield) | [0x77B3aE777095384666008B9156948Ea9E5Df4A0c](https://testnet.bscscan.com/address/0x77B3aE777095384666008B9156948Ea9E5Df4A0c#code) | | ThreePoAdapter | [0x8920921B1cf1d71d404087aA350B7C87B3F609e2](https://testnet.bscscan.com/address/0x8920921B1cf1d71d404087aA350B7C87B3F609e2#code) | | ChainlinkUpDownAdapter | [0x7978ae55d7BC762b57A72ec2cC6e3B84712D0602](https://testnet.bscscan.com/address/0x7978ae55d7BC762b57A72ec2cC6e3B84712D0602#code) | | ChainlinkReceiverProxy | [0xf23ba61D9D9Cc6C9b28eE4440dBEdE117fDC091f](https://testnet.bscscan.com/address/0xf23ba61D9D9Cc6C9b28eE4440dBEdE117fDC091f#code) | | AdminAdapter | [0x1773aa23d4F16c2cdD6CBA269F9C057806511310](https://testnet.bscscan.com/address/0x1773aa23d4F16c2cdD6CBA269F9C057806511310#code) | | CTFAutoRedemptionHelper | [0x17a398219B79901E8598ADe7c315D195CaA9e0c0](https://testnet.bscscan.com/address/0x17a398219B79901E8598ADe7c315D195CaA9e0c0#code) | ### Yield-bearing markets [#yield-bearing-markets-1] | Name | Address | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- | | YieldBearingConditionalTokens | [0x38BF1cbD66d174bb5F3037d7068E708861D68D7f](https://testnet.bscscan.com/address/0x38BF1cbD66d174bb5F3037d7068E708861D68D7f#code) | | UmaCompatibleCtfAdapter | [0x97909cfD6247D67963922Cf8908Bfb648049d533](https://testnet.bscscan.com/address/0x97909cfD6247D67963922Cf8908Bfb648049d533#code) | | CTFExchange | [0x8a6B4Fa700A1e310b106E7a48bAFa29111f66e89](https://testnet.bscscan.com/address/0x8a6B4Fa700A1e310b106E7a48bAFa29111f66e89#code) | | NegRisk ConditionalTokens | [0x26e865CbaAe99b62fbF9D18B55c25B5E079A93D5](https://testnet.bscscan.com/address/0x26e865CbaAe99b62fbF9D18B55c25B5E079A93D5#code) | | YieldBearingNegRiskAdapter | [0xb74aea04bdeBE912Aa425bC9173F9668e6f11F99](https://testnet.bscscan.com/address/0xb74aea04bdeBE912Aa425bC9173F9668e6f11F99#code) | | YieldBearingWrappedCollateral | [0xd5647Df4f2e884bb21167C2aBB99971693094599](https://testnet.bscscan.com/address/0xd5647Df4f2e884bb21167C2aBB99971693094599#code) | | NegRiskCtfExchange | [0x95D5113bc50eD201e319101bbca3e0E250662fCC](https://testnet.bscscan.com/address/0x95D5113bc50eD201e319101bbca3e0E250662fCC#code) | | NegRiskOperator | [0xBF726EbF730ba7e35AE439fE8A8eceAc56c58f44](https://testnet.bscscan.com/address/0xBF726EbF730ba7e35AE439fE8A8eceAc56c58f44#code) | | NegRisk UmaCompatibleCtfAdapter | [0xC3aE7C024513ec82e0eb4B4f1F568B58D4b30f9E](https://testnet.bscscan.com/address/0xC3aE7C024513ec82e0eb4B4f1F568B58D4b30f9E#code) | | FeeModuleV3 | [0xB4Ec0Bf3067462bC49181cA82083FC85d35e21a5](https://testnet.bscscan.com/address/0xB4Ec0Bf3067462bC49181cA82083FC85d35e21a5#code) | | NegRiskFeeModuleV3 | [0xEfb5B1Fc08a785dF4f13487309D99472b9714Cb9](https://testnet.bscscan.com/address/0xEfb5B1Fc08a785dF4f13487309D99472b9714Cb9#code) | | ConditionalTokensFeesHandlerV2 | [0xB3383985cF3d39d971a6b5eaD9Ab8Ced260Ded97](https://testnet.bscscan.com/address/0xB3383985cF3d39d971a6b5eaD9Ab8Ced260Ded97#code) | | RegisterTokenHelper | [0x20526fA3Ef788A0898b9DFcf390c16B1391d84e7](https://testnet.bscscan.com/address/0x20526fA3Ef788A0898b9DFcf390c16B1391d84e7#code) | ### Non-yield-bearing markets [#non-yield-bearing-markets-1] | Name | Address | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- | | ConditionalTokens | [0x2827AAef52D71910E8FBad2FfeBC1B6C2DA37743](https://testnet.bscscan.com/address/0x2827AAef52D71910E8FBad2FfeBC1B6C2DA37743#code) | | UmaCompatibleCtfAdapter | [0x8403922ad0c5d39B4148e17C86095cAf10B37E50](https://testnet.bscscan.com/address/0x8403922ad0c5d39B4148e17C86095cAf10B37E50#code) | | CTFExchange | [0x2A6413639BD3d73a20ed8C95F634Ce198ABbd2d7](https://testnet.bscscan.com/address/0x2A6413639BD3d73a20ed8C95F634Ce198ABbd2d7#code) | | NegRisk ConditionalTokens | [0x2827AAef52D71910E8FBad2FfeBC1B6C2DA37743](https://testnet.bscscan.com/address/0x2827AAef52D71910E8FBad2FfeBC1B6C2DA37743#code) | | NegRiskAdapter | [0x285c1B939380B130D7EBd09467b93faD4BA623Ed](https://testnet.bscscan.com/address/0x285c1B939380B130D7EBd09467b93faD4BA623Ed#code) | | WrappedCollateral | [0x61E0b85207393A3804c79Ef35A155A676238C469](https://testnet.bscscan.com/address/0x61E0b85207393A3804c79Ef35A155A676238C469#code) | | NegRiskCtfExchange | [0xd690b2bd441bE36431F6F6639D7Ad351e7B29680](https://testnet.bscscan.com/address/0xd690b2bd441bE36431F6F6639D7Ad351e7B29680#code) | | NegRiskOperator | [0xD02331CdbEf06F54aeEddA76A2E61Df2cef1C8e8](https://testnet.bscscan.com/address/0xD02331CdbEf06F54aeEddA76A2E61Df2cef1C8e8#code) | | NegRisk UmaCompatibleCtfAdapter | [0x52DA245ac170155391e7607c67b77D549005002d](https://testnet.bscscan.com/address/0x52DA245ac170155391e7607c67b77D549005002d#code) | | FeeModuleV3 | [0x9913D3407B9A24C7B07D7fB7400Dc3c58376a7EC](https://testnet.bscscan.com/address/0x9913D3407B9A24C7B07D7fB7400Dc3c58376a7EC#code) | | FeeModuleV2Adapter | [0x02d5caF4068D6B13d733D11660c5f0Aec557a46D](https://testnet.bscscan.com/address/0x02d5caF4068D6B13d733D11660c5f0Aec557a46D#code) | | ConditionalTokensFeesHandlerV2 | [0x35a506B63E162FBD5171Ff9cBfa4780Db1C1a64A](https://testnet.bscscan.com/address/0x35a506B63E162FBD5171Ff9cBfa4780Db1C1a64A#code) | | RegisterTokenHelper | [0x450bfd61d19b0a02dD87056F04914a04749d5bc4](https://testnet.bscscan.com/address/0x450bfd61d19b0a02dD87056F04914a04749d5bc4#code) | ## Previous contracts [#previous-contracts] Older fee contracts that matching no longer uses. You may still see them in past transactions. ### BNB Mainnet [#bnb-mainnet-1] | Name | Markets | Address | | :------------------------------------------ | :---------------- | :------------------------------------------------------------------------------------------------------------------------ | | FeeModuleV2 | Yield-bearing | [0xFbC2259aBB3F01c019ECE1d0200Ee673BB7BA34F](https://bscscan.com/address/0xFbC2259aBB3F01c019ECE1d0200Ee673BB7BA34F#code) | | NegRiskFeeModuleV2 | Yield-bearing | [0xD172f3FBabe763Ee8E52D8b32421574236dA6057](https://bscscan.com/address/0xD172f3FBabe763Ee8E52D8b32421574236dA6057#code) | | ConditionalTokensFeesHandler | Yield-bearing | [0xb4D9F13738a50E88E0ade2ecCc89254EF1645f6E](https://bscscan.com/address/0xb4D9F13738a50E88E0ade2ecCc89254EF1645f6E#code) | | ConditionalTokensFeesHandlerV2 (Deprecated) | Yield-bearing | [0x57f12f4B39a16e13994F9bd95246a32C85883AB2](https://bscscan.com/address/0x57f12f4B39a16e13994F9bd95246a32C85883AB2#code) | | FeeModuleV2 | Non-yield-bearing | [0xF1f8F5C641F20C48526269EF7DFF19172Efa9783](https://bscscan.com/address/0xF1f8F5C641F20C48526269EF7DFF19172Efa9783#code) | | NegRiskFeeModuleV2 | Non-yield-bearing | [0xF2311C668aAA8dEc48D5da577d3018eb94b3132F](https://bscscan.com/address/0xF2311C668aAA8dEc48D5da577d3018eb94b3132F#code) | | ConditionalTokensFeesHandler | Non-yield-bearing | [0xD63206243192f1AF3d6fC4442db4e3cf25E64030](https://bscscan.com/address/0xD63206243192f1AF3d6fC4442db4e3cf25E64030#code) | | ConditionalTokensFeesHandlerV2 (Deprecated) | Non-yield-bearing | [0x2d4370431b5cd3D2EE4ab11F66369B8ed424093E](https://bscscan.com/address/0x2d4370431b5cd3D2EE4ab11F66369B8ed424093E#code) | ### BNB Testnet [#bnb-testnet-1] | Name | Markets | Address | | :------------------------------------------ | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------- | | FeeModuleV2 | Yield-bearing | [0x66569E776f247C0e270Bb71259755DC31869c3c7](https://testnet.bscscan.com/address/0x66569E776f247C0e270Bb71259755DC31869c3c7#code) | | NegRiskFeeModuleV2 | Yield-bearing | [0xD803Ab73B8598305D7256BF3ba7143a2b5C39Cdf](https://testnet.bscscan.com/address/0xD803Ab73B8598305D7256BF3ba7143a2b5C39Cdf#code) | | ConditionalTokensFeesHandler | Yield-bearing | [0x749c677A7DA13f1D4bdF5853071FA2f1FB1c2587](https://testnet.bscscan.com/address/0x749c677A7DA13f1D4bdF5853071FA2f1FB1c2587#code) | | ConditionalTokensFeesHandlerV2 (Deprecated) | Yield-bearing | [0x16F0aEa40f4610a6D895c00dc07D42feeb060D09](https://testnet.bscscan.com/address/0x16F0aEa40f4610a6D895c00dc07D42feeb060D09#code) | | FeeModuleV2 | Non-yield-bearing | [0xD479C5c6A3B98C6db8503cb6530519Bb19249CC2](https://testnet.bscscan.com/address/0xD479C5c6A3B98C6db8503cb6530519Bb19249CC2#code) | | NegRiskFeeModuleV2 | Non-yield-bearing | [0x7F5ED9CD275DD6b88C5302262DbA9FD8c407Dd87](https://testnet.bscscan.com/address/0x7F5ED9CD275DD6b88C5302262DbA9FD8c407Dd87#code) | | ConditionalTokensFeesHandler | Non-yield-bearing | [0xD63D7F7AeAD49C1711b5Cd00Ed4CA26e9a83ff6C](https://testnet.bscscan.com/address/0xD63D7F7AeAD49C1711b5Cd00Ed4CA26e9a83ff6C#code) | | ConditionalTokensFeesHandlerV2 (Deprecated) | Non-yield-bearing | [0x203ba4F2f339D21f2895Eb14f2742e511f4306C6](https://testnet.bscscan.com/address/0x203ba4F2f339D21f2895Eb14f2742e511f4306C6#code) | # Errors (/resources/errors) Every REST API error has the same JSON shape, whatever the endpoint. ```json { "success": false, "code": 400, "error": "create_order_insufficient_collateral_allowance", "message": "Insufficient collateral: USDT allowance is less than the total bid amount.", "timestamp": "2026-11-04T20:37:19.030930831Z", "trace": "f81a7973a8d080f6723e5d3fb705604a" } ``` | Field | Meaning | | :--- | :--- | | `success` | Always `false` for an error. | | `code` | The HTTP status code. | | `error` | A stable code to match on in your client. For a 400 it names the exact problem, like the codes below. For any other status it names the status, like `unauthorized`. | | `message` | A description for people. It can carry details, such as the minimum a value has to reach. Do not match on it. | | `timestamp` | When the error happened, in RFC 3339. | | `trace` | An ID that identifies the request in our logs. Include it when you report a problem. | ## Status codes [#status-codes] | Status | `error` | Meaning | | :--- | :--- | :--- | | 400 | A specific code | The request is invalid. The `error` code says why. | | 401 | `unauthorized` | The API key or JWT is missing, invalid or expired, or a JWT does not belong to the wallet the request acts for. | | 403 | `forbidden` | The request is not allowed, for example from a restricted region or for a blocked wallet. | | 404 | `not_found` | The resource does not exist. | | 429 | `too_many_requests` | You went over a rate limit. See [Rate limits](/resources/rate-limits). | | 500 | `internal_server_error` | Something failed on our side. Retry later, and report it with the `trace` if it persists. | | 502 | `bad_gateway` | An upstream service failed. Retry later. | | 503 | `service_unavailable` | The service or feature is unavailable for now. Retry later. | ## General codes [#general-codes] | `error` | Cause | Fix | | :--- | :--- | :--- | | `bad_request` | The request is invalid in a way no specific code covers. | Read the `message`. | | `invalid_cursor` | The `after` cursor is malformed. | Pass the `cursor` from the previous page unchanged. | | `activity_invalid_input` | The input to an activity endpoint is invalid. | Check the request against the endpoint's reference. | | `activity_lock_rejected` | One or more activity selections fail the activity's lock rules. | Read the `message` for the selection that failed. | | `activity_state_conflict` | The activity's current state does not allow the request. | Check the activity's state first. | ## Order codes [#order-codes] These come from [Create an order](/api/orders/create-an-order). [Place orders](/trading/place-orders) explains each rule. ### Order fields [#order-fields] | `error` | Cause | Fix | | :--- | :--- | :--- | | `create_order_invalid_numeric_value` | `pricePerShare`, `makerAmount` or `takerAmount` is not an integer string. `pricePerShare` is in wei, so `"0.4"` is invalid. | Send integers in wei, such as `"400000000000000000"` for 0.40. | | `create_order_invalid_fee_rate_bps` | `feeRateBps` is above 10000. | Use the market's `feeRateBps`. | | `create_order_invalid_nonce` | `nonce` is negative or too large. | Use `0`, as the SDK does. | | `create_order_invalid_slippage_bps` | `slippageBps` is above 10000. | Use a value from 0 to 10000. | | `create_order_invalid_signature_hex` | `signature` is not valid hex, or is too long. | Send the signature the SDK returns. | | `create_order_price_out_of_range` | `pricePerShare` is not between 0 and 1 (exclusive). | Use a price above 0 and below 1e18. | | `create_order_price_field_zero` | The amount that sets the price is zero: `makerAmount` for a buy, `takerAmount` for a sell. | Compute the amounts with the SDK's order amount helpers. | | `create_order_amount_field_zero` | `makerAmount` or `takerAmount` is zero. | Use non-zero amounts. | | `create_order_price_field_precision_invalid` | For a `LIMIT` order, the price-setting amount has digits below 1e10 wei. | Compute the amounts with the SDK's `getLimitOrderAmounts`. | | `create_order_price_precision_too_high` | The price has more decimals than the market's `decimalPrecision`. | Round the price to `decimalPrecision` decimals. | | `create_order_limit_amounts_mismatch` | A `LIMIT` order's amounts do not match `pricePerShare`. | Compute the amounts with `getLimitOrderAmounts`, and send the `pricePerShare` it returns. | | `create_order_market_amounts_mismatch` | A `MARKET` order's signed average price is more than 1% better for you than `pricePerShare`. | Compute the amounts with the SDK's `getMarketOrderAmounts`. | | `create_order_slippage_non_market` | `slippageBps` is set on a `LIMIT` order. | Leave `slippageBps` out, or set it to `0`. | | `create_order_min_order_value_not_met` | A buy is worth less than 1 USDT. | Increase the order's size. | | `create_order_min_order_size_not_met` | A sell has fewer shares than the minimum: 1 share for a `LIMIT` order, 0.01 for a `MARKET` order. | Increase the order's size. | | `create_order_expiry_too_soon` | The order expires in less than 2 minutes. | Set the expiration at least 2 minutes ahead. | | `create_order_expiry_too_far` | The order expires after 2100-01-01. | Use an earlier expiration. | | `create_order_maker_signer_mismatch` | `maker` and `signer` differ. | Set both to the wallet's address. For a Predict account, use the account's address. | | `create_order_fill_or_kill_not_supported` | `isFillOrKill` is set on a `LIMIT` order. | Use it only on `MARKET` orders. | | `create_order_post_only_not_supported` | `isPostOnly` is set on a `MARKET` order. | Use it only on `LIMIT` orders. | | `create_order_reserved_balance_policy_invalid` | `reservedBalancePolicy` is set on a `LIMIT` order. | Use it only on `MARKET` orders. | | `create_order_fee_rate_too_low` | `feeRateBps` is below the market's fee, less any referral discount. | Sign the market's `feeRateBps`. See [Fees](/trading/fees). | | `create_order_hash_mismatch` | The `hash` you sent does not match the order. | Send the hash the SDK computes, or leave it out. | | `create_order_duplicate_order` | An order with this hash already exists. | Build a new order. The SDK gives each order a new salt. | ### Market and exchange state [#market-and-exchange-state] | `error` | Cause | Fix | | :--- | :--- | :--- | | `create_order_market_token_not_found` | No market has this `tokenId`, or the market is hidden, as when its `tradingStatus` is `CANCEL_ONLY`. | Use an outcome's `onChainId` from a market that is trading. | | `create_order_market_not_trading` | The market is paused or resolved. | Check the market's `status` and `tradingStatus`. | | `create_order_trading_disabled` | Matching is off for the market, and only post-only `LIMIT` orders are accepted. | Wait for `tradingStatus` to be `OPEN`, or send a post-only order. | | `create_order_market_orders_disabled` | `MARKET` orders are not accepted in the market right now. | Use a `LIMIT` order, or wait. | | `create_order_exchange_paused` | The exchange contract is paused. | Wait until trading resumes. | | `create_order_max_orders_per_market_exceeded` | Your wallet has the maximum number of open `LIMIT` orders in this market. | Remove some orders first. See [Cancel orders](/trading/cancel-orders). | | `create_order_unsupported_wallet` | The signing wallet is a smart contract the exchange does not support. | Sign with an EOA or a Predict account. | ### Funds and approvals [#funds-and-approvals] The API checks your balance and allowance against the new order plus your other open orders on the same outcome and side in the same market. | `error` | Cause | Fix | | :--- | :--- | :--- | | `create_order_insufficient_collateral_balance` | Your USDT balance does not cover your open buys plus this one. | Deposit USDT, or remove open buys. | | `create_order_insufficient_collateral_allowance` | Your USDT allowance to the exchange does not cover your open buys plus this one. | Set your approvals. See [Approvals](/trading/approvals). | | `create_order_insufficient_shares_balance` | You hold fewer shares than your open sells plus this one. | Sell fewer shares, or remove open sells. | | `create_order_exchange_not_approved` | The exchange is not approved to move your outcome tokens. | Set your approvals. | | `create_order_neg_risk_adapter_not_approved` | In a negative-risk market, the adapter is not approved to move your outcome tokens. | Set your approvals for negative-risk markets. | ### On-chain checks [#on-chain-checks] The API asks the exchange contract whether the order is valid. These codes carry the contract's reason. If the contract's answer comes after the API has responded, the order is cancelled instead of rejected (see the note at the end of this page). | `error` | Cause | Fix | | :--- | :--- | :--- | | `create_order_contract_invalid_signature` | The signature does not match the order and signer. | Sign the order's typed data for the right exchange. Check `isNegRisk` and `isYieldBearing`. | | `create_order_contract_invalid_signature_length` | The signature has the wrong length. | Send the signature the SDK returns. | | `create_order_contract_order_expired` | The order has expired. | Build a new order with a later expiration. | | `create_order_contract_fee_too_high` | `feeRateBps` is above the exchange's cap of 1000. | Sign the market's `feeRateBps`. | | `create_order_contract_invalid_token_id` | The exchange does not know this token. | Use an outcome's `onChainId` from the market. | | `create_order_contract_order_filled_or_cancelled` | The order was already filled, or cancelled on-chain. | Build a new order. | | `create_order_contract_invalid_nonce` | The order's `nonce` does not match the wallet's nonce on the exchange. | Sign the order with the wallet's current nonce on that exchange. It is `0`, the SDK's default, unless the wallet has raised it on-chain. | | `create_order_contract_paused` | The exchange contract is paused. | Wait until trading resumes. | A `201` from Create an order does not guarantee the order stays open. Some checks finish after the response. When one fails, the order is cancelled and you get a [wallet event](/websocket/topics/wallet-events). See [Order lifecycle](/trading/order-lifecycle). # Rate limits (/resources/rate-limits) Rate limits apply to the REST API. Each limit counts requests in a fixed window, such as one calendar minute. When the window ends, the count starts again. ## Limits [#limits] Keys from the [developer console](https://developers.predict.fun) belong to an app. The app's limits are shared by all of its keys, and you can see them in the console. A new app starts with these: | Routes | Limit | | :--- | :--- | | Trading: `/v1/orders` and everything under it, `/v1/markets/{id}/orderbook`, `/v1/markets/{id}/quote`, `/v1/markets/quotes` | 500 per minute | | Search: `/v1/search` | 120 per minute | | Everything else | 250 per minute | Placing and removing orders (`POST` requests under `/v1/orders`) also has a limit of 40 per second. Both limits apply, so a burst of orders has to stay under both. Keys issued before the developer console have two limits: 500 per minute for the trading routes and 240 per minute for everything else, search included. On testnet you can call the API without a key. Requests without a key are limited per IP address: 500 per minute for the trading routes and 240 per minute for everything else. On mainnet, every request needs a key. ## Read your budget from responses [#read-your-budget-from-responses] Every response carries headers that tell you where you stand: | Header | Meaning | | :--- | :--- | | `RateLimit-Limit` | The number of requests the window allows. | | `RateLimit-Remaining` | How many requests are left in the window. | | `RateLimit-Reset` | Seconds until the window resets. | | `RateLimit-Policy` | Every limit that applied to the request, as `"name";q=;w=`. | | `RateLimit` | Every limit's live state, as `"name";r=;t=`. | For example, a request to `/v1/markets` on testnet returns: ```http RateLimit-Limit: 240 RateLimit-Remaining: 239 RateLimit-Reset: 39 RateLimit-Policy: "rpm";q=240;w=60 RateLimit: "rpm";r=239;t=39 ``` When two limits apply, as for order placement, `RateLimit-Policy` and `RateLimit` list both, such as `"rps";q=40;w=1, "rpm";q=500;w=60`. `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` then describe whichever limit is closer to running out. ## When you go over [#when-you-go-over] A request over the limit gets a `429` response, and the request is not processed: ```json { "success": false, "code": 429, "error": "too_many_requests", "message": "Rate limit exceeded", "timestamp": "2026-11-04T20:37:19.030930831Z" } ``` The response has the same `RateLimit-*` headers. There is no `Retry-After` header, so use `RateLimit-Reset` to know how long to wait: ```typescript // Sends a request and, on a 429, waits for the rate limit window to reset before // trying again. async function fetchWithRetry(url: string, init: RequestInit, attempts = 3) { for (let attempt = 1; ; attempt++) { const response = await fetch(url, init); if (response.status !== 429 || attempt === attempts) return response; // seconds until the window that refused the request resets, plus a random // fraction so parallel clients do not retry at the same moment. const resetSeconds = Number(response.headers.get("RateLimit-Reset") ?? 1); await new Promise((resolve) => setTimeout(resolve, (resetSeconds + Math.random()) * 1000), ); } } const response = await fetchWithRetry("https://api.predict.fun/v1/markets", { headers: { "x-api-key": "YOUR_API_KEY" }, }); console.log(response.status, response.headers.get("RateLimit-Remaining")); ``` ```python import random import time import requests def get_with_retry(url, headers, attempts=3): """Sends a GET request and, on a 429, waits for the rate limit window to reset before trying again.""" for attempt in range(1, attempts + 1): response = requests.get(url, headers=headers) if response.status_code != 429 or attempt == attempts: return response # seconds until the window that refused the request resets, plus a random # fraction so parallel clients do not retry at the same moment. reset_seconds = int(response.headers.get("RateLimit-Reset", "1")) time.sleep(reset_seconds + random.random()) response = get_with_retry( "https://api.predict.fun/v1/markets", headers={"x-api-key": "YOUR_API_KEY"}, ) print(response.status_code, response.headers.get("RateLimit-Remaining")) ``` ## Stay under the limits [#stay-under-the-limits] - Use the [WebSocket API](/websocket) for anything that changes often, such as order books and your own orders, instead of polling the REST API. - Watch `RateLimit-Remaining` and slow down before it reaches `0`. - Remove orders in batches. One request can remove up to 100 orders (see [Cancel orders](/trading/cancel-orders)). # Approvals (/trading/approvals) Orders settle on-chain. When an order fills, an exchange contract moves USDT from the buyer and outcome tokens from the seller. So the wallet that holds the funds must approve those contracts first. You do this once for each contract, not once for each order. Without the approvals, the API rejects your orders. ## What to approve [#what-to-approve] A market's `isNegRisk` and `isYieldBearing` fields, from [`GET /v1/markets/{id}`](/api/markets/get-market-by-id), tell you which contracts it uses. USDT approvals are ERC-20 allowances (`approve`). Outcome token approvals are ERC-1155 operator approvals (`setApprovalForAll`) on the conditional tokens contract that holds the market's shares. [Which contract holds your shares](/concepts/positions#which-contract-holds-your-shares) lists it for each market type. | To | Standard market | Negative-risk market | | :----------------- | :--------------------------------------- | :------------------------------------------------------------------ | | Buy | USDT allowance for `CTFExchange` | USDT allowance for `NegRiskCtfExchange` | | Sell | Outcome tokens for `CTFExchange` | Outcome tokens for `NegRiskCtfExchange` and `NegRiskAdapter` | | Split positions | USDT allowance for `ConditionalTokens` | USDT allowance for `NegRiskAdapter` | | Merge or redeem | Nothing | Outcome tokens for `NegRiskAdapter` | | Convert positions | Not available | Outcome tokens for `NegRiskAdapter` | Negative-risk markets need the `NegRiskAdapter` for selling, because the exchange moves your outcome tokens through it. The SDK's `TRADE` steps include this approval for both sides. Yield-bearing markets follow the same table, with `YieldBearingConditionalTokens` in place of `ConditionalTokens`, `YieldBearingNegRiskAdapter` in place of `NegRiskAdapter`, and their own `CTFExchange` and `NegRiskCtfExchange`. A yield-bearing negative-risk market holds its outcome tokens on its own `NegRisk ConditionalTokens` contract, not on `YieldBearingConditionalTokens`. The addresses are on [Deployed contracts](/resources/deployed-contracts). ## Set every approval at once [#set-every-approval-at-once] `setApprovals()` sends each approval that the wallet is still missing for buying and selling on every market type. It skips the ones already in place, so you can call it safely at startup. Each missing approval is one transaction, paid in BNB. It doesn't set the USDT allowances for splitting positions. For those, use the `SPLIT` steps described below. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // sends each missing trading approval for every market type, and skips the ones in place. const result = await orderBuilder.setApprovals(); if (!result.success) throw new Error("Failed to set approvals"); ``` ```python import os from predict_sdk import ChainId, OrderBuilder builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # sends each missing trading approval for every market type, and skips the ones in place. result = builder.set_approvals() if not result.success: raise RuntimeError("Failed to set approvals") ``` In Python, `set_approvals(is_yield_bearing=False)` or `set_approvals(is_yield_bearing=True)` limits the run to one set of contracts. ## Set only what an operation needs [#set-only-what-an-operation-needs] To show users an approval checklist, ask the SDK for the steps one operation needs. Describe the operation with a scope: - `operation` is one of `TRADE`, `SPLIT`, `MERGE`, `REDEEM` and `CONVERT`. `CONVERT` exists only for negative-risk markets, and the SDK throws `InvalidApprovalOperationError` for a standard market. - `isNegRisk` and `isYieldBearing` describe the market. - `side` is optional and narrows a `TRADE` to buying (`Side.BUY`) or selling (`Side.SELL`). Without it, the steps cover both. `getApprovalSteps` returns the steps in order. It reads nothing from the chain, so it works before a wallet is connected. An operation that needs no approval returns an empty list. For onboarding, `getAllApprovalSteps()` returns every step the protocol could need. Then check which steps are already done with `checkApprovals`, and send the rest with `runApprovals`. In TypeScript, `checkApprovals` batches its reads into one call. In Python, it reads the steps one at a time. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // isNegRisk and isYieldBearing come from the market, for example GET /v1/markets/{id}. const steps = orderBuilder.getApprovalSteps({ operation: "TRADE", isNegRisk: true, isYieldBearing: false, }); // mark the steps already in place, in one batched read. for (const { step, satisfied } of await orderBuilder.checkApprovals(steps)) { console.log(step.label, satisfied ? "done" : "to do"); } // send the missing ones, one transaction each. const report = await orderBuilder.runApprovals(steps, { onProgress: ({ step, status }) => console.log(step.label, status), }); if (!report.success) throw new Error("Failed to set approvals"); ``` ```python import os from predict_sdk import ApprovalScope, ChainId, OrderBuilder builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # both flags come from the market, for example GET /v1/markets/{id}. steps = builder.get_approval_steps( ApprovalScope(operation="TRADE", is_neg_risk=True, is_yield_bearing=False) ) # mark the steps already in place, reading them one at a time. for check in builder.check_approvals(steps): print(check.step.label, "done" if check.satisfied else "to do") # send the missing ones, one transaction each. report = builder.run_approvals( steps, on_progress=lambda progress: print(progress.step.label, progress.status) ) if not report.success: raise RuntimeError("Failed to set approvals") ``` Each step is plain data. Its `id` is stable, so you can key your own labels or translations off it. The SDK's `label` and `description` match the web app's wording. ```typescript { id: "ERC1155_APPROVAL:CTF_EXCHANGE", type: "ERC1155_APPROVAL", // or "ERC20_ALLOWANCE" spender: "0x8BC0...B689", // the contract that gets the permission token: "0x22DA...d244", // the conditional tokens contract, or USDT label: "Approve Exchange", description: "Allows you to interact with the exchange.", } ``` `runApprovals` removes duplicate steps, so you can pass the steps of several operations together. It checks each step first and skips it if it's already in place (`skipSatisfied`, on by default). It stops at the first failure (`stopOnError`, on by default). Its progress callback reports each step's status: | Status | Meaning | | :----------- | :---------------------------------------------------------- | | `checking` | Reading on-chain whether the approval is already in place. | | `skipped` | Already in place, so nothing was sent. | | `submitting` | The SDK is sending the transaction and waiting for it to confirm. | | `confirmed` | The transaction succeeded. | | `failed` | The transaction reverted or failed. | To send a single step yourself, for example after the user confirms it, call `setApproval(step)`. ERC-20 steps approve the maximum amount by default. ## Predict accounts [#predict-accounts] The approvals belong to the Predict account, because it holds the funds. When you create the order builder with a Predict account, the SDK sends each approval through the account. Your Privy wallet signs those transactions and pays their gas, so it needs some BNB. See [Authentication](/trading/authentication#with-a-predict-account) for how to set up the builder. ## When an approval is missing [#when-an-approval-is-missing] The API checks approvals and balances when you place an order. If that check doesn't finish before the API responds, the order is accepted and then cancelled when the check fails (see [Place orders](/trading/place-orders#the-response)). Otherwise you get one of these errors when something is missing: | Error code | Meaning | | :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | | `create_order_insufficient_collateral_allowance` | The USDT allowance for the exchange is lower than this order plus the unfilled part of your other open buy orders for the same outcome. | | `create_order_exchange_not_approved` | Your outcome tokens aren't approved for the exchange, so you can't sell them. | | `create_order_neg_risk_adapter_not_approved` | On a negative-risk market, your outcome tokens aren't approved for the `NegRiskAdapter`. | See [Errors](/resources/errors) for every error code. # Authentication (/trading/authentication) Predict's API uses two credentials: | Credential | Header | Needed for | | :--------- | :---------------------------- | :------------------------------------------------------------------------------------------------ | | API key | `x-api-key: ` | Every request on mainnet. | | JWT | `Authorization: Bearer ` | Requests that act for a wallet, such as creating and removing orders or reading its own orders. | ## API key [#api-key] Create an API key in the [developer console](https://developers.predict.fun). Send it in the `x-api-key` header of every mainnet request. A request without a valid key gets a `401`. Testnet (`https://api-testnet.predict.fun`) needs no key. Leave the header out there, because testnet still rejects an invalid key. The WebSocket API takes the same key, in the `x-api-key` header or the `apiKey` query parameter. See [WebSocket API](/websocket). Each key has its own [rate limits](/resources/rate-limits). ## Get a JWT [#get-a-jwt] A JWT proves that you control a wallet. You get one in three steps: 1. Get a message to sign from [`GET /v1/auth/message`](/api/auth/get-auth-message). 2. Sign the message with the wallet. 3. Send the message, the signature and the wallet's address to [`POST /v1/auth`](/api/auth/get-jwt-with-valid-signature). The response holds the JWT in `data.token`. The message ends with a timestamp. Sign and send it within 10 minutes, or the login is rejected. A JWT is valid for 24 hours. After that, log in again. ### With a wallet [#with-a-wallet] Sign the message as a personal message (EIP-191) with the wallet's private key. The `signer` is the wallet's address. ```typescript import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); // 1. Get the message to sign. const messageResponse = await fetch("https://api.predict.fun/v1/auth/message", { headers: { "x-api-key": apiKey }, }); const { message } = (await messageResponse.json()).data; // 2. Sign it with the wallet. const signature = await wallet.signMessage(message); // 3. Exchange the signature for a JWT. const jwtResponse = await fetch("https://api.predict.fun/v1/auth", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey }, body: JSON.stringify({ signer: wallet.address, message, signature }), }); if (!jwtResponse.ok) throw new Error(`Login failed: ${jwtResponse.status}`); const { token } = (await jwtResponse.json()).data; console.log(token); ``` ```python import os import requests from eth_account import Account from eth_account.messages import encode_defunct API_KEY = "YOUR_API_KEY" wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"]) # 1. Get the message to sign. message_response = requests.get( "https://api.predict.fun/v1/auth/message", headers={"x-api-key": API_KEY} ) message = message_response.json()["data"]["message"] # 2. Sign it with the wallet. signed = wallet.sign_message(encode_defunct(text=message)) signature = signed.signature.to_0x_hex() # 3. Exchange the signature for a JWT. jwt_response = requests.post( "https://api.predict.fun/v1/auth", headers={"x-api-key": API_KEY}, json={"signer": wallet.address, "message": message, "signature": signature}, ) jwt_response.raise_for_status() token = jwt_response.json()["data"]["token"] print(token) ``` ### With a Predict account [#with-a-predict-account] A Predict account is the smart wallet that the web app creates for you. Its address is your deposit address, and it holds your funds. Your Privy wallet signs for it. You can export the Privy wallet's private key from your [account settings](https://predict.fun/account/settings). For a Predict account, two things change: - The signature must come from the SDK's Predict account signing. A plain wallet signature is rejected. - The `signer` is the Predict account's address, not the Privy wallet's. When you create the order builder with a Predict account, the SDK checks on-chain that the Privy wallet owns that account. If it doesn't, the builder fails with "The signer is not the owner of the Predict account". This also happens when the account was never set up in the web app. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; // the Predict account's address, also called its deposit address. const predictAccount = "PREDICT_ACCOUNT_ADDRESS"; // the Privy wallet, whose key you export from https://predict.fun/account/settings const privyWallet = new Wallet(process.env.PRIVY_WALLET_PRIVATE_KEY!); // the order builder signs for the Predict account with the Privy wallet. const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, privyWallet, { predictAccount, }); const messageResponse = await fetch("https://api.predict.fun/v1/auth/message", { headers: { "x-api-key": apiKey }, }); const { message } = (await messageResponse.json()).data; // a Predict account needs this signature. A plain wallet signature is rejected. const signature = await orderBuilder.signPredictAccountMessage(message); const jwtResponse = await fetch("https://api.predict.fun/v1/auth", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey }, body: JSON.stringify({ signer: predictAccount, message, signature }), }); if (!jwtResponse.ok) throw new Error(`Login failed: ${jwtResponse.status}`); const { token } = (await jwtResponse.json()).data; console.log(token); ``` ```python import os import requests from predict_sdk import ChainId, OrderBuilder, OrderBuilderOptions API_KEY = "YOUR_API_KEY" # the Predict account's address, also called its deposit address. PREDICT_ACCOUNT = "PREDICT_ACCOUNT_ADDRESS" # the order builder signs for the Predict account with the Privy wallet, whose key you # export from https://predict.fun/account/settings builder = OrderBuilder.make( ChainId.BNB_MAINNET, os.environ["PRIVY_WALLET_PRIVATE_KEY"], OrderBuilderOptions(predict_account=PREDICT_ACCOUNT), ) message_response = requests.get( "https://api.predict.fun/v1/auth/message", headers={"x-api-key": API_KEY} ) message = message_response.json()["data"]["message"] # a Predict account needs this signature. A plain wallet signature is rejected. signature = builder.sign_predict_account_message(message) jwt_response = requests.post( "https://api.predict.fun/v1/auth", headers={"x-api-key": API_KEY}, json={"signer": PREDICT_ACCOUNT, "message": message, "signature": signature}, ) jwt_response.raise_for_status() token = jwt_response.json()["data"]["token"] print(token) ``` Keep private keys on a server you control. Never put one in code that runs in a browser. ## Send the credentials [#send-the-credentials] Send both headers on requests that act for a wallet: ```json { "x-api-key": "YOUR_API_KEY", "Authorization": "Bearer YOUR_JWT" } ``` Each REST reference page lists the credentials its endpoint needs. For live order updates over the WebSocket, the JWT goes in the topic name instead. See [Order lifecycle](/trading/order-lifecycle#follow-your-orders-live). # Cancel orders (/trading/cancel-orders) You can stop an order in two ways: | | Remove through the API | Cancel on-chain | | :-------------------- | :--------------------------------------------------- | :----------------------------------------------------------------- | | How | [`POST /v1/orders/remove`](/api/orders/remove-orders) or [`/v1/orders/remove-by-hash`](/api/orders/remove-orders-by-hash) | `cancelOrders` on the exchange contract | | Cost | Free | A transaction, paid in BNB | | When it takes effect | Right away | Once the transaction confirms and Predict picks it up | | The signed order | Leaves Predict's book. Its signature stays valid on-chain. | Becomes invalid on-chain for good. | Both end with the order's status `CANCELLED`. Removing through the API is the usual way. Cancel on-chain when you need the signature itself to become unusable. ## Remove orders through the API [#remove-orders-through-the-api] Send up to 100 order IDs to `POST /v1/orders/remove`, or up to 100 order hashes to `POST /v1/orders/remove-by-hash`. Both need the API key and the JWT of the wallet that owns the orders. ```typescript const apiKey = "YOUR_API_KEY"; const jwt = "YOUR_JWT"; // from the Authentication guide const response = await fetch("https://api.predict.fun/v1/orders/remove", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${jwt}`, }, body: JSON.stringify({ data: { ids: ["ORDER_ID_1", "ORDER_ID_2"] } }), }); const result = await response.json(); if (!response.ok) throw new Error(`${result.error}: ${result.message}`); const { removed, noop, rejected } = result; console.log({ removed, noop, rejected }); ``` ```python import requests API_KEY = "YOUR_API_KEY" JWT = "YOUR_JWT" # from the Authentication guide response = requests.post( "https://api.predict.fun/v1/orders/remove", headers={"x-api-key": API_KEY, "Authorization": f"Bearer {JWT}"}, json={"data": {"ids": ["ORDER_ID_1", "ORDER_ID_2"]}}, ) result = response.json() if not response.ok: raise RuntimeError(f"{result['error']}: {result['message']}") print(result["removed"], result["noop"], result["rejected"]) ``` To remove by hash, send `{ "data": { "hashes": ["0x…"] } }` to `/v1/orders/remove-by-hash` instead. The response sorts the orders you sent into three lists: | List | What it means | | :--------- | :---------------------------------------------------------------------------------------------------- | | `removed` | The orders were removed. | | `noop` | Nothing changed, because the orders were already filled, removed or cancelled. | | `rejected` | The orders can't be removed yet, because their `removalLockedUntil` hasn't passed. Try again after it. | Only some up/down markets lock removal for a short time. The create response's `removalLockedUntil` tells you when. See [Place orders](/trading/place-orders#the-response). Every order in the request must belong to the logged-in wallet. If even one doesn't, including an ID or hash that doesn't exist, the whole request fails with `401` and nothing is removed. ## Cancel orders on-chain [#cancel-orders-on-chain] `cancelOrders` sends one transaction that marks the orders as cancelled in the exchange contract. The transaction must come from the orders' maker. With a Predict account, the SDK sends it through the account, and the Privy wallet pays the gas. Each market type has its own exchange contract, so cancel each group separately. [`GET /v1/orders`](/api/orders/get-orders) returns each order with its `isNegRisk` and `isYieldBearing`, which tell you its group. The TypeScript SDK checks that the orders belong to the exchange you chose, and throws `InvalidNegRiskConfig` if they don't. The Python SDK doesn't check. An order sent to the wrong exchange isn't cancelled, even though the transaction succeeds. ```typescript import { ChainId, OrderBuilder, type Order } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const jwt = "YOUR_JWT"; // from the Authentication guide const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // your open orders. Follow the response's cursor for more than one page. const response = await fetch( "https://api.predict.fun/v1/orders?status=OPEN&first=150", { headers: { "x-api-key": apiKey, Authorization: `Bearer ${jwt}` } }, ); const openOrders: { order: Order; isNegRisk: boolean; isYieldBearing: boolean; }[] = (await response.json()).data; // each market type has its own exchange, so cancel each group separately. for (const isNegRisk of [false, true]) { for (const isYieldBearing of [false, true]) { const orders = openOrders .filter( (o) => o.isNegRisk === isNegRisk && o.isYieldBearing === isYieldBearing, ) .map((o) => o.order); if (orders.length === 0) continue; const result = await orderBuilder.cancelOrders(orders, { isNegRisk, isYieldBearing, }); if (!result.success) throw new Error("A cancel transaction failed"); } } ``` ```python import os import requests from predict_sdk import ( CancelOrdersOptions, ChainId, Order, OrderBuilder, Side, SignatureType, ) API_KEY = "YOUR_API_KEY" JWT = "YOUR_JWT" # from the Authentication guide builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) def to_order(o): """The SDK's order from the order the API returns.""" return Order( salt=o["salt"], maker=o["maker"], signer=o["signer"], taker=o["taker"], token_id=o["tokenId"], maker_amount=o["makerAmount"], taker_amount=o["takerAmount"], expiration=str(o["expiration"]), nonce=o["nonce"], fee_rate_bps=o["feeRateBps"], side=Side(o["side"]), signature_type=SignatureType(o["signatureType"]), ) # your open orders. Follow the response's cursor for more than one page. open_orders = requests.get( "https://api.predict.fun/v1/orders", params={"status": "OPEN", "first": 150}, headers={"x-api-key": API_KEY, "Authorization": f"Bearer {JWT}"}, ).json()["data"] # each market type has its own exchange, so cancel each group separately. for is_neg_risk in (False, True): for is_yield_bearing in (False, True): orders = [ to_order(o["order"]) for o in open_orders if o["isNegRisk"] == is_neg_risk and o["isYieldBearing"] == is_yield_bearing ] if not orders: continue result = builder.cancel_orders( orders, CancelOrdersOptions( is_neg_risk=is_neg_risk, is_yield_bearing=is_yield_bearing ), ) if not result.success: raise RuntimeError("A cancel transaction failed") ``` If any order in the list is already filled or cancelled on-chain, the contract reverts the whole transaction. So send only orders that are still open. Predict sees the contract's cancel event and marks the orders `CANCELLED`. # Fees (/trading/fees) Takers pay a fee on each matched trade, and makers pay nothing. The fee formula, the fee schedule and the fee discount are explained in [Predict fees and limits](https://docs.predict.fun/the-basics/predict-fees-and-limits) in the product docs. This page covers what you need when you trade through the API. Placing and removing orders costs nothing. On-chain actions, such as cancelling an order or redeeming a position, cost only the BNB gas of their transaction. Converting positions in a negative-risk market can carry a fee of its own (see [Manage positions](/trading/manage-positions)). ## The market's fee fields [#the-markets-fee-fields] | Field on the market | Meaning | | :--- | :--- | | `feeRateBps` | The taker fee rate, in basis points (100 bps is 1%). | | `makerFeeBps` | The maker fee rate. It is always `0`. | Read both from [Get market by ID](/api/markets/get-market-by-id) or from the market objects inside [Get categories](/api/categories/get-categories). Rates can differ between markets, so read `feeRateBps` from each market you trade. The taker is the order that matches against resting orders. A resting limit order that gets filled is the maker, and it pays no fee. ## How the fee is taken [#how-the-fee-is-taken] The exchange contract takes the fee on-chain, from what the taker receives: - **Buy**: you receive fewer shares. The fee's value is converted to shares at the trade's price. - **Sell**: you receive less USDT. ## The `feeRateBps` you sign [#the-feeratebps-you-sign] Every order carries a `feeRateBps` field, which you sign with the rest of the order. It is the most you agree to pay, not the rate you are charged. - Set it to the market's `feeRateBps`. It must be at least the market's rate less your referral discount, if you have one. A lower rate is rejected with `create_order_fee_rate_too_low`. - When you place the order, Predict records its rate: the market's `feeRateBps` less your referral discount. - You are charged the lower of the market's current rate and the rate recorded for the order. Signing a rate above the recorded one doesn't change what you pay. - The exchange contract caps the signed rate at 1000 bps (10%). An order signed above that fails the exchange's check with `create_order_contract_fee_too_high`. On-chain, the exchange first charges the signed rate. The fee module then refunds the difference to you in the same transaction, so you never pay more than the charged rate. ```typescript // sign the market's own rate, read from GET /v1/markets/{id}. const order = orderBuilder.buildOrder("LIMIT", { side: Side.BUY, tokenId: market.outcomes[0].onChainId, makerAmount, takerAmount, feeRateBps: market.feeRateBps, }); ``` See [Place orders](/trading/place-orders) for the full order flow. ## See the fees you paid [#see-the-fees-you-paid] [Get order match events](/api/orders/get-order-match-events) returns each match with a `fee` object on the taker and on every maker: | Field | Meaning | | :--- | :--- | | `fee.amount` | The fee, in wei. | | `fee.type` | `SHARES` for a fee taken in outcome tokens (buys), `COLLATERAL` for a fee taken in USDT (sells). | ## Maker rebates and referral fees [#maker-rebates-and-referral-fees] Part of a taker's fee can be paid out to the makers of the orders it filled, and to whoever referred the taker. Both come out of the taker fee, so the taker never pays more. The product docs explain both programs in [Maker rebates](https://docs.predict.fun/knowledge-base/maker-rebates) and [Invites](https://docs.predict.fun/knowledge-base/invites), which also covers the referral discount. Two endpoints list what your wallet has earned. Both need your API key and a JWT (see [Authentication](/trading/authentication)). | Endpoint | Lists | | :--- | :--- | | [`GET /v1/maker-rebates`](/api/fee-events/get-maker-rebate-events) | Rebates earned as a maker. | | [`GET /v1/referral-fees`](/api/fee-events/get-referral-fee-events) | Fees earned from the trades of wallets you referred. | Query parameters, the same for both: | Parameter | Meaning | | :--- | :--- | | `fee_in_collateral` | Required. `true` lists amounts paid in USDT, `false` lists amounts paid in outcome tokens. | | `first` | Page size. Default 50, maximum 150. | | `after` | The `cursor` from the previous page. | | `occurred_after`, `occurred_before` | Unix timestamps in milliseconds. Both bounds are exclusive. Send them again with every page. | Each row has `transactionHash`, `createdAt`, `takerFeeTokenId` (`0` when the amount is USDT), `amount` in wei, `marketQuestion`, `marketOutcomeName` and `price`. Results are newest first. # Manage positions (/trading/manage-positions) A position is a balance of one outcome's tokens. Positions live on-chain, so managing them means sending a transaction from your wallet. The SDKs do this for you through the `OrderBuilder` you already use for orders. Each operation costs BNB gas, so keep some BNB in the wallet that sends it. [Positions and outcome tokens](/concepts/positions) explains what each operation does. This page shows how to run them. ## Find your positions [#find-your-positions] Two endpoints list a wallet's positions: | Endpoint | Returns | | :--- | :--- | | [Get positions](/api/positions/get-positions) | Your own positions. Needs a JWT (see [Authentication](/trading/authentication)). | | [Get positions by address](/api/positions/get-positions-by-address) | Any wallet's positions. Needs only your API key. | Each position has the `market`, the `outcome` it holds and its `amount` in wei. It also has `valueUsd`, `averageBuyPriceUsd` and `pnlUsd`. The fields the operations below need are: | Operation input | Where to find it | | :--- | :--- | | `conditionId` | `market.conditionId` | | `indexSet` | `outcome.indexSet`: `1` for Yes, `2` for No | | `isNegRisk`, `isYieldBearing` | `market.isNegRisk`, `market.isYieldBearing` | | amount | `amount`, in wei | Filter with `isResolved=true` to list positions in resolved markets. Positions in outcomes that lost are left out unless you add `includeLost=true`. Pages hold up to 100 positions. Pass the response's `cursor` as `after` to get the next page. ## Approvals [#approvals] Each operation moves tokens through a contract that needs your approval first. Splitting spends USDT, and merging, redeeming and converting move outcome tokens. The SDKs can set exactly the approvals one operation needs. See [Approvals](/trading/approvals). ## Redeem winning positions [#redeem-winning-positions] Once a market resolves, each share of the winning outcome redeems for 1 USDT. A binary market can also resolve 50-50, which pays 0.50 USDT per share of either outcome. Redeeming burns the tokens and pays out the USDT. There is no deadline to redeem. See [Resolution](/concepts/resolution) for when a market resolves. This example redeems every winning position of your wallet: ```typescript // Redeem every winning position of your wallet in resolved markets. import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // positions in resolved markets. Lost positions are left out by default. const response = await fetch( `https://api.predict.fun/v1/positions/${wallet.address}?isResolved=true&first=100`, { headers: { "x-api-key": apiKey } }, ); if (!response.ok) throw new Error(`Predict API error: ${response.status}`); const { data: positions } = await response.json(); for (const { market, outcome, amount } of positions) { const result = await orderBuilder.redeemPositions({ conditionId: market.conditionId, indexSet: outcome.indexSet, isNegRisk: market.isNegRisk, isYieldBearing: market.isYieldBearing, // negative-risk markets redeem a stated amount, the whole position here. amount: market.isNegRisk ? BigInt(amount) : undefined, }); console.log(market.question, result.success ? "redeemed" : result.cause); } ``` ```python # Redeem every winning position of your wallet in resolved markets. import os import requests from eth_account import Account from predict_sdk import ChainId, OrderBuilder API_KEY = "YOUR_API_KEY" wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"]) builder = OrderBuilder.make(ChainId.BNB_MAINNET, wallet) # positions in resolved markets. Lost positions are left out by default. response = requests.get( f"https://api.predict.fun/v1/positions/{wallet.address}", params={"isResolved": "true", "first": 100}, headers={"x-api-key": API_KEY}, ) response.raise_for_status() for position in response.json()["data"]: market, outcome = position["market"], position["outcome"] result = builder.redeem_positions( market["conditionId"], outcome["indexSet"], # negative-risk markets redeem a stated amount, the whole position here. int(position["amount"]) if market["isNegRisk"] else None, is_neg_risk=market["isNegRisk"], is_yield_bearing=market["isYieldBearing"], ) print(market["question"], "redeemed" if result.success else result.cause) ``` In a negative-risk market, you redeem a stated amount. In other markets, redeeming always redeems your whole balance of that outcome, so the amount is left out. ## Merge positions [#merge-positions] Merging turns equal amounts of Yes and No back into USDT: 1 Yes share plus 1 No share returns 1 USDT. It doesn't need the market to resolve, so it is how you exit a position you hold on both sides. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // merge 10 Yes and 10 No shares back into 10 USDT. const result = await orderBuilder.mergePositions({ conditionId: "CONDITION_ID", // the market's conditionId amount: 10n * 10n ** 18n, // 10 shares of each outcome, in wei isNegRisk: false, isYieldBearing: true, }); if (!result.success) throw result.cause ?? new Error("Merge failed"); ``` ```python import os from predict_sdk import ChainId, OrderBuilder builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # merge 10 Yes and 10 No shares back into 10 USDT. result = builder.merge_positions( "CONDITION_ID", # the market's conditionId 10 * 10**18, # 10 shares of each outcome, in wei is_neg_risk=False, is_yield_bearing=True, ) if not result.success: raise RuntimeError("Merge failed") from result.cause ``` ## Split USDT into positions [#split-usdt-into-positions] Splitting is the reverse of merging. Each USDT you split becomes 1 Yes share and 1 No share. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // split 10 USDT into 10 Yes and 10 No shares. const result = await orderBuilder.splitPositions({ conditionId: "CONDITION_ID", // the market's conditionId amount: 10n * 10n ** 18n, // 10 USDT, in wei isNegRisk: false, isYieldBearing: true, }); if (!result.success) throw result.cause ?? new Error("Split failed"); ``` ```python import os from predict_sdk import ChainId, OrderBuilder builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # split 10 USDT into 10 Yes and 10 No shares. result = builder.split_positions( "CONDITION_ID", # the market's conditionId 10 * 10**18, # 10 USDT, in wei is_neg_risk=False, is_yield_bearing=True, ) if not result.success: raise RuntimeError("Split failed") from result.cause ``` ## Convert positions [#convert-positions] Converting works only in negative-risk categories, where several markets share one winner. It burns No positions and gives back the equivalent in Yes positions and USDT. For each `amount` you convert, you give `amount` No shares of each selected market, and you receive: - `amount` Yes shares of every other market in the category. - `amount × (number of selected No positions − 1)` USDT. If the category has a conversion fee, it is taken from both the Yes shares and the USDT. A category needs more than one market to convert. See [Negative-risk markets](/concepts/negative-risk) for why this holds its value. Two inputs come from the API: - `negRiskOnChainId`: the category's field of that name, from [Get categories](/api/categories/get-categories). It is a 32-byte hex string, not the numeric `id` the API uses for categories and markets. - `indexSet`: a bitmask that selects which No positions to convert. For each one, set the bit at that market's `questionIndex`. Markets carry their `questionIndex` in categories and positions. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // convert the No positions of the questions at index 0 and 2. const questionIndexes = [0, 2]; const indexSet = questionIndexes.reduce( (set, index) => set | (1n << BigInt(index)), 0n, ); const result = await orderBuilder.convertPositions({ negRiskOnChainId: "NEG_RISK_ON_CHAIN_ID", // the category's negRiskOnChainId indexSet, amount: 10n * 10n ** 18n, // 10 shares of each No position, in wei isYieldBearing: true, }); if (!result.success) throw result.cause ?? new Error("Convert failed"); ``` ```python import os from predict_sdk import ChainId, OrderBuilder builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # convert the No positions of the questions at index 0 and 2. index_set = sum(1 << index for index in [0, 2]) result = builder.convert_positions( "NEG_RISK_ON_CHAIN_ID", # the category's negRiskOnChainId index_set, 10 * 10**18, # 10 shares of each No position, in wei is_yield_bearing=True, ) if not result.success: raise RuntimeError("Convert failed") from result.cause ``` ## Check your USDT balance [#check-your-usdt-balance] ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { formatUnits, Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // the wallet's USDT balance, in wei. USDT on BNB Chain has 18 decimals. const balance = await orderBuilder.balanceOf(); console.log(`${formatUnits(balance, 18)} USDT`); ``` ```python import os from predict_sdk import ChainId, OrderBuilder builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # the wallet's USDT balance, in wei. USDT on BNB Chain has 18 decimals. balance = builder.balance_of() print(f"{balance / 10**18} USDT") ``` ## Call the contracts directly [#call-the-contracts-directly] The `OrderBuilder` exposes every contract it uses. You can call any contract function through it, with the wallet you gave to `OrderBuilder.make` as the sender. ```typescript import { ChainId, OrderBuilder } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // the contracts exist only when OrderBuilder.make gets a signer. if (!orderBuilder.contracts) throw new Error("No signer was given to OrderBuilder.make"); // each entry has the ethers `contract` and its `codec`, an ethers Interface. const { contract: usdt } = orderBuilder.contracts.USDT; const balance = await usdt.balanceOf(wallet.address); ``` ```python import os from eth_account import Account from predict_sdk import ChainId, OrderBuilder wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"]) builder = OrderBuilder.make(ChainId.BNB_MAINNET, wallet) # the contracts exist only when OrderBuilder.make gets a signer. if builder.contracts is None: raise RuntimeError("No signer was given to OrderBuilder.make") # each attribute is a web3.py contract. usdt = builder.contracts.usdt balance = usdt.functions.balanceOf(wallet.address).call() ``` The SDKs also export the contract addresses and ABIs, so you can use them with your own client: ```typescript import { // supported chains and contract addresses ChainId, AddressesByChainId, // ABIs CTFExchangeAbi, NegRiskCtfExchangeAbi, NegRiskAdapterAbi, ConditionalTokensAbi, YieldBearingConditionalTokensAbi, ERC20Abi, // contract types type CTFExchange, type ConditionalTokens, type NegRiskAdapter, type NegRiskCtfExchange, type ERC20, } from "@predictdotfun/sdk"; console.log(AddressesByChainId[ChainId.BnbMainnet].CTF_EXCHANGE); ``` ```python from predict_sdk import ( # supported chains and contract addresses ADDRESSES_BY_CHAIN_ID, # ABIs CONDITIONAL_TOKENS_ABI, CTF_EXCHANGE_ABI, ERC20_ABI, NEG_RISK_ADAPTER_ABI, NEG_RISK_CTF_EXCHANGE_ABI, YIELD_BEARING_CONDITIONAL_TOKENS_ABI, ChainId, ) print(ADDRESSES_BY_CHAIN_ID[ChainId.BNB_MAINNET].CTF_EXCHANGE) ``` If you call the contracts without the SDKs, these are the functions behind each operation. The contract addresses are on [Deployed contracts](/resources/deployed-contracts). | Operation | Standard markets: `ConditionalTokens` or `YieldBearingConditionalTokens` | Negative-risk markets: `NegRiskAdapter` or `YieldBearingNegRiskAdapter` | | :--- | :--- | :--- | | Split | `splitPosition(USDT, 0x0, conditionId, [1, 2], amount)` | `splitPosition(conditionId, amount)` | | Merge | `mergePositions(USDT, 0x0, conditionId, [1, 2], amount)` | `mergePositions(conditionId, amount)` | | Redeem | `redeemPositions(USDT, 0x0, conditionId, [indexSet])` | `redeemPositions(conditionId, [yesAmount, noAmount])` | | Convert | Not available | `convertPositions(negRiskOnChainId, indexSet, amount)` | `0x0` is the zero `bytes32` parent collection, and `[1, 2]` are the index sets of Yes and No. To redeem one outcome in a negative-risk market, set the other outcome's amount to `0`. All amounts are in wei. USDT on BNB Chain has 18 decimals. # Market making (/trading/market-making) A market maker keeps resting limit orders on both sides of a market's order book. This page covers what is specific to that: quoting both outcomes, keeping orders from taking liquidity, staying in sync, and the limits that apply. It assumes you can already [place orders](/trading/place-orders). ## Quote both sides [#quote-both-sides] Each market has one order book, priced in the Yes outcome. A bid for No at price `p` sits in the book as an ask for Yes at `1 − p`. So a two-sided quote can use either outcome: | You want to | Equivalent order | | :--- | :--- | | Bid for Yes at 0.48 | Buy Yes at 0.48 | | Offer Yes at 0.52 | Sell Yes at 0.52, or buy No at 0.48 | Selling Yes needs Yes shares in your wallet. Buying No only needs USDT. That makes buying No the way to quote the ask side without holding inventory. See [Prices and order book](/concepts/order-book) for how the two outcomes map onto one book. ## Keep orders from taking liquidity [#keep-orders-from-taking-liquidity] Set `isPostOnly: true` on a `LIMIT` order to make sure it only ever rests on the book. If the order would match a resting order at the moment it arrives, it is not accepted at all. You get an `orderNotAccepted` [wallet event](/websocket/topics/wallet-events) with the reason `rejectedPostOnly`, and the order ends as `CANCELLED`. Post-only orders have two more advantages on markets with a speed bump, such as the up/down markets: - Other orders wait a short delay before they reach the book. Post-only orders skip it. - Other orders cannot be removed until their `removalLockedUntil` time. Post-only orders can be removed right away. ## Avoid trading with yourself [#avoid-trading-with-yourself] By default, your own orders can match each other. Set `selfTradePrevention` on an order to stop that. With any of the three modes, the order never matches orders from the same wallet. The mode decides what happens when it would have: | `selfTradePrevention` | Effect when the order would match your own resting orders | | :--- | :--- | | `CANCEL_TAKER` | The incoming order is cancelled. Your resting orders stay. | | `CANCEL_MAKER` | Your crossing resting orders are cancelled. The incoming order still matches anyone else's orders. | | `CANCEL_BOTH` | Both the incoming order and your crossing resting orders are cancelled. | Cancelled orders arrive as `orderCancelled` wallet events with the reason `selfTradePrevention`. ## Update quotes by replacing them [#update-quotes-by-replacing-them] Orders cannot be edited. To move a quote, remove the old order and place a new one. Removal is off-chain and free, and you can remove up to 100 orders per request. See [Cancel orders](/trading/cancel-orders). ## Stay in sync [#stay-in-sync] Two WebSocket topics give you everything you need to track your quotes. Subscribe to both on one connection. | Topic | What it tells you | | :--- | :--- | | [`predictOrderbook/{marketId}`](/websocket/topics/order-book) | The full book each time it changes, including what is waiting to settle. | | [`predictWalletEvents/{jwt}`](/websocket/topics/wallet-events) | Your own orders: accepted, not accepted, matched, cancelled, expired, and each settlement transaction. | To follow prices in markets you watch but don't quote, you don't need their whole books. Subscribe to their [top of book topic](/websocket/topics/top-of-book), or poll [Get quotes for multiple markets](/api/markets/get-quotes-for-multiple-markets), which covers up to 500 markets per request. Answer every heartbeat, or the server closes the connection (see [Stay connected](/websocket/how-tos/stay-connected)). Before a planned restart, the server sends a [reconnect notice](/websocket/connection/reconnect-notice). Reconnect when you get it, subscribe again, and then fetch your open orders with [Get orders](/api/orders/get-orders) to catch anything you missed. ### Pending settlements [#pending-settlements] A match is final only once its transaction settles on-chain. Until then, the matched amounts are reserved and cannot match again. The order book shows them in `settlementsPending`: | Field | Meaning | | :--- | :--- | | `settlementsPending.asks` | `[price, amount]` pairs on the ask side, with prices on the Yes axis. | | `settlementsPending.bids` | The same for the bid side. | Each `amount` is the USDT value at that price that has matched and is waiting to settle. `lastOrderSettled` is the most recent order whose settlement confirmed. ## Market states [#market-states] The market's `tradingStatus` tells you what the market accepts right now. The [`predictTradingStatus/{marketId}`](/websocket/topics/trading-status) topic pushes each change. `tradingStatus` doesn't show a pause. A market whose `status` is `PAUSED` can still show `OPEN`, but it rejects new orders with `create_order_market_not_trading`. So watch the market's `status` too. | `tradingStatus` | What you can do | | :--- | :--- | | `OPEN` | Everything. | | `MATCHING_NOT_ENABLED` | Only post-only `LIMIT` orders are accepted, and they rest without matching. Other `LIMIT` orders are rejected with `create_order_trading_disabled`, and `MARKET` orders with `create_order_market_orders_disabled`. Matching starts when the status turns `OPEN`. | | `CANCEL_ONLY` | New orders are rejected, but you can still remove your open orders. | | `CLOSED` | The market is resolved or removed. It accepts no new orders. | ## Rewards fields [#rewards-fields] Some markets reward resting liquidity. [How to earn Predict Points](https://docs.predict.fun/the-basics/how-to-earn-points) and [Maker rebates](https://docs.predict.fun/knowledge-base/maker-rebates) in the product docs explain the programs. These market fields describe a market's rewards: | Field | Meaning | | :--- | :--- | | `rewards.current` | The reward period active now, or `null` when the market is not accruing rewards. | | `rewards.schedule` | Every past and future reward period, each with `hourlyRate` (reward points per hour), `startsAt` and `endsAt`. | | `spreadThreshold` | How far apart, in USDT, the best bid and best ask can be before the market stops counting towards rewards. | | `shareThreshold` | How many shares an order needs before it counts towards rewards. | [Get markets](/api/markets/get-markets) can filter by `hasActiveRewards`. ## Rate limits [#rate-limits] Order placement and removal count against the trading limit, which is higher than the limit for other routes. Keys from the developer console also have a per-second cap on order operations. See [Rate limits](/resources/rate-limits) for the numbers and headers. ## Checklist [#checklist] - Use post-only orders for quotes, so a stale price cannot take liquidity. - Set `selfTradePrevention` if you quote both sides from the same wallet. - Remove and replace orders to move quotes. Track each order's `hash` so you can remove it later. - Subscribe to the order book and your wallet events, and answer heartbeats. - After a reconnect, subscribe again and reconcile your open orders through the REST API. - Watch `tradingStatus` and `status`, and stop quoting a market that leaves `OPEN` or is `PAUSED`. - Keep enough USDT and allowance for all your open bids. The API checks a new order only against your open orders on the same outcome and side in that market. It doesn't stop you from committing more than you hold across outcomes or markets, and an order your funds can't cover fails when it settles. # Order lifecycle (/trading/order-lifecycle) After the API accepts an order, the order book tries to match it. Each match is settled on-chain by the exchange contract. An order ends in one of four final statuses. ## Statuses [#statuses] [`GET /v1/orders`](/api/orders/get-orders) and [`GET /v1/orders/{hash}`](/api/orders/get-order-by-hash) return each order's `status`: | Status | What it means | Final | | :------------ | :------------------------------------------------------------------------------------------------------------------ | :---- | | `OPEN` | The order was accepted and still has shares to fill. A limit order rests on the book. | No | | `FILLED` | The order filled completely. A market order is `FILLED` after any fill, because the rest of it is cancelled. | Yes | | `CANCELLED` | The order was cancelled. See the causes below. | Yes | | `EXPIRED` | The order reached its expiration before it filled. | Yes | | `INVALIDATED` | Predict removed the order because it can't be matched or settled any more, for example after a failed settlement. | Yes | An order is cancelled when: - You removed it through the API, or cancelled it on-chain. See [Cancel orders](/trading/cancel-orders). - A market order found nothing to match within its slippage, or a fill-or-kill order couldn't fill completely. - A post-only order would have matched right away. - Self-trade prevention cancelled it. - A check that runs after the API responds found your balance or allowance too low. `amount` is the order's size, and `amountFilled` is how much of it has been settled, both in wei. ## Matched and settled [#matched-and-settled] A match and a settlement are two steps: 1. **Matched.** The order book pairs your order with others. It locks the matched shares, so no other order can take them, and submits a transaction to the exchange contract. 2. **Settled.** The transaction succeeds on-chain. Now the shares and USDT have moved, and `amountFilled` grows. Between the two steps, the locked amount shows in the order book's `settlementsPending`. See [Prices and order book](/concepts/order-book). A settlement can fail. What happens next depends on why it failed: - Often the locked liquidity goes back to the book, and the orders can match again. - An order that caused the failure, for example because its balance changed, is removed. Its status becomes `INVALIDATED`. - A market order whose settlement fails is cancelled. ## Follow your orders live [#follow-your-orders-live] The WebSocket topic `predictWalletEvents/{jwt}` streams events for every order of the wallet that the JWT belongs to. The server checks the JWT when you subscribe. So after a reconnect, subscribe again with a JWT that hasn't expired. See [Authentication](/trading/authentication) for getting one. | Event | When you get it | | :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | `orderAccepted` | The order was placed in the book. | | `orderNotAccepted` | The order was rejected. `reason` is `noMarketMatch`, `rejectedDuplicate` or `rejectedPostOnly`. | | `orderMarketMatchPlan` | How a market order will fill: `plannedFill`, and the `remainderCancelled` that won't fill. | | `orderTransactionSubmitted` | The order matched, and its settlement was submitted on-chain. An order that matches your own order gets one as taker and one as maker. | | `orderTransactionSuccess` | The settlement succeeded. | | `orderTransactionFailed` | The settlement failed. | | `orderCancelled` | The order was cancelled or invalidated. `reason` is `selfTradePrevention` when self-trade prevention cancelled it. | | `orderExpired` | The order reached its expiration. | Every event has the order's `orderId` and `orderHash`, a `timestamp` in epoch milliseconds, and `details` such as the `marketId`, the `outcome`, the `price` and the `quantityFilled`. The [wallet events reference](/websocket/topics/wallet-events) has every field. [Follow your orders](/websocket/how-tos/follow-your-orders) shows how to subscribe in TypeScript and Python, print each event, and recover after a reconnect. # Place orders (/trading/place-orders) An order is an EIP-712 message that your wallet signs. You build and sign it with the SDK, then send it to [`POST /v1/orders`](/api/orders/create-an-order). The order book matches it off-chain, and the exchange contract settles each match on-chain. ## Before you start [#before-you-start] You need three things: - An API key and a JWT for the wallet. See [Authentication](/trading/authentication). - The [approvals](/trading/approvals) for the market's exchange. - The market itself, from [`GET /v1/markets/{id}`](/api/markets/get-market-by-id). These are the fields an order uses: | Field | What it's for | | :---------------------------- | :---------------------------------------------------------------------------------------------------- | | `outcomes[].onChainId` | The order's `tokenId`, which is the outcome you buy or sell. On a binary market, `indexSet` 1 is Yes and 2 is No. | | `feeRateBps` | The fee rate you sign into the order. See [Fees](/trading/fees). | | `isNegRisk`, `isYieldBearing` | Which exchange settles the order, so which contract the order is signed for. | | `decimalPrecision` | How many decimals a price can have: `2` for steps of 0.01, `3` for steps of 0.001. | | `tradingStatus` | Whether the market accepts orders right now. See [Markets](/concepts/markets). | ## Limit and market orders [#limit-and-market-orders] | | `LIMIT` | `MARKET` | | :----------------- | :--------------------------------------------------------------- | :----------------------------------------------------------------------- | | Price | The price you set. | The best prices in the book, within your slippage. | | The unfilled part | Rests on the book until it fills, you remove it, or it expires. | Is cancelled at once. A market order never rests on the book. | | Options | `isPostOnly`, `selfTradePrevention` | `isFillOrKill`, `slippageBps`, `isMinAmountOut`, `reservedBalancePolicy`, `selfTradePrevention` | ## Place a limit order [#place-a-limit-order] `getLimitOrderAmounts` turns a price and a quantity into the order's amounts. The example buys 10 shares of the market's first outcome at 0.40 USDT each. ```typescript import { ChainId, OrderBuilder, Side } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const jwt = "YOUR_JWT"; // from the Authentication guide const marketId = "MARKET_ID"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); // the market's fee, its outcomes, and which exchange it trades on. const marketResponse = await fetch( `https://api.predict.fun/v1/markets/${marketId}`, { headers: { "x-api-key": apiKey }, }, ); const market = (await marketResponse.json()).data; // buy 10 shares of the first outcome at 0.40 USDT each. const { pricePerShare, makerAmount, takerAmount } = orderBuilder.getLimitOrderAmounts({ side: Side.BUY, pricePerShareWei: 400000000000000000n, // 0.40 USDT quantityWei: 10000000000000000000n, // 10 shares }); const order = orderBuilder.buildOrder("LIMIT", { side: Side.BUY, tokenId: market.outcomes[0].onChainId, makerAmount, takerAmount, feeRateBps: market.feeRateBps, }); const typedData = orderBuilder.buildTypedData(order, { isNegRisk: market.isNegRisk, isYieldBearing: market.isYieldBearing, }); const signedOrder = await orderBuilder.signTypedDataOrder(typedData); const hash = orderBuilder.buildTypedDataHash(typedData); const response = await fetch("https://api.predict.fun/v1/orders", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${jwt}`, }, body: JSON.stringify({ data: { order: { ...signedOrder, hash }, pricePerShare: pricePerShare.toString(), strategy: "LIMIT", }, }), }); const result = await response.json(); if (!response.ok) throw new Error(`${result.error}: ${result.message}`); console.log(result.data); // { code, orderId, orderHash, removalLockedUntil } ``` ```python import os import requests from predict_sdk import BuildOrderInput, ChainId, LimitHelperInput, OrderBuilder, Side API_KEY = "YOUR_API_KEY" JWT = "YOUR_JWT" # from the Authentication guide MARKET_ID = "MARKET_ID" builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) # the market's fee, its outcomes, and which exchange it trades on. market = requests.get( f"https://api.predict.fun/v1/markets/{MARKET_ID}", headers={"x-api-key": API_KEY} ).json()["data"] # buy 10 shares of the first outcome at 0.40 USDT each. amounts = builder.get_limit_order_amounts( LimitHelperInput( side=Side.BUY, price_per_share_wei=400000000000000000, # 0.40 USDT quantity_wei=10000000000000000000, # 10 shares ) ) order = builder.build_order( "LIMIT", BuildOrderInput( side=Side.BUY, token_id=market["outcomes"][0]["onChainId"], maker_amount=amounts.maker_amount, taker_amount=amounts.taker_amount, fee_rate_bps=market["feeRateBps"], ), ) typed_data = builder.build_typed_data( order, is_neg_risk=market["isNegRisk"], is_yield_bearing=market["isYieldBearing"] ) signed_order = builder.sign_typed_data_order(typed_data) response = requests.post( "https://api.predict.fun/v1/orders", headers={"x-api-key": API_KEY, "Authorization": f"Bearer {JWT}"}, json={ "data": { # the typed data's message holds the order's fields as the API names them. "order": { **typed_data.message, "hash": builder.build_typed_data_hash(typed_data), "signature": signed_order.signature, }, "pricePerShare": str(amounts.price_per_share), "strategy": "LIMIT", } }, ) result = response.json() if not response.ok: raise RuntimeError(f"{result['error']}: {result['message']}") print(result["data"]) # code, orderId, orderHash, removalLockedUntil ``` A limit order has no expiration unless you set one. To set one, pass `expiresAt` (a `Date`) to `buildOrder` in TypeScript, or `expires_at` (a `datetime`) to `BuildOrderInput` in Python. ## Place a market order [#place-a-market-order] A market order's amounts come from the current book, so fetch it from [`GET /v1/markets/{id}/orderbook`](/api/markets/get-market-orderbook) right before you order. `getMarketOrderAmounts` walks the book and returns the amounts, plus the `pricePerShare` to send. You can size a market order in two ways: - By quantity, with `quantityWei`. This works for buys and sells. - By value, with `valueWei`, the most USDT to spend. This works for buys only. The example spends up to 5 USDT on the market's first outcome and accepts up to 1% slippage. ```typescript import { ChainId, OrderBuilder, Side } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const jwt = "YOUR_JWT"; // from the Authentication guide const marketId = "MARKET_ID"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); const headers = { "x-api-key": apiKey }; const market = ( await ( await fetch(`https://api.predict.fun/v1/markets/${marketId}`, { headers }) ).json() ).data; // the amounts come from the current book, so fetch it right before you order. const book = ( await ( await fetch(`https://api.predict.fun/v1/markets/${marketId}/orderbook`, { headers, }) ).json() ).data; // spend up to 5 USDT on the first outcome, accepting up to 1% slippage. const amounts = orderBuilder.getMarketOrderAmounts( { side: Side.BUY, valueWei: 5000000000000000000n, // 5 USDT slippageBps: 100n, // 1% isMinAmountOut: true, }, book, ); const order = orderBuilder.buildOrder("MARKET", { side: Side.BUY, tokenId: market.outcomes[0].onChainId, makerAmount: amounts.makerAmount, takerAmount: amounts.takerAmount, feeRateBps: market.feeRateBps, }); const typedData = orderBuilder.buildTypedData(order, { isNegRisk: market.isNegRisk, isYieldBearing: market.isYieldBearing, }); const signedOrder = await orderBuilder.signTypedDataOrder(typedData); const hash = orderBuilder.buildTypedDataHash(typedData); const response = await fetch("https://api.predict.fun/v1/orders", { method: "POST", headers: { ...headers, "Content-Type": "application/json", Authorization: `Bearer ${jwt}`, }, body: JSON.stringify({ data: { order: { ...signedOrder, hash }, pricePerShare: amounts.pricePerShare.toString(), strategy: "MARKET", // send the slippage settings the amounts were computed with. slippageBps: amounts.slippageBps.toString(), isMinAmountOut: amounts.isMinAmountOut, }, }), }); const result = await response.json(); if (!response.ok) throw new Error(`${result.error}: ${result.message}`); console.log(result.data); ``` ```python import os import requests from predict_sdk import ( Book, BuildOrderInput, ChainId, MarketHelperValueInput, OrderBuilder, Side, ) API_KEY = "YOUR_API_KEY" JWT = "YOUR_JWT" # from the Authentication guide MARKET_ID = "MARKET_ID" HEADERS = {"x-api-key": API_KEY} builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) market = requests.get( f"https://api.predict.fun/v1/markets/{MARKET_ID}", headers=HEADERS ).json()["data"] # the amounts come from the current book, so fetch it right before you order. data = requests.get( f"https://api.predict.fun/v1/markets/{MARKET_ID}/orderbook", headers=HEADERS ).json()["data"] book = Book( market_id=data["marketId"], update_timestamp_ms=data["updateTimestampMs"], asks=[tuple(level) for level in data["asks"]], bids=[tuple(level) for level in data["bids"]], ) # spend up to 5 USDT on the first outcome, accepting up to 1% slippage. amounts = builder.get_market_order_amounts( MarketHelperValueInput( side=Side.BUY, value_wei=5000000000000000000, # 5 USDT slippage_bps=100, # 1% is_min_amount_out=True, ), book, ) order = builder.build_order( "MARKET", BuildOrderInput( side=Side.BUY, token_id=market["outcomes"][0]["onChainId"], maker_amount=amounts.maker_amount, taker_amount=amounts.taker_amount, fee_rate_bps=market["feeRateBps"], ), ) typed_data = builder.build_typed_data( order, is_neg_risk=market["isNegRisk"], is_yield_bearing=market["isYieldBearing"] ) signed_order = builder.sign_typed_data_order(typed_data) response = requests.post( "https://api.predict.fun/v1/orders", headers={**HEADERS, "Authorization": f"Bearer {JWT}"}, json={ "data": { "order": { **typed_data.message, "hash": builder.build_typed_data_hash(typed_data), "signature": signed_order.signature, }, "pricePerShare": str(amounts.price_per_share), "strategy": "MARKET", # send the slippage settings the amounts were computed with. "slippageBps": str(amounts.slippage_bps), "isMinAmountOut": amounts.is_min_amount_out, } }, ) result = response.json() if not response.ok: raise RuntimeError(f"{result['error']}: {result['message']}") print(result["data"]) ``` The SDK sets a market order to expire 5 minutes after you build it, so build it right before you send it. ### Slippage [#slippage] Slippage is a tolerance in basis points, where 100 is 1%. It changes the amounts the SDK signs: - **Buy with `isMinAmountOut`**: `makerAmount` is the expected cost, and `takerAmount` drops by the slippage. So you spend at most what you planned and accept a few shares less. This lets you spend your whole balance. - **Buy without `isMinAmountOut`**: `makerAmount` grows by the slippage, so you may pay more for the same shares. - **Sell**: `takerAmount` drops by the slippage, so you accept a little less USDT. The API applies slippage only when you send the `slippageBps` and `isMinAmountOut` the SDK returned. If the book moves beyond your slippage before the order matches, the order is cancelled without a fill. You get an `orderNotAccepted` event with the reason `noMarketMatch`. See [Order lifecycle](/trading/order-lifecycle). ## The request [#the-request] The body wraps the signed order and its settings in `data`: ```json { "data": { "order": { "hash": "0x…", "salt": "…", "maker": "0x…", "signer": "0x…", "taker": "0x0000000000000000000000000000000000000000", "tokenId": "…", "makerAmount": "4000000000000000000", "takerAmount": "10000000000000000000", "expiration": "4102444800", "nonce": "0", "feeRateBps": "200", "side": 0, "signatureType": 0, "signature": "0x…" }, "pricePerShare": "400000000000000000", "strategy": "LIMIT" } } ``` The `order` fields are the signed EIP-712 order. Send them exactly as the SDK returns them, plus its `hash`. For a wallet, `maker` and `signer` are the wallet's address. For a Predict account, the SDK sets both to the account's address. `side` is `0` to buy and `1` to sell. These are the settings next to the order: | Field | Strategy | What it does | | :---------------------- | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pricePerShare` | Both | The price, as a wei string. Required. | | `strategy` | Both | `LIMIT` or `MARKET`. Required. | | `isPostOnly` | `LIMIT` only | The order only adds liquidity. If it would match right away, it isn't placed in the book, and you get an `orderNotAccepted` event with the reason `rejectedPostOnly`. | | `isFillOrKill` | `MARKET` only | The order must fill completely, or it's cancelled without a fill. | | `slippageBps` | `MARKET` only | The slippage the amounts allow, from `0` to `10000`, as a string. | | `isMinAmountOut` | `MARKET` only | Tells the API that `takerAmount` is the minimum to receive. Send the value the SDK returned. | | `reservedBalancePolicy` | `MARKET` only | `SKIP_RESERVED_BALANCE_CHECKS` checks the order against your balance and allowance on its own, ignoring the funds your open orders already reserve. Leave it out for the default check, which counts them. | | `selfTradePrevention` | Both | What happens when the order would match one of your own orders. See below. | ### Self-trade prevention [#self-trade-prevention] Without `selfTradePrevention`, your own orders can match each other. With it, they never do. The value decides what happens to the orders of yours that the new order would cross: | Value | What happens | | :------------- | :------------------------------------------------------------------------------------------ | | `CANCEL_TAKER` | The new order is cancelled. Your resting orders stay. | | `CANCEL_MAKER` | Your resting orders that it would cross are cancelled. The new order still matches other users' orders. | | `CANCEL_BOTH` | The new order and your resting orders that it would cross are cancelled. | A cancelled order gets an `orderCancelled` event with the reason `selfTradePrevention`. ## Amounts and prices [#amounts-and-prices] Prices and amounts are integer strings in wei, where `10^18` is one unit. A price of `400000000000000000` is 0.40 USDT, and an amount of `10000000000000000000` is 10 shares. A decimal string such as `"0.4"` is rejected. For a buy, `makerAmount` is the USDT you pay and `takerAmount` is the shares you get. For a sell, it's the other way around. Buying 10 shares at 0.40 USDT signs a `makerAmount` of 4 USDT and a `takerAmount` of 10 shares. A limit order's amounts must match its price exactly, and the price can have at most `decimalPrecision` decimals. The SDK helpers compute amounts that match the price, so pass the `pricePerShare` they return. They don't know the market's `decimalPrecision`, so round the price to it before you call them. ## Rules the API checks [#rules-the-api-checks] The API rejects an order that breaks one of these rules. The error codes are listed on [Errors](/resources/errors). | Rule | Error code | | :------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------- | | A buy is worth at least 1 USDT. | `create_order_min_order_value_not_met` | | A limit sell is at least 1 share. A market sell is at least 0.01 share. | `create_order_min_order_size_not_met` | | The expiration is at least 2 minutes away, and no later than January 1, 2100. | `create_order_expiry_too_soon`, `create_order_expiry_too_far` | | The price is above 0 and below 1. A limit order's price has at most `decimalPrecision` decimals. | `create_order_price_out_of_range`, `create_order_price_precision_too_high` | | The amounts match the price. | `create_order_limit_amounts_mismatch`, `create_order_market_amounts_mismatch` | | The signed `feeRateBps` is at least the market's `feeRateBps`, less any referral discount. | `create_order_fee_rate_too_low` | | The `maker` is the `signer`. | `create_order_maker_signer_mismatch` | | Each option is used with its strategy, as in the table above. | `create_order_post_only_not_supported`, `create_order_fill_or_kill_not_supported`, `create_order_slippage_non_market`, `create_order_reserved_balance_policy_invalid` | | The market is trading. | `create_order_market_not_trading` | | The same order isn't sent twice. | `create_order_duplicate_order` | | Your balance and approvals cover the order. See [Approvals](/trading/approvals). | `create_order_insufficient_collateral_balance`, `create_order_insufficient_shares_balance`, and the approval errors | While a market's `tradingStatus` is `MATCHING_NOT_ENABLED`, the book takes orders but doesn't match them. Only post-only limit orders are accepted then. Other limit orders get `create_order_trading_disabled`, and market orders get `create_order_market_orders_disabled`. ## The response [#the-response] An accepted order returns `201`: ```json { "success": true, "data": { "code": "OK", "orderId": "123456789", "orderHash": "0x…", "removalLockedUntil": null } } ``` Keep the `orderId`, because you need it to [remove the order](/trading/cancel-orders). `removalLockedUntil` is a time on some up/down markets, which have a speed bump. On those markets, an order that isn't post-only waits a short delay before it reaches the book, and you can't remove it before `removalLockedUntil`. On other markets, and for post-only orders, it's `null`. A `201` means the order passed its first checks. It isn't filled yet, and it isn't final. The API finishes some balance and allowance checks after it responds, and cancels the order if they fail. Follow the order's [wallet events](/trading/order-lifecycle#follow-your-orders-live) to see what happens to it. # Log in (/api/how-tos/log-in) Endpoints that act for a wallet, such as creating, reading and removing its orders, need a JWT for that wallet. You get one by signing a message with the wallet's key. This page shows the requests for a regular wallet (an EOA). For a Predict account, the message is signed through the SDK instead: see [Authentication](/trading/authentication#with-a-predict-account). ## 1. Get the message [#1-get-the-message] [`GET /v1/auth/message`](/api/auth/get-auth-message) returns the message to sign. It needs only your API key. ```json { "success": true, "data": { "message": "Please sign this message to log in. By signing, you accept the Predict Terms of Service, which you can find here: https://docs.predict.fun/terms-of-service Timestamp: 1793832395205" } } ``` The message ends with the time it was issued. Sign and send it within 10 minutes. ## 2. Sign it [#2-sign-it] Sign the message exactly as you received it, as a personal message (EIP-191). This is the standard `personal_sign` that every Ethereum library and wallet supports. ## 3. Get the JWT [#3-get-the-jwt] Send the wallet's address, the message and the signature to [`POST /v1/auth`](/api/auth/get-jwt-with-valid-signature): ```json { "signer": "0xYourWalletAddress", "message": "Please sign this message to log in. …", "signature": "0x…" } ``` The response holds the JWT in `data.token`: ```json { "success": true, "data": { "token": "eyJ0eXAiOiJKV1Qi…" } } ``` ## 4. Use it [#4-use-it] Send the JWT in the `Authorization` header, as `Bearer `, next to your API key. A JWT is valid for 24 hours. After that, log in again. ## The whole flow [#the-whole-flow] This logs in, then lists the wallet's open orders with the JWT: ```typescript import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const apiUrl = "https://api.predict.fun"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); // 1. Get the message to sign. const messageResponse = await fetch(`${apiUrl}/v1/auth/message`, { headers: { "x-api-key": apiKey }, }); const { message } = (await messageResponse.json()).data; // 2. Sign it as a personal message (EIP-191). const signature = await wallet.signMessage(message); // 3. Exchange the signature for a JWT. const loginResponse = await fetch(`${apiUrl}/v1/auth`, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey }, body: JSON.stringify({ signer: wallet.address, message, signature }), }); const login = await loginResponse.json(); if (!loginResponse.ok) throw new Error(`${login.error}: ${login.message}`); const jwt = login.data.token; // 4. Send the JWT with the API key on the wallet's own requests. const ordersResponse = await fetch(`${apiUrl}/v1/orders?status=OPEN`, { headers: { "x-api-key": apiKey, Authorization: `Bearer ${jwt}` }, }); const orders = await ordersResponse.json(); console.log(`${orders.data.length} open orders`); ``` ```python import os import requests from eth_account import Account from eth_account.messages import encode_defunct API_KEY = "YOUR_API_KEY" API_URL = "https://api.predict.fun" wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"]) # 1. Get the message to sign. message_response = requests.get( f"{API_URL}/v1/auth/message", headers={"x-api-key": API_KEY} ) message = message_response.json()["data"]["message"] # 2. Sign it as a personal message (EIP-191). signed = wallet.sign_message(encode_defunct(text=message)) signature = signed.signature.to_0x_hex() # 3. Exchange the signature for a JWT. login_response = requests.post( f"{API_URL}/v1/auth", headers={"x-api-key": API_KEY}, json={"signer": wallet.address, "message": message, "signature": signature}, ) login = login_response.json() if not login_response.ok: raise RuntimeError(f"{login['error']}: {login['message']}") jwt = login["data"]["token"] # 4. Send the JWT with the API key on the wallet's own requests. orders_response = requests.get( f"{API_URL}/v1/orders", params={"status": "OPEN"}, headers={"x-api-key": API_KEY, "Authorization": f"Bearer {jwt}"}, ) orders = orders_response.json() print(f"{len(orders['data'])} open orders") ``` ## When it fails [#when-it-fails] A rejected login is a `401` with `error: "unauthorized"`. The `message` says why: | `message` | Cause | Fix | | :--- | :--- | :--- | | `invalid signature` | The signature doesn't match the message and `signer`. | Sign the message exactly as you received it, with the key of `signer`. | | `invalid message` | The message isn't the one the API sent. | Send the message back unchanged. | | `invalid message timestamp` | The message was issued more than 10 minutes ago, or its timestamp was changed. | Get a new message and sign it right away. | | `authorization error` | The API key is missing or invalid, or a later request has no JWT or an expired one. | Check the API key. Log in again, and send the JWT as `Bearer `. | A body that can't be read, such as one with a malformed `signer` address, gets a `400` instead. # Remove orders (/api/how-tos/remove-orders) Removing an order takes it off Predict's order book. It's free and takes effect right away. To make the signed order unusable on-chain as well, cancel it on-chain instead: see [Cancel orders](/trading/cancel-orders). You need your API key and a JWT for the wallet. See [Log in](/api/how-tos/log-in). ## 1. List the open orders [#1-list-the-open-orders] [`GET /v1/orders`](/api/orders/get-orders) with `status=OPEN` returns the wallet's open orders. Each one has its `id` and, under `order`, its `hash`. Page through them with `first` and `after` (see [Pagination](/api#pagination)). ## 2. Remove them [#2-remove-them] [`POST /v1/orders/remove`](/api/orders/remove-orders) takes the orders' IDs, from 1 to 100 per request: ```json { "data": { "ids": ["123456789", "123456790"] } } ``` [`POST /v1/orders/remove-by-hash`](/api/orders/remove-orders-by-hash) does the same with the orders' hashes, sent as `hashes` instead of `ids`. The response sorts the orders into three lists: ```json { "success": true, "removed": ["123456789"], "noop": ["123456790"], "rejected": [] } ``` | List | Meaning | | :--- | :--- | | `removed` | The order was open, and is now off the book. | | `noop` | The order was no longer open, for example because it filled or was already removed. Nothing changed. | | `rejected` | The order can't be removed yet, because its `removalLockedUntil` time hasn't passed. Try again after that time. | ## The whole flow [#the-whole-flow] This removes every open order of the wallet: ```typescript const apiKey = "YOUR_API_KEY"; const apiUrl = "https://api.predict.fun"; const jwt = "YOUR_JWT"; // from the Log in how-to const headers = { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${jwt}`, }; // 1. List the wallet's open orders, one page at a time. const ids: string[] = []; let after: string | undefined; do { const query = new URLSearchParams({ status: "OPEN", first: "100" }); if (after) query.set("after", after); const response = await fetch(`${apiUrl}/v1/orders?${query}`, { headers }); const page = await response.json(); if (!response.ok) throw new Error(`${page.error}: ${page.message}`); ids.push(...page.data.map((order: { id: string }) => order.id)); after = page.cursor ?? undefined; } while (after); // 2. Remove them, up to 100 per request. for (let i = 0; i < ids.length; i += 100) { const response = await fetch(`${apiUrl}/v1/orders/remove`, { method: "POST", headers, body: JSON.stringify({ data: { ids: ids.slice(i, i + 100) } }), }); const result = await response.json(); if (!response.ok) throw new Error(`${result.error}: ${result.message}`); console.log( `Removed ${result.removed.length}, already closed ${result.noop.length}, ` + `still locked ${result.rejected.length}`, ); } console.log(`${ids.length} open orders found`); ``` ```python import requests API_KEY = "YOUR_API_KEY" API_URL = "https://api.predict.fun" JWT = "YOUR_JWT" # from the Log in how-to HEADERS = {"x-api-key": API_KEY, "Authorization": f"Bearer {JWT}"} # 1. List the wallet's open orders, one page at a time. ids = [] after = None while True: params = {"status": "OPEN", "first": 100} if after: params["after"] = after response = requests.get(f"{API_URL}/v1/orders", params=params, headers=HEADERS) page = response.json() if not response.ok: raise RuntimeError(f"{page['error']}: {page['message']}") ids += [order["id"] for order in page["data"]] after = page.get("cursor") if not after: break # 2. Remove them, up to 100 per request. for start in range(0, len(ids), 100): response = requests.post( f"{API_URL}/v1/orders/remove", headers=HEADERS, json={"data": {"ids": ids[start : start + 100]}}, ) result = response.json() if not response.ok: raise RuntimeError(f"{result['error']}: {result['message']}") print( f"Removed {len(result['removed'])}, already closed {len(result['noop'])}, " f"still locked {len(result['rejected'])}" ) print(f"{len(ids)} open orders found") ``` ## When it fails [#when-it-fails] | Status | `message` | Cause | | :--- | :--- | :--- | | `401` | `one or more orders do not belong to this wallet` | An ID or hash isn't an order of the JWT's wallet, or doesn't exist. Nothing is removed. | | `400` | Mentions `minItems(1)` or `maxItems(100)` | The list is empty, or has more than 100 entries. | | `401` | `authorization error` | The JWT is missing or has expired. Log in again. | # Send an order (/api/how-tos/send-an-order) Sending an order takes two parts: signing it, and sending it. The SDK does the signing, because it computes the order's amounts and its EIP-712 signature for the right exchange contract. This page shows the requests around it. [Place orders](/trading/place-orders) explains the order types, the options and the rules the API checks. You need your API key, a JWT for the wallet (see [Log in](/api/how-tos/log-in)), and the [approvals](/trading/approvals) for the market. ## 1. Read the market [#1-read-the-market] [`GET /v1/markets/{id}`](/api/markets/get-market-by-id) returns what the order needs: each outcome's `onChainId` (the order's `tokenId`), the market's `feeRateBps`, and `isNegRisk` and `isYieldBearing`, which pick the exchange contract the order is signed for. ## 2. Sign the order [#2-sign-the-order] The SDK builds the order from a price and a quantity, signs it, and computes its hash. See [Place orders](/trading/place-orders#place-a-limit-order) for limit and market orders. ## 3. Send it [#3-send-it] [`POST /v1/orders`](/api/orders/create-an-order) takes the signed order, its hash, the price and the strategy. It needs your API key and the wallet's JWT. ```json { "data": { "order": { "hash": "0x…", "salt": "…", "maker": "0x…", "signer": "0x…", "taker": "0x0000000000000000000000000000000000000000", "tokenId": "…", "makerAmount": "4000000000000000000", "takerAmount": "10000000000000000000", "expiration": "4102444800", "nonce": "0", "feeRateBps": "200", "side": 0, "signatureType": 0, "signature": "0x…" }, "pricePerShare": "400000000000000000", "strategy": "LIMIT" } } ``` An accepted order returns `201`: ```json { "success": true, "data": { "code": "OK", "orderId": "123456789", "orderHash": "0x…", "removalLockedUntil": null } } ``` Keep `orderId` to [remove the order](/api/how-tos/remove-orders) later. A `201` means the order passed its first checks, not that it filled. Some balance and allowance checks finish after the response, and the order is cancelled if they fail. Read its status, or follow it live through [wallet events](/trading/order-lifecycle#follow-your-orders-live). ## 4. Read its status [#4-read-its-status] [`GET /v1/orders/{hash}`](/api/orders/get-order-by-hash) returns the order with its `status` (`OPEN`, `FILLED`, `CANCELLED`, `EXPIRED` or `INVALIDATED`) and its `amountFilled` in wei. [Order lifecycle](/trading/order-lifecycle) explains each status. Right after you send an order, this can briefly return `404`. Retry for a few seconds before you treat the hash as unknown. ## The whole flow [#the-whole-flow] ```typescript import { ChainId, OrderBuilder, Side } from "@predictdotfun/sdk"; import { Wallet } from "ethers"; const apiKey = "YOUR_API_KEY"; const apiUrl = "https://api.predict.fun"; const jwt = "YOUR_JWT"; // from the Log in how-to // a market ID, like the ones the Market data example prints. const marketId = "MARKET_ID"; const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); const headers = { "Content-Type": "application/json", "x-api-key": apiKey, Authorization: `Bearer ${jwt}`, }; // 1. Read the market: its outcomes, its fee, and which exchange it trades on. const marketResponse = await fetch(`${apiUrl}/v1/markets/${marketId}`, { headers, }); const market = (await marketResponse.json()).data; // 2. Build and sign the order with the SDK. Place orders explains each step. const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, wallet); const { pricePerShare, makerAmount, takerAmount } = orderBuilder.getLimitOrderAmounts({ side: Side.BUY, pricePerShareWei: 400000000000000000n, // 0.40 USDT quantityWei: 10000000000000000000n, // 10 shares }); const order = orderBuilder.buildOrder("LIMIT", { side: Side.BUY, tokenId: market.outcomes[0].onChainId, makerAmount, takerAmount, feeRateBps: market.feeRateBps, }); const typedData = orderBuilder.buildTypedData(order, { isNegRisk: market.isNegRisk, isYieldBearing: market.isYieldBearing, }); const signedOrder = await orderBuilder.signTypedDataOrder(typedData); const hash = orderBuilder.buildTypedDataHash(typedData); // 3. Send it. const createResponse = await fetch(`${apiUrl}/v1/orders`, { method: "POST", headers, body: JSON.stringify({ data: { order: { ...signedOrder, hash }, pricePerShare: pricePerShare.toString(), strategy: "LIMIT", }, }), }); const created = await createResponse.json(); if (!createResponse.ok) throw new Error(`${created.error}: ${created.message}`); console.log(`Order ${created.data.orderId} accepted`); // 4. Read its status. A new order can briefly return 404, so retry for a // few seconds. const statusUrl = `${apiUrl}/v1/orders/${hash}`; let statusResponse = await fetch(statusUrl, { headers }); for (let retry = 0; retry < 4 && statusResponse.status === 404; retry++) { await new Promise((resolve) => setTimeout(resolve, 1000)); statusResponse = await fetch(statusUrl, { headers }); } const orderStatus = await statusResponse.json(); if (!statusResponse.ok) { throw new Error(`${orderStatus.error}: ${orderStatus.message}`); } const { status, amountFilled } = orderStatus.data; console.log(status, amountFilled); ``` ```python import os import time import requests from predict_sdk import BuildOrderInput, ChainId, LimitHelperInput, OrderBuilder, Side API_KEY = "YOUR_API_KEY" API_URL = "https://api.predict.fun" JWT = "YOUR_JWT" # from the Log in how-to # a market ID, like the ones the Market data example prints. MARKET_ID = "MARKET_ID" HEADERS = {"x-api-key": API_KEY, "Authorization": f"Bearer {JWT}"} # 1. Read the market: its outcomes, its fee, and which exchange it trades on. market = requests.get(f"{API_URL}/v1/markets/{MARKET_ID}", headers=HEADERS).json()[ "data" ] # 2. Build and sign the order with the SDK. Place orders explains each step. builder = OrderBuilder.make(ChainId.BNB_MAINNET, os.environ["WALLET_PRIVATE_KEY"]) amounts = builder.get_limit_order_amounts( LimitHelperInput( side=Side.BUY, price_per_share_wei=400000000000000000, # 0.40 USDT quantity_wei=10000000000000000000, # 10 shares ) ) order = builder.build_order( "LIMIT", BuildOrderInput( side=Side.BUY, token_id=market["outcomes"][0]["onChainId"], maker_amount=amounts.maker_amount, taker_amount=amounts.taker_amount, fee_rate_bps=market["feeRateBps"], ), ) typed_data = builder.build_typed_data( order, is_neg_risk=market["isNegRisk"], is_yield_bearing=market["isYieldBearing"], ) signed_order = builder.sign_typed_data_order(typed_data) order_hash = builder.build_typed_data_hash(typed_data) # 3. Send it. The typed data's message holds the order's fields under the # names the API uses. body = { "data": { "order": { **typed_data.message, "hash": order_hash, "signature": signed_order.signature, }, "pricePerShare": str(amounts.price_per_share), "strategy": "LIMIT", } } create_response = requests.post(f"{API_URL}/v1/orders", headers=HEADERS, json=body) created = create_response.json() if not create_response.ok: raise RuntimeError(f"{created['error']}: {created['message']}") print(f"Order {created['data']['orderId']} accepted") # 4. Read its status. A new order can briefly return 404, so retry for a # few seconds. status_url = f"{API_URL}/v1/orders/{order_hash}" status_response = requests.get(status_url, headers=HEADERS) for _ in range(4): if status_response.status_code != 404: break time.sleep(1) status_response = requests.get(status_url, headers=HEADERS) order_status = status_response.json() if not status_response.ok: raise RuntimeError(f"{order_status['error']}: {order_status['message']}") print(order_status["data"]["status"], order_status["data"]["amountFilled"]) ``` ## When it fails [#when-it-fails] A rejected order returns `400`, and `error` names the rule it broke: ```json { "success": false, "code": 400, "error": "create_order_insufficient_collateral_allowance", "message": "Insufficient collateral: USDT allowance is less than the total bid amount." } ``` [Errors](/resources/errors#order-codes) lists every order code with its fix. A missing or expired JWT returns `401`, and so does an order whose `signer` isn't the JWT's wallet. A request from a restricted region or wallet returns `403`. In step 4, a hash that is still unknown after a few seconds returns `404` with `error: "not_found"`. # Overview (/api) The REST API serves market data, your account and positions, and the orders you place and remove. Every endpoint in this section follows the rules on this page. ## Base URLs [#base-urls] | Network | Base URL | API key | | :--- | :--- | :--- | | BNB Mainnet | `https://api.predict.fun` | Required | | BNB Testnet | `https://api-testnet.predict.fun` | Not needed | Every path starts with `/v1`, for example `https://api.predict.fun/v1/markets`. ## Credentials [#credentials] | Header | Value | Needed for | | :--- | :--- | :--- | | `x-api-key` | Your API key from the [developer console](https://developers.predict.fun) | Every mainnet request | | `Authorization` | `Bearer ` | Requests that act for a wallet, such as creating, reading and removing its orders | Each endpoint's page lists the credentials it needs under **Authorization**. [Log in](/api/how-tos/log-in) shows how to get a JWT for a wallet. For a Predict account, see [Authentication](/trading/authentication#with-a-predict-account). On testnet, leave `x-api-key` out. Testnet doesn't need a key, but it rejects an invalid one. ## Requests [#requests] Send request bodies as JSON, with `Content-Type: application/json`. Query and path parameters are listed on each endpoint's page. Amounts and prices in orders are integer strings in wei, where `10^18` is 1 USDT or 1 share. For example, a price of 0.40 is `"400000000000000000"`. A decimal string such as `"0.4"` is rejected. ## Responses [#responses] A successful response has `success: true`. Most endpoints put their result in `data`: ```json { "success": true, "data": { "message": "Please sign this message to log in. …" } } ``` A list adds a `cursor` for the next page (see [Pagination](#pagination)). A few endpoints, such as [Remove orders](/api/orders/remove-orders), return their fields next to `success` instead of in `data`. Each endpoint's page shows its response. An error has `success: false`, the HTTP status in `code`, and a stable code in `error` to match on: ```json { "success": false, "code": 401, "error": "unauthorized", "message": "invalid signature", "timestamp": "2026-11-04T20:37:19.030930831Z" } ``` [Errors](/resources/errors) lists every code with its fix. ### Units and timestamps [#units-and-timestamps] - Share amounts in responses, such as a position's `amount` or an order's `amountFilled`, are wei strings, like the amounts you send. - Order book prices and sizes are plain numbers, such as `[0.62, 1500]`: a price between 0 and 1 and a number of shares. See [Prices and order book](/concepts/order-book). - Fee rates such as `feeRateBps` are in basis points, where 100 is 1%. - Times in fields named `…At`, such as `createdAt`, are date-time strings. Fields named `…TimestampMs` are milliseconds since the Unix epoch. ## Pagination [#pagination] List endpoints return one page at a time: 1. Set the page size with `first`. The table below gives each endpoint's default and maximum. 2. Read `cursor` from the response. 3. Send it back as `after` to get the next page. When there are no more pages, `cursor` is `null`. | Endpoints | Default `first` | Maximum `first` | | :--- | :--- | :--- | | `GET /v1/categories`, `GET /v1/markets`, `GET /v1/positions`, `GET /v1/positions/{address}`, `GET /v1/series/{slug}/rounds` | 20 | 100 | | `GET /v1/orders`, `GET /v1/orders/matches`, `GET /v1/account/activity`, `GET /v1/account/{address}/activity`, `GET /v1/maker-rebates`, `GET /v1/referral-fees` | 50 | 150 | A `first` outside that range isn't rejected. A larger value is read as the maximum, and a value below 1 is read as 1. ## Limits [#limits] Each API key has a request budget per minute. A key can also have a per-second cap on order placement and removal. Responses carry `RateLimit-*` headers with your remaining budget, except when the API key is rejected. A request over the limit gets a `429`. See [Rate limits](/resources/rate-limits). ## Try it in the browser [#try-it-in-the-browser] Every endpoint's page has a **Try in Playground** button that sends the request from your browser. It uses mainnet unless you pick the testnet server. Enter your API key, and a JWT for wallet endpoints, in its **Authorization** fields. The OpenAPI document behind these pages is at [`/openapi/rest.json`](/openapi/rest.json). You can use it to generate a client. ## How-tos [#how-tos] Get a JWT for a wallet, and send it with the wallet's requests. Send a signed order, and read its status. List a wallet's open orders, and remove them from the book. For the SDK side of trading, such as order options, approvals and positions, see the [Trading guides](/trading/place-orders). # Client example (/websocket/client-example) A zero-dependency reference client for the Predict WebSocket API, with heartbeat handling, reconnect notices, reconnection with backoff, subscription tracking, and typed payloads.
```typescript /** * A zero-dependency client for the Predict WebSocket API. * Designed to be used in the browser. */ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); function neOf(item: T): NonEmptyArray { return [item] as NonEmptyArray; } function isNonEmpty(items: T[]): items is NonEmptyArray { return items.length > 0; } /* * Realtime Client */ export class RealtimeClient { constructor( private ws: WebSocket, private readonly options: { maxConnAttempts: number; maxRetryInterval: number; }, ) { this.bindAll(); } /** Topic of each in-flight subscribe or unsubscribe request, cleared on its response. */ private topicSubReqIdMap: Map = new Map(); /** Active topics this connection is subscribed to. */ private subscriptions: Map> = new Map(); private connectionAttempts = 0; private requestId = 0; /** Set by close(), so the client doesn't reconnect after a close it asked for. */ private closedByUser = false; /** Set by a reconnect notice, so the next connection opens at once. */ private reconnectNow = false; private bindAll(): void { this.ws.onerror = this.onError; this.ws.onmessage = this.onMessage; this.ws.onclose = this.onClose; this.ws.onopen = this.onOpen; } private async reconnect() { // a reconnect notice moves to a new connection at once, and isn't a failed attempt. if (this.reconnectNow) { this.reconnectNow = false; return this.open(); } const attempt = this.connectionAttempts++; if (attempt < this.options.maxConnAttempts) { // reconnect at once the first time. while reconnecting keeps failing, wait // 1, 2, 4 and 8 seconds and so on, up to maxRetryInterval. up to half a // second of jitter keeps clients from all reconnecting at the same moment. const wait = attempt === 0 ? 0 : Math.min(1000 * 2 ** (attempt - 1), this.options.maxRetryInterval); await sleep(wait + Math.random() * 500); this.open(); } else { const allHandlers = Array.from(this.subscriptions.values()).flat(); for (const handler of allHandlers) { handler({ err: { code: 'ws_disconnected', message: 'Max connection attempts reached' }, }); } } } /** Opens a new connection to the same URL, unless close() was called. */ private open() { if (this.closedByUser) return; this.ws = new WebSocket( this.ws.url, this.ws.protocol !== '' ? this.ws.protocol : undefined, ); this.bindAll(); } private onOpen = () => { this.connectionAttempts = 0; // re-subscribe every active topic after a reconnect. for (const topic of this.subscriptions.keys()) { this.subUnsub('subscribe', topic); } }; private onClose = () => { // reconnect after every close the client didn't ask for, clean or not. the // server's own closes, such as 1001 when it shuts down, are clean ones. if (!this.closedByUser) { return this.reconnect(); } }; private onMessage = (event: MessageEvent) => { const parsed = JSON.parse(event.data as unknown as string) as Response; if (parsed.type === 'M') { const topic = parsed.topic; if (topic === 'heartbeat') { // echo the server timestamp back exactly. this.send({ method: 'heartbeat', data: parsed.data }); } else if (parsed.topic === 'system') { if (parsed.data.event === 'reconnect') { // the server is shutting down, so move to a new connection right away. this.reconnectNow = true; this.ws.close(); } } else { const handlers = this.subscriptions.get(parsed.topic); for (const handler of handlers || []) { handler({ data: parsed.data }); } } } else if (parsed.type === 'R') { const requestIdTopic = this.topicSubReqIdMap.get(parsed.requestId); if (requestIdTopic) { if (!parsed.success) { const handlers = this.subscriptions.get(requestIdTopic); if (handlers) { for (const handler of handlers) { handler({ err: parsed.error }); } } this.subscriptions.delete(requestIdTopic); } this.topicSubReqIdMap.delete(parsed.requestId); } else { console.warn(`Unknown response received`, JSON.stringify(parsed, null, 2)); } } }; private onError = (event: Event) => { const allHandlers = Array.from(this.subscriptions.values()).flat(); for (const handler of allHandlers) { handler({ err: { code: 'ws_error_terminated', message: 'Websocket error' } }); } console.error('RealtimeClientSocketError', event); }; private send(data: Requests) { if (this.ws.readyState === this.ws.OPEN) { if (data.method !== 'heartbeat') { this.topicSubReqIdMap.set(data.requestId, data.params[0]); } return this.ws.send(JSON.stringify(data)); } else { console.warn(`Not connected to WS. Ignoring.`); } } private subUnsub(method: 'subscribe' | 'unsubscribe', topic: string) { this.send({ requestId: this.requestId++, method, params: neOf(topic) }); } private getTopicStringFor(channel: Channel): string { switch (channel.name) { case 'predictOrderbook': return `predictOrderbook/${channel.marketId}`; case 'predictTradingStatus': return `predictTradingStatus/${channel.marketId}`; case 'predictMarketStatus': return `predictMarketStatus/${channel.marketId}`; case 'predictMarketChanged': return `predictMarketChanged/${channel.marketId}`; case 'predictCategoryChanged': return `predictCategoryChanged/${channel.categoryId}`; case 'predictWalletEvents': return `predictWalletEvents/${channel.jwt}`; } } subscribe(channel: Channel, callback: EventCallback): { unsubscribe: () => void } { const topicName = this.getTopicStringFor(channel); const existing = this.subscriptions.get(topicName); if (existing) { existing.push(callback); } else { this.subscriptions.set(topicName, [callback]); this.subUnsub('subscribe', topicName); } return { unsubscribe: (): void => { const item = this.subscriptions.get(topicName); if (!item) { throw new Error('InconsistentState: No subscriptions for this topic'); } const cbRemoved = item.filter((x) => x !== callback); if (isNonEmpty(cbRemoved)) { this.subscriptions.set(topicName, cbRemoved); } else { this.subscriptions.delete(topicName); this.subUnsub('unsubscribe', topicName); } }, }; } close(): void { this.closedByUser = true; this.ws.close(); } } /* * Types */ export type NonEmptyArray = [T, ...T[]]; export type Pretty = { [K in keyof T]: T[K] } extends infer U ? U : never; /* Payloads */ export type MarketTradingStatusUpdate = { kind: 'tradingStatusUpdate'; tsMs: number; marketId: number; tradingStatus: 'OPEN' | 'MATCHING_NOT_ENABLED' | 'CANCEL_ONLY' | 'CLOSED'; }; export type MarketStatusOutcomeEntry = { label: string; price: string; indexSet: number; onChainId?: string; }; export type MarketStatusUpdatePayload = { kind: 'marketStatusUpdate'; tsMs: number; marketId: number; status: string; marketOutcomes?: MarketStatusOutcomeEntry[]; }; export type DepthLevel = [number, number]; export type OrderbookLastSettled = { id: string; price: string; kind: string; marketId: number; side: 'Ask' | 'Bid'; outcome: 'Yes' | 'No'; }; export type PredictOrderbookUpdate = { version: 1; marketId: number; updateTimestampMs: number; orderCount: number; asks: DepthLevel[]; bids: DepthLevel[]; lastOrderSettled: OrderbookLastSettled | null; settlementsPending: { asks: DepthLevel[]; bids: DepthLevel[] }; }; export type MarketChangedUpdate = { kind: 'marketChanged'; patchKind: string | null; tsMs: number; marketId: number; patch?: Record; }; export type CategoryChangedUpdate = { kind: 'categoryChanged'; patchKind: string | null; tsMs: number; categoryId: number; slug: string; patch?: Record; }; /** A change with no detail. Refetch the category. */ export type CategoryChangedNotice = { categoryId: number; tsMs: number; }; export type SystemEvent = { event: string; reason: string; graceMs: number; }; /* Wallet events */ type BaseEvent = { orderId: string; orderHash: string; timestamp: number; walletAddress?: string; details: { marketId: number; outcomeIndex: number; marketQuestion: string; outcome: 'YES' | 'NO'; outcomeName: string; quoteType: 'ASK' | 'BID'; quantity: string; quantityFilled: string; price: string; value: string; valueFilled: string; strategyType: 'MARKET' | 'LIMIT'; categorySlug: string; }; }; type Fill = { executedPriceWei: string; executedSizeWei: string; executedValueWei: string; }; type OrderAccepted = BaseEvent & { type: 'orderAccepted' }; type OrderNotAccepted = BaseEvent & { type: 'orderNotAccepted'; reason: 'noMarketMatch' | 'rejectedDuplicate' | 'rejectedPostOnly'; settlementId?: string | null; }; type OrderMarketMatchPlan = BaseEvent & { type: 'orderMarketMatchPlan'; amount: string; amountFilled: string; plannedFill: string; remainderCancelled: string; settlementIds: string[]; }; type OrderExpired = BaseEvent & { type: 'orderExpired' }; type OrderCancelled = BaseEvent & { type: 'orderCancelled'; reason?: 'selfTradePrevention'; }; type OrderTransactionSubmitted = BaseEvent & { type: 'orderTransactionSubmitted'; kind: 'SALE' | 'PURCHASE'; isMaker: boolean; settlementId: string; fill: Fill; }; type OrderTransactionSuccess = BaseEvent & { type: 'orderTransactionSuccess'; settlementId: string; fill: Fill; isMaker?: boolean; fee?: { amountWei: string; type: 'COLLATERAL' | 'SHARES' }; }; type OrderTransactionFailed = BaseEvent & { type: 'orderTransactionFailed'; settlementId: string; fill: Fill; }; export type PredictWalletEvents = | OrderAccepted | OrderNotAccepted | OrderMarketMatchPlan | OrderExpired | OrderCancelled | OrderTransactionSubmitted | OrderTransactionSuccess | OrderTransactionFailed; /* Channels */ export interface PredictOrderbookChannel { name: 'predictOrderbook'; marketId: number } export interface PredictTradingStatusChannel { name: 'predictTradingStatus'; marketId: number } export interface PredictMarketStatusChannel { name: 'predictMarketStatus'; marketId: number } export interface PredictMarketChangedChannel { name: 'predictMarketChanged'; marketId: number } export interface PredictCategoryChangedChannel { name: 'predictCategoryChanged'; categoryId: number } export interface PredictWalletEventsChannel { name: 'predictWalletEvents'; jwt: string } export type Channel = Pretty< | PredictOrderbookChannel | PredictTradingStatusChannel | PredictMarketStatusChannel | PredictMarketChangedChannel | PredictCategoryChangedChannel | PredictWalletEventsChannel >; /* Requests */ type WithRequestId = T & { requestId: number }; export type SubscribeRequest = WithRequestId<{ method: 'subscribe'; params: NonEmptyArray }>; export type UnsubscribeRequest = WithRequestId<{ method: 'unsubscribe'; params: NonEmptyArray }>; export type HeartbeatRequest = { method: 'heartbeat'; data: unknown }; export type Requests = Pretty; /* Errors */ type InvalidJson = { code: 'invalid_json'; message?: string }; export type InvalidTopic = { code: 'invalid_topic'; message?: string }; export type InvalidParams = { code: 'invalid_params'; message?: string }; export type InvalidCredentials = { code: 'invalid_credentials'; message?: string }; export type InternalServerError = { code: 'internal_server_error'; message?: string }; export type ResponseError = Pretty< InvalidJson | InvalidTopic | InvalidParams | InvalidCredentials | InternalServerError >; /* Responses */ export type RequestResponse = { type: 'R'; requestId: number; } & ({ success: true } | { success: false; error: ResponseError }); export type MessageResponse = { type: 'M'; topic: Topic; data: Data; }; export type MessageResponses = | MessageResponse<'heartbeat', number> | MessageResponse<`predictOrderbook${string}`, PredictOrderbookUpdate> | MessageResponse<`predictTradingStatus${string}`, MarketTradingStatusUpdate> | MessageResponse<`predictMarketStatus${string}`, MarketStatusUpdatePayload> | MessageResponse<`predictMarketChanged${string}`, MarketChangedUpdate> | MessageResponse<`predictCategoryChanged${string}`, CategoryChangedUpdate | CategoryChangedNotice> | MessageResponse<`predictWalletEvents${string}`, PredictWalletEvents>; export type Response = Pretty< MessageResponses | MessageResponse<'system', SystemEvent> | RequestResponse >; /* Internal */ export type WSError = | ResponseError | { code: 'ws_disconnected'; message?: string } | { code: 'ws_error_terminated'; message?: string }; export type TopicName = string; export type EventCallback = ( arg: { err?: null; data: MessageResponses['data'] } | { err: WSError; data?: null }, ) => void; ```
# Follow your orders (/websocket/how-tos/follow-your-orders) The `predictWalletEvents/{jwt}` topic sends an event each time something happens to one of your wallet's orders. Use it to learn whether an order was accepted, when it matched, and when its trades settled on-chain. ## Subscribe with a JWT [#subscribe-with-a-jwt] The topic holds a JWT for the wallet, from the REST API's [Log in](/api/how-tos/log-in) how-to: ```json { "method": "subscribe", "requestId": 1, "params": ["predictWalletEvents/eyJ0eXAiOiJKV1Qi…"] } ``` The server checks the JWT when you subscribe. An invalid or expired JWT gets an `invalid_credentials` error. A JWT is valid for 24 hours, so log in again before you subscribe on a new connection. Nothing is sent on subscribe: events start with the next thing that happens to an order. ## Events [#events] Each event's `type` says what happened to the order, such as `orderAccepted`, `orderTransactionSuccess` or `orderCancelled`. [Order lifecycle](/trading/order-lifecycle#follow-your-orders-live) describes when you get each type, and the [wallet events topic](/websocket/topics/wallet-events) lists every field. Every event has the order's `orderId` and `orderHash`, a `timestamp` in epoch milliseconds, and `details` with the `marketId`, the side (`quoteType`), the `outcome`, the `price`, the `quantity` and the `quantityFilled`. ## The whole example [#the-whole-example] This subscribes with a JWT and prints a line for each event: ```typescript // Print each event of a wallet's orders as it happens. // Runs in browsers and in Node.js 22 or later, which have WebSocket built in. const jwt = "YOUR_JWT"; // from the REST API's Log in how-to const topic = `predictWalletEvents/${jwt}`; // Browsers can't set headers on a WebSocket, so the key goes in the URL. const ws = new WebSocket("wss://ws.predict.fun/ws?apiKey=YOUR_API_KEY"); ws.addEventListener("open", () => { ws.send( JSON.stringify({ method: "subscribe", requestId: 1, params: [topic] }), ); }); ws.addEventListener("message", (event) => { const message = JSON.parse(event.data); if (message.type === "R" && !message.success) { console.error(`Subscribe failed: ${message.error.message}`); } else if (message.topic === "heartbeat") { // Echo each heartbeat, or the server closes the connection. ws.send(JSON.stringify({ method: "heartbeat", data: message.data })); } else if (message.topic === topic) { const { type, orderId, reason, details } = message.data; const { quoteType, outcome, quantityFilled, quantity, price } = details; console.log( `${type} ${orderId}: ${quoteType} ${outcome} ` + `${quantityFilled}/${quantity} at ${price}${reason ? ` (${reason})` : ""}`, ); } }); ``` ```python # Print each event of a wallet's orders as it happens. # Install: pip install websockets import asyncio import json import websockets API_KEY = "YOUR_API_KEY" JWT = "YOUR_JWT" # from the REST API's Log in how-to async def main(): topic = f"predictWalletEvents/{JWT}" async with websockets.connect( "wss://ws.predict.fun/ws", additional_headers={"x-api-key": API_KEY} ) as ws: await ws.send( json.dumps({"method": "subscribe", "requestId": 1, "params": [topic]}) ) async for raw in ws: message = json.loads(raw) if message.get("type") == "R" and not message["success"]: print(f"Subscribe failed: {message['error']['message']}") elif message.get("topic") == "heartbeat": # Echo each heartbeat, or the server closes the connection. reply = {"method": "heartbeat", "data": message["data"]} await ws.send(json.dumps(reply)) elif message.get("topic") == topic: event, details = message["data"], message["data"]["details"] reason = f" ({event['reason']})" if event.get("reason") else "" print( f"{event['type']} {event['orderId']}: {details['quoteType']} " f"{details['outcome']} {details['quantityFilled']}/" f"{details['quantity']} at {details['price']}{reason}" ) asyncio.run(main()) ``` ## After a reconnect [#after-a-reconnect] Events sent while you were disconnected aren't replayed. After you reconnect: 1. Subscribe again, with a JWT that hasn't expired. 2. Fetch your open orders from [`GET /v1/orders?status=OPEN`](/api/orders/get-orders), following its `cursor` for more pages. 3. Treat that list as the current state, and apply new events on top of it. [Stay connected](/websocket/how-tos/stay-connected) shows the reconnection itself. # Stay connected (/websocket/how-tos/stay-connected) A WebSocket connection can close for many reasons: a missed heartbeat, a server shutting down, a network change. A client that stays connected does three things: it answers heartbeats, it moves to a new connection when the server asks, and it reconnects with backoff after any other disconnect. ## Answer heartbeats [#answer-heartbeats] Every 15 seconds the server sends a heartbeat with the current time. The first one arrives about 15 seconds after you connect: ```json { "type": "M", "topic": "heartbeat", "data": 1736696400000 } ``` Echo its `data` back before the next one: ```json { "method": "heartbeat", "data": 1736696400000 } ``` The echo has no `requestId` and gets no response. A missing, late or different value closes the connection with code `1000` at the next heartbeat. ## Handle reconnect notices [#handle-reconnect-notices] Before a server shuts down, for example during a release, it sends a notice on the `system` topic: ```json { "type": "M", "topic": "system", "data": { "event": "reconnect", "reason": "server_shutdown", "graceMs": 7431 } } ``` The connection keeps delivering for about `graceMs`, then closes with code `1001` and the reason `server draining`. Use that time to move to a new connection. The examples below reconnect right away. If you can't miss a single update, open the new connection and subscribe on it before you close the old one. ## Reconnect with backoff [#reconnect-with-backoff] After any other disconnect, reconnect right away. If that fails, wait longer after each failed attempt, for example 1, 2, 4 and 8 seconds, then 10 seconds between tries, so you don't flood a server that is down. Add up to half a second of random jitter, so that many clients don't reconnect at the same moment. Reset the wait once a connection opens, so the next disconnect reconnects right away again. A new connection starts with no subscriptions, and messages sent while you were away aren't replayed. So on every connection: 1. Subscribe to every topic you need. 2. Refetch from the REST API anything you track, such as your open orders or the market's status. ## The whole example [#the-whole-example] This keeps a subscription running through heartbeats, reconnect notices and disconnects: ```typescript // Keep subscriptions running: echo heartbeats, and reconnect after any // disconnect, subscribing again each time. // Runs in browsers and in Node.js 22 or later, which have WebSocket built in. const url = "wss://ws.predict.fun/ws?apiKey=YOUR_API_KEY"; // MARKET_ID: a market ID, like the ones the Market data example prints. const topics = ["predictOrderbook/MARKET_ID"]; let attempt = 0; function connect() { const ws = new WebSocket(url); let reconnectNow = false; ws.addEventListener("open", () => { attempt = 0; // A new connection has no subscriptions, so subscribe to every topic. topics.forEach((topic, i) => { ws.send( JSON.stringify({ method: "subscribe", requestId: i + 1, params: [topic], }), ); }); // Updates sent while you were disconnected are not replayed. Refetch // anything you track from the REST API here. }); ws.addEventListener("message", (event) => { const message = JSON.parse(event.data); if (message.topic === "heartbeat") { // Echo each heartbeat, or the server closes the connection. ws.send(JSON.stringify({ method: "heartbeat", data: message.data })); } else if ( message.topic === "system" && message.data.event === "reconnect" ) { // The server is shutting down, so move to a new connection right away. reconnectNow = true; ws.close(); } else if (message.type === "R" && !message.success) { console.error( `Request ${message.requestId} failed: ${message.error.message}`, ); } else if (message.type === "M") { console.log(`Update on ${message.topic}`); } }); ws.addEventListener("close", (event) => { let delay = 0; if (!reconnectNow) { // Reconnect at once the first time. While reconnecting keeps failing, // wait 1, 2, 4 and 8 seconds, then 10 seconds between tries. Up to half a // second of jitter keeps clients from all reconnecting at the same moment. const wait = attempt === 0 ? 0 : Math.min(1000 * 2 ** (attempt - 1), 10_000); delay = wait + Math.random() * 500; attempt++; } console.log( `Closed (${event.code}). Reconnecting in ${Math.round(delay)} ms`, ); setTimeout(connect, delay); }); } connect(); ``` ```python # Keep subscriptions running: echo heartbeats, and reconnect after any # disconnect, subscribing again each time. # Install: pip install websockets import asyncio import json import random import websockets API_KEY = "YOUR_API_KEY" # MARKET_ID: a market ID, like the ones the Market data example prints. TOPICS = ["predictOrderbook/MARKET_ID"] async def main(): attempt = 0 while True: reconnect_now = False try: async with websockets.connect( "wss://ws.predict.fun/ws", additional_headers={"x-api-key": API_KEY} ) as ws: attempt = 0 # A new connection has no subscriptions, so subscribe to every topic. for request_id, topic in enumerate(TOPICS, start=1): request = {"method": "subscribe", "requestId": request_id} await ws.send(json.dumps({**request, "params": [topic]})) # Updates sent while you were disconnected are not replayed. # Refetch anything you track from the REST API here. async for raw in ws: message = json.loads(raw) if message.get("topic") == "heartbeat": # Echo each heartbeat, or the server closes the connection. reply = {"method": "heartbeat", "data": message["data"]} await ws.send(json.dumps(reply)) elif message.get("topic") == "system": # The server is shutting down, so move to a new # connection right away. if message["data"].get("event") == "reconnect": reconnect_now = True break elif message.get("type") == "R" and not message["success"]: request_id, error = message["requestId"], message["error"] print(f"Request {request_id} failed: {error['message']}") elif message.get("type") == "M": print(f"Update on {message['topic']}") except (websockets.WebSocketException, OSError) as error: print(f"Connection lost: {error}") if not reconnect_now: # Reconnect at once the first time. While reconnecting keeps # failing, wait 1, 2, 4 and 8 seconds, then 10 seconds between # tries. Up to half a second of jitter keeps clients from all # reconnecting at the same moment. wait = 0 if attempt == 0 else min(2 ** (attempt - 1), 10) await asyncio.sleep(wait + random.random() / 2) attempt += 1 asyncio.run(main()) ``` For a complete client with typed messages and subscription tracking, see the [client example](/websocket/client-example). # Stream an order book (/websocket/how-tos/stream-an-order-book) The `predictOrderbook/{marketId}` topic sends a market's whole order book each time it changes. The latest book usually arrives right after you subscribe. When it doesn't, fetch it from [`GET /v1/markets/{id}/orderbook`](/api/markets/get-market-orderbook). ## Subscribe [#subscribe] Send a subscribe request with the market's ID in the topic: ```json { "method": "subscribe", "requestId": 1, "params": ["predictOrderbook/123"] } ``` Each update is the whole book, so replace what you have instead of merging: ```json { "type": "M", "topic": "predictOrderbook/123", "data": { "marketId": 123, "updateTimestampMs": 1736696400000, "asks": [[0.62, 1500], [0.63, 800]], "bids": [[0.61, 2000], [0.6, 1200]] } } ``` `asks` and `bids` are `[price, shares]` levels, best price first. The [order book topic](/websocket/topics/order-book) lists every field, including the settlements that are still pending. ## The No side [#the-no-side] The book is priced in Yes. The No side holds the same orders seen from the other side: a Yes bid at 0.61 is a No ask at 0.39. So the No book is the Yes book with its sides swapped, and each price replaced by its complement at the market's `decimalPrecision`. [Prices and order book](/concepts/order-book) explains why. ## The whole example [#the-whole-example] This reads the market's precision from the REST API, subscribes to its order book, and prints both books on every update. It also echoes the server's heartbeats, which keeps the connection open. ```typescript // Show a market's order book for both outcomes, Yes and No, as it changes. // The API sends the Yes book. The No book holds the same orders seen from the // other side: a Yes bid at 0.40 is a No ask at 0.60. So the sides swap, and each // price becomes its complement. // Runs in browsers and in Node.js 22 or later, which have WebSocket built in. // Get an API key from the developer console: https://developers.predict.fun const apiKey = "YOUR_API_KEY"; // a market ID, like the ones the Market data example prints. const marketId = "MARKET_ID"; type Level = [price: number, shares: number]; // Prices have the market's decimal precision, for example 2 for steps of 0.01. const response = await fetch(`https://api.predict.fun/v1/markets/${marketId}`, { headers: { "x-api-key": apiKey }, }); if (!response.ok) throw new Error(`Predict API error: ${response.status}`); const { decimalPrecision } = (await response.json()).data; // The No price of a Yes price. Rounding at the market's precision makes the two // add up to exactly 1. function complement(price: number): number { const factor = 10 ** decimalPrecision; return (factor - Math.round(price * factor)) / factor; } function cents(price: number): string { return `${(price * 100).toFixed(Math.max(decimalPrecision - 2, 0))}¢`; } // Prints the 5 best levels of each side. The asks print highest first, so the // best ask and the best bid meet at the spread. function printBook(outcome: string, asks: Level[], bids: Level[]) { const row = (side: string, [price, shares]: Level) => ` ${side} ${cents(price).padStart(6)} ${shares.toFixed(2).padStart(10)}`; console.log(`${outcome} price shares`); for (const level of asks.slice(0, 5).reverse()) { console.log(row("ask", level)); } if (asks.length > 0 && bids.length > 0) { console.log(` spread ${cents(asks[0][0] - bids[0][0])}`); } for (const level of bids.slice(0, 5)) { console.log(row("bid", level)); } } // Browsers can't set headers on a WebSocket, so the key goes in the URL. const ws = new WebSocket(`wss://ws.predict.fun/ws?apiKey=${apiKey}`); const topic = `predictOrderbook/${marketId}`; ws.addEventListener("open", () => { ws.send( JSON.stringify({ method: "subscribe", requestId: 1, params: [topic] }), ); }); ws.addEventListener("message", (event) => { const message = JSON.parse(event.data); if (message.type === "R" && !message.success) { console.error(`Subscribe failed: ${message.error.message}`); } else if (message.topic === "heartbeat") { // Echo each heartbeat, or the server closes the connection. ws.send(JSON.stringify({ method: "heartbeat", data: message.data })); } else if (message.topic === topic) { const { asks, bids }: { asks: Level[]; bids: Level[] } = message.data; const toNo = ([price, shares]: Level): Level => [complement(price), shares]; console.clear(); printBook("Yes", asks, bids); // No asks come from the Yes bids, and No bids from the Yes asks. printBook("No ", bids.map(toNo), asks.map(toNo)); } }); ws.addEventListener("close", (event) => { console.log(`Connection closed (${event.code})`); }); ``` ```python # Show a market's order book for both outcomes, Yes and No, as it changes. # The API sends the Yes book. The No book holds the same orders seen from the # other side: a Yes bid at 0.40 is a No ask at 0.60. So the sides swap, and each # price becomes its complement. # Install: pip install requests websockets # Get an API key from the developer console: https://developers.predict.fun import asyncio import json import requests import websockets API_KEY = "YOUR_API_KEY" # a market ID, like the ones the Market data example prints. MARKET_ID = "MARKET_ID" # Prices have the market's decimal precision, for example 2 for steps of 0.01. response = requests.get( f"https://api.predict.fun/v1/markets/{MARKET_ID}", headers={"x-api-key": API_KEY}, ) response.raise_for_status() PRECISION = response.json()["data"]["decimalPrecision"] def complement(price): """The No price of a Yes price. Rounding at the market's precision makes the two add up to exactly 1.""" factor = 10**PRECISION return (factor - round(price * factor)) / factor def cents(price): return f"{price * 100:.{max(PRECISION - 2, 0)}f}¢" def print_book(outcome, asks, bids): """Prints the 5 best levels of each side. The asks print highest first, so the best ask and the best bid meet at the spread.""" print(f"{outcome} price shares") for price, shares in reversed(asks[:5]): print(f" ask {cents(price):>6} {shares:>10.2f}") if asks and bids: print(f" spread {cents(asks[0][0] - bids[0][0])}") for price, shares in bids[:5]: print(f" bid {cents(price):>6} {shares:>10.2f}") async def main(): topic = f"predictOrderbook/{MARKET_ID}" async with websockets.connect( "wss://ws.predict.fun/ws", additional_headers={"x-api-key": API_KEY}, ) as ws: await ws.send( json.dumps({"method": "subscribe", "requestId": 1, "params": [topic]}) ) async for raw in ws: message = json.loads(raw) if message.get("type") == "R" and not message["success"]: print(f"Subscribe failed: {message['error']['message']}") elif message.get("topic") == "heartbeat": # Echo each heartbeat, or the server closes the connection. reply = {"method": "heartbeat", "data": message["data"]} await ws.send(json.dumps(reply)) elif message.get("topic") == topic: asks, bids = message["data"]["asks"], message["data"]["bids"] print("\033[2J\033[H", end="") # clears the terminal print_book("Yes", asks, bids) # No asks come from the Yes bids, and No bids from the Yes asks. no_asks = [(complement(price), shares) for price, shares in bids] no_bids = [(complement(price), shares) for price, shares in asks] print_book("No ", no_asks, no_bids) asyncio.run(main()) ``` To keep the stream running through disconnects, add the reconnection from [Stay connected](/websocket/how-tos/stay-connected). # Overview (/websocket) The WebSocket API streams live market data and the events of your own orders over one connection. You connect once, subscribe to the topics you need, and the server pushes each topic's messages as they happen. ## Connect [#connect] | Network | URL | | :--- | :--- | | BNB Mainnet | `wss://ws.predict.fun/ws` | | BNB Testnet | Not available yet | Connections are served only on the `/ws` path, over TLS. Send your API key when you connect, in one of two ways: - The `x-api-key` header, which is preferred. - The `apiKey` query parameter, for clients that can't set headers, such as browsers: `wss://ws.predict.fun/ws?apiKey=YOUR_API_KEY`. The key is checked once, at the handshake. A handshake that fails gets an HTTP status instead of a connection, such as `401` for an invalid key or `503` while the service is unavailable. ## Messages [#messages] Every message is a JSON text frame. Field names and values are case-sensitive, and timestamps are milliseconds since the Unix epoch. You send **requests**, with a `method`, a `requestId` of your choice and the request's `params`: ```json { "method": "subscribe", "requestId": 1, "params": ["predictOrderbook/123"] } ``` The server answers each request with a **response** (`type: "R"`) that carries the same `requestId`: ```json { "type": "R", "requestId": 1, "success": true } ``` Everything else the server sends is a **topic message** (`type: "M"`), with the `topic` it belongs to and its `data`: ```json { "type": "M", "topic": "predictOrderbook/123", "data": { "marketId": 123, "asks": [[0.62, 1500]], "bids": [[0.61, 2000]] } } ``` The requests are [Subscribe](/websocket/requests/subscribe), [Unsubscribe](/websocket/requests/unsubscribe) and [Set language](/websocket/requests/set-language). Each subscribe or unsubscribe names one topic. ### Errors [#errors] A request that fails gets a response with `success: false` and an `error`: ```json { "type": "R", "requestId": 1, "success": false, "error": { "code": "invalid_topic", "message": "unknown topic: predictOrderbok" } } ``` | `error.code` | Cause | | :--- | :--- | | `invalid_json` | The frame isn't a valid request, for example its `requestId` or `params` is missing. The response's `requestId` is `-1`. | | `invalid_topic` | The topic is missing or unknown, or one of its parameters is invalid. | | `invalid_params` | A [Set language](/websocket/requests/set-language) request has no language code. | | `invalid_credentials` | The JWT in a wallet events topic is invalid, revoked or expired. | | `internal_server_error` | Something failed on our side. Try again. | ## Topics [#topics] | Topic | What it sends | Latest value on subscribe | | :--- | :--- | :--- | | [`predictOrderbook/{marketId}`](/websocket/topics/order-book) | The market's whole order book, each time it changes. | Yes | | [`predictTopOfBook/{marketId}`](/websocket/topics/top-of-book) | The market's best bid and best ask, each time either changes. | Yes | | [`predictTradingStatus/{marketId}`](/websocket/topics/trading-status) | Changes to the market's `tradingStatus`. | Yes | | [`predictMarketStatus/{marketId}`](/websocket/topics/market-status) | Changes to the market's `status`, such as resolution. | Yes | | [`predictMarketChanged/{marketId}`](/websocket/topics/market-changed) | A notice that the market's details changed. | No | | [`predictCategoryChanged/{categoryId}`](/websocket/topics/category-changed) | A notice that the category's details changed. | No | | [`predictWalletEvents/{jwt}`](/websocket/topics/wallet-events) | The events of your wallet's orders. The topic holds a JWT from the REST API. | No | For the topics marked Yes, the latest value arrives right after the subscribe response, followed by live updates. It's best effort: when there's nothing to send yet, only updates arrive. ## Keep the connection open [#keep-the-connection-open] - **Heartbeats.** Every 15 seconds the server sends a `heartbeat` message. Echo its `data` back before the next one, or the server closes the connection. - **Reconnect notices.** Before a server shuts down, it sends a reconnect notice on the `system` topic, then closes the connection after `graceMs`. - **Message size.** A frame from the client can be at most 8 KiB. A larger one closes the connection. - **Keeping up.** Read messages as fast as they arrive. The server disconnects a client that falls too far behind. | Close code | Why the server closed the connection | | :--- | :--- | | `1000` | A heartbeat went unanswered, or a message from the client was over 8 KiB once decompressed. | | `1001` | The server is shutting down, after a reconnect notice. The reason is `server draining`. | | `1009` | A frame from the client was over 8 KiB. | | None | The client fell behind, or the server dropped its connections after a gap in delivery. The server sends no close frame, so browsers report `1006`. | Reconnect after every close you didn't start. Subscriptions don't carry over to a new connection, and messages sent while you were disconnected aren't replayed. After you reconnect, subscribe again and refetch what you track from the REST API. [Stay connected](/websocket/how-tos/stay-connected) shows how. ## How-tos [#how-tos] Show a market's Yes and No order book as it changes. Get the events of your wallet's orders as they happen. Answer heartbeats, and reconnect after a disconnect. The [client example](/websocket/client-example) is a complete TypeScript client with heartbeats, reconnection and typed messages. The AsyncAPI document behind these pages is at [`/asyncapi/websocket.json`](/asyncapi/websocket.json). You can use it to generate a client. # Get auth message (/api/auth/get-auth-message) `GET https://api.predict.fun/v1/auth/message` Get the message to sign to generate a JWT token. Do not hardcode this message as it is dynamic and can be subject to changes. ```yaml paths: /auth/message: get: tags: - Auth summary: Get auth message description: >- Get the message to sign to generate a JWT token. Do not hardcode this message as it is dynamic and can be subject to changes. responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/AuthMessageResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: AuthMessageData: type: object title: AuthMessageData description: Authentication message data required: - message properties: message: type: string description: The message to sign with your wallet AuthMessageResponse: type: object title: AuthMessageResponse description: Response for GET /v1/auth/message required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/AuthMessageData" ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null ``` # Get JWT with valid signature (/api/auth/get-jwt-with-valid-signature) `POST https://api.predict.fun/v1/auth` Provide a signature and get back a valid JWT that allows order actions. ```yaml paths: /auth: post: tags: - Auth summary: Get JWT with valid signature description: Provide a signature and get back a valid JWT that allows order actions. requestBody: content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/PostAuthRequest" required: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/AuthTokenResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: AuthTokenData: type: object title: AuthTokenData description: Authentication token data required: - token properties: token: type: string description: JWT token for authenticated requests AuthTokenResponse: type: object title: AuthTokenResponse description: Response for POST /v1/auth required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/AuthTokenData" ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null PostAuthRequest: type: object title: PostAuthRequest description: Request body for POST /v1/auth required: - signer - signature - message properties: signer: type: string description: The wallet address (checksummed) signature: type: string description: The signature of the message maxLength: 200 message: type: string description: The signed message (from GET /v1/auth/message) maxLength: 2048 ``` # Get categories (/api/categories/get-categories) `GET https://api.predict.fun/v1/categories` Get categories information and their markets. Markets within RESOLVED categories are not tradable. ```yaml paths: /categories: get: tags: - Categories summary: Get categories description: |- Get categories information and their markets. Markets within RESOLVED categories are not tradable. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 100. Defaults to 20. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: status schema: $ref: "#/components/schemas/CategoryStatusFilter" in: query description: >- `RESOLVED` returns only resolved categories, and any other value only unresolved ones. Omit it to get both. required: false deprecated: false explode: true - name: sort schema: $ref: "#/components/schemas/CategorySort" in: query description: >- Defaults to `PUBLISHED_AT_DESC`, most recently published first. `POPULAR` sorts like `VOLUME_24H_DESC`, and `VOLUME` like `VOLUME_ALL_DESC`. required: false deprecated: false explode: true - name: tagIds schema: type: string in: query description: >- Only categories with at least one of these tag IDs, comma-separated. Entries that are not numbers are ignored. required: false deprecated: false explode: true - name: marketVariant schema: $ref: "#/components/schemas/MarketVariant" in: query description: Only categories of this variant. Cannot be combined with `marketVariants`. required: false deprecated: false explode: true - name: marketVariants schema: type: array items: $ref: "#/components/schemas/MarketVariant" in: query description: |- Only categories of any of these variants (comma-separated or repeated). Cannot be combined with `marketVariant`. required: false deprecated: false explode: true - name: includeStats schema: type: boolean in: query description: >- Whether to fill in `stats` with volume and liquidity figures. Defaults to `false`, which leaves `stats` null. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CategoriesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CategoriesResponse: type: object title: CategoriesResponse description: Response for GET /v1/categories required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/CategoryWithStats" CategorySort: type: string description: Category sort options enum: - POPULAR - VOLUME - VOLUME_24H_DESC - VOLUME_ALL_DESC - PUBLISHED_AT_ASC - PUBLISHED_AT_DESC CategoryStatsData: type: object title: CategoryStatsData description: Category statistics data required: - totalLiquidityUsd - volumeTotalUsd - volume24hUsd - holdersCount properties: totalLiquidityUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double holdersCount: type: integer format: int64 CategoryStatus: type: string description: Category status enum: - OPEN - RESOLVED - REMOVED CategoryStatusFilter: type: string description: Category status filter for queries enum: - OPEN - ACTIVE - RESOLVED - REMOVED CategoryVariantDetails: type: object title: CategoryVariantDetails description: |- What a category is about, one key per kind of category. See [`MarketVariantDetails`]. properties: sports: description: Set for a sports category. allOf: - $ref: "#/components/schemas/SportsCategoryDetails" - description: Set for a sports category. crypto: description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. upDown: description: Set for an up down price category, whatever the asset class. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: Set for an up down price category, whatever the asset class. tweet: description: Set for a tweet count category. allOf: - $ref: "#/components/schemas/TweetCategoryDetails" - description: Set for a tweet count category. CategoryWithStats: type: object title: CategoryWithStats description: Category data with optional inline statistics. required: - id - slug - title - description - imageUrl - isNegRisk - protocol - isYieldBearing - marketVariant - createdAt - publishedAt - markets - startsAt - status - isVisible - resolutionProvider - tags properties: id: type: integer format: int64 slug: type: string title: type: string shortTitle: type: string description: Short, human-readable label, e.g. `"BTC Up or Down 15m"`. `null` when not available. seriesSlug: type: string description: >- Slug of the recurring series this category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. description: type: string imageUrl: type: string isNegRisk: type: boolean protocol: $ref: "#/components/schemas/Protocol" isYieldBearing: type: boolean negRiskOnChainId: type: string description: On-chain NegRisk market ID as a 32-byte hex string. marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this category is about, replacing `variantData`. allOf: - $ref: "#/components/schemas/CategoryVariantDetails" - description: What this category is about, replacing `variantData`. createdAt: type: string publishedAt: type: string markets: type: array items: $ref: "#/components/schemas/Market" startsAt: type: string endsAt: type: string tradingSession: description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. allOf: - $ref: "#/components/schemas/TradingSession" - description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. status: $ref: "#/components/schemas/CategoryStatus" isVisible: type: boolean resolutionProvider: $ref: "#/components/schemas/ResolutionProvider" tags: type: array items: $ref: "#/components/schemas/Tag" teams: type: array description: |- Deprecated in favour of `variantDetails.sports.teams`. The teams playing, where the category is a fixture. items: $ref: "#/components/schemas/Team" deprecated: true parentSlug: type: string description: The slug of the parent category, when this is a prop/child market. stats: $ref: "#/components/schemas/CategoryStatsData" CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double Protocol: type: string description: |- The protocol a category's markets trade on. Every market in a category shares it, and it decides which exchange contracts a client talks to. enum: - LEGACY - PREDICT ResolutionProvider: type: string description: Resolution provider for a category (maps to `resolution_provider_type` table). enum: - PREDICT_DOT_FUN - THREE_PO - CHAINLINK - ATLAS RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string ScoreboardSource: type: string description: The source a scoreboard is built on. enum: - GRID - POLYMARKET - PREDICT_DOT_FUN SportsCategoryDetails: type: object title: SportsCategoryDetails description: What a sports category is about. required: - provider - teams properties: provider: description: The provider that supplies the fixture. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that supplies the fixture. providerEventId: type: string description: The provider's own id for the fixture, where it has one. teams: type: array description: The two sides of the fixture. items: $ref: "#/components/schemas/SportsEventTeam" scoreboard: description: Absent until a source has reported on the match. allOf: - $ref: "#/components/schemas/SportsScoreboard" - description: Absent until a source has reported on the match. SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsScoreboard: type: object title: SportsScoreboard description: How a match stands. required: - source - score - games properties: source: description: The source the record is built on. allOf: - $ref: "#/components/schemas/ScoreboardSource" - description: The source the record is built on. bestOf: type: integer format: int32 isLive: type: boolean finished: type: boolean currentGame: type: integer format: int32 description: Counted from one. period: type: string description: "As the feed labels it: `1H`, `Q3`, `2/3`." clock: type: string description: >- The game clock as the live feed shows it. Its meaning depends on the sport: time left in the period for basketball, time played for soccer. score: type: array description: Games won in a series, the match score otherwise. items: $ref: "#/components/schemas/SportsTeamScore" games: type: array items: $ref: "#/components/schemas/SportsScoreboardGame" SportsScoreboardGame: type: object title: SportsScoreboardGame description: One game of a best-of series. required: - gameNumber - isLive - finished - scores properties: gameNumber: type: integer format: int32 description: Counted from one. isLive: type: boolean finished: type: boolean winnerTeamId: type: integer format: int64 scores: type: array description: Empty when only the winner is known. items: $ref: "#/components/schemas/SportsTeamScore" SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. SportsTeamScore: type: object title: SportsTeamScore description: One team's count in a series or a game. required: - teamId - score properties: teamId: type: integer format: int64 description: The id the category's `teams` entries carry. score: type: integer format: int32 description: Games won, goals, rounds or kills, by what the entry is. Tag: type: object title: Tag description: Tag data required: - id - name properties: id: type: string description: bigint string name: type: string level: type: integer format: int32 description: >- Hierarchy level of the tag (1, 2, or 3), or null for flat tags. Tags form a tree up to three levels deep, e.g. Sports -> Soccer -> EPL. parentId: type: string description: >- Id of the parent tag as a bigint string, or null for root and flat tags. Together with `level` this allows rendering nested tag menus. makerRebateBps: type: integer format: int32 description: >- Maker rebate in basis points for fills on categories carrying this tag, or null if no rebate applies. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TradingSession: type: object title: TradingSession description: Exchange session during which the settlement price is live, without trading buffers. required: - opensAt - closesAt properties: opensAt: type: string description: Session opening time as an ISO 8601 UTC timestamp with millisecond precision. closesAt: type: string description: Session closing time as an ISO 8601 UTC timestamp with millisecond precision. TweetCategoryDetails: type: object title: TweetCategoryDetails description: What a tweet count category is about. required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: The account whose posts are counted. tweetCount: type: integer format: int64 description: Posts counted so far within the category's window. TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range UpDownCategoryDetails: type: object title: UpDownCategoryDetails description: What an up down price category is about, whatever the asset class. required: - priceFeedProvider properties: priceFeedProvider: description: Who publishes the price this category settles against. allOf: - $ref: "#/components/schemas/PriceFeedProvider" - description: Who publishes the price this category settles against. priceFeedId: type: string description: The feed's own identifier, where it has one. priceFeedSymbol: type: string description: The symbol the feed publishes under, eg. `BTCUSDT` or `SPYUSDT`. startPrice: type: number format: double description: The price at the start of the window, once the window has opened. endPrice: type: number format: double description: The price at the end of the window, once it has closed. VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get category by slug (/api/categories/get-category-by-slug) `GET https://api.predict.fun/v1/categories/{slug}` Get category information and its markets by slug. ```yaml paths: /categories/{slug}: get: tags: - Categories summary: Get category by slug description: Get category information and its markets by slug. parameters: - name: slug schema: type: string maxLength: 256 in: path description: The category's slug. required: true deprecated: false explode: true - name: includeStats schema: type: boolean in: query description: >- Whether to fill in `stats` with volume and liquidity figures. Defaults to `false`, which leaves `stats` null. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CategoryResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CategoryResponse: type: object title: CategoryResponse description: Response for GET /v1/categories/:slug required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/CategoryWithStats" CategoryStatsData: type: object title: CategoryStatsData description: Category statistics data required: - totalLiquidityUsd - volumeTotalUsd - volume24hUsd - holdersCount properties: totalLiquidityUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double holdersCount: type: integer format: int64 CategoryStatus: type: string description: Category status enum: - OPEN - RESOLVED - REMOVED CategoryVariantDetails: type: object title: CategoryVariantDetails description: |- What a category is about, one key per kind of category. See [`MarketVariantDetails`]. properties: sports: description: Set for a sports category. allOf: - $ref: "#/components/schemas/SportsCategoryDetails" - description: Set for a sports category. crypto: description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. upDown: description: Set for an up down price category, whatever the asset class. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: Set for an up down price category, whatever the asset class. tweet: description: Set for a tweet count category. allOf: - $ref: "#/components/schemas/TweetCategoryDetails" - description: Set for a tweet count category. CategoryWithStats: type: object title: CategoryWithStats description: Category data with optional inline statistics. required: - id - slug - title - description - imageUrl - isNegRisk - protocol - isYieldBearing - marketVariant - createdAt - publishedAt - markets - startsAt - status - isVisible - resolutionProvider - tags properties: id: type: integer format: int64 slug: type: string title: type: string shortTitle: type: string description: Short, human-readable label, e.g. `"BTC Up or Down 15m"`. `null` when not available. seriesSlug: type: string description: >- Slug of the recurring series this category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. description: type: string imageUrl: type: string isNegRisk: type: boolean protocol: $ref: "#/components/schemas/Protocol" isYieldBearing: type: boolean negRiskOnChainId: type: string description: On-chain NegRisk market ID as a 32-byte hex string. marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this category is about, replacing `variantData`. allOf: - $ref: "#/components/schemas/CategoryVariantDetails" - description: What this category is about, replacing `variantData`. createdAt: type: string publishedAt: type: string markets: type: array items: $ref: "#/components/schemas/Market" startsAt: type: string endsAt: type: string tradingSession: description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. allOf: - $ref: "#/components/schemas/TradingSession" - description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. status: $ref: "#/components/schemas/CategoryStatus" isVisible: type: boolean resolutionProvider: $ref: "#/components/schemas/ResolutionProvider" tags: type: array items: $ref: "#/components/schemas/Tag" teams: type: array description: |- Deprecated in favour of `variantDetails.sports.teams`. The teams playing, where the category is a fixture. items: $ref: "#/components/schemas/Team" deprecated: true parentSlug: type: string description: The slug of the parent category, when this is a prop/child market. stats: $ref: "#/components/schemas/CategoryStatsData" CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double Protocol: type: string description: |- The protocol a category's markets trade on. Every market in a category shares it, and it decides which exchange contracts a client talks to. enum: - LEGACY - PREDICT ResolutionProvider: type: string description: Resolution provider for a category (maps to `resolution_provider_type` table). enum: - PREDICT_DOT_FUN - THREE_PO - CHAINLINK - ATLAS RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string ScoreboardSource: type: string description: The source a scoreboard is built on. enum: - GRID - POLYMARKET - PREDICT_DOT_FUN SportsCategoryDetails: type: object title: SportsCategoryDetails description: What a sports category is about. required: - provider - teams properties: provider: description: The provider that supplies the fixture. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that supplies the fixture. providerEventId: type: string description: The provider's own id for the fixture, where it has one. teams: type: array description: The two sides of the fixture. items: $ref: "#/components/schemas/SportsEventTeam" scoreboard: description: Absent until a source has reported on the match. allOf: - $ref: "#/components/schemas/SportsScoreboard" - description: Absent until a source has reported on the match. SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsScoreboard: type: object title: SportsScoreboard description: How a match stands. required: - source - score - games properties: source: description: The source the record is built on. allOf: - $ref: "#/components/schemas/ScoreboardSource" - description: The source the record is built on. bestOf: type: integer format: int32 isLive: type: boolean finished: type: boolean currentGame: type: integer format: int32 description: Counted from one. period: type: string description: "As the feed labels it: `1H`, `Q3`, `2/3`." clock: type: string description: >- The game clock as the live feed shows it. Its meaning depends on the sport: time left in the period for basketball, time played for soccer. score: type: array description: Games won in a series, the match score otherwise. items: $ref: "#/components/schemas/SportsTeamScore" games: type: array items: $ref: "#/components/schemas/SportsScoreboardGame" SportsScoreboardGame: type: object title: SportsScoreboardGame description: One game of a best-of series. required: - gameNumber - isLive - finished - scores properties: gameNumber: type: integer format: int32 description: Counted from one. isLive: type: boolean finished: type: boolean winnerTeamId: type: integer format: int64 scores: type: array description: Empty when only the winner is known. items: $ref: "#/components/schemas/SportsTeamScore" SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. SportsTeamScore: type: object title: SportsTeamScore description: One team's count in a series or a game. required: - teamId - score properties: teamId: type: integer format: int64 description: The id the category's `teams` entries carry. score: type: integer format: int32 description: Games won, goals, rounds or kills, by what the entry is. Tag: type: object title: Tag description: Tag data required: - id - name properties: id: type: string description: bigint string name: type: string level: type: integer format: int32 description: >- Hierarchy level of the tag (1, 2, or 3), or null for flat tags. Tags form a tree up to three levels deep, e.g. Sports -> Soccer -> EPL. parentId: type: string description: >- Id of the parent tag as a bigint string, or null for root and flat tags. Together with `level` this allows rendering nested tag menus. makerRebateBps: type: integer format: int32 description: >- Maker rebate in basis points for fills on categories carrying this tag, or null if no rebate applies. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TradingSession: type: object title: TradingSession description: Exchange session during which the settlement price is live, without trading buffers. required: - opensAt - closesAt properties: opensAt: type: string description: Session opening time as an ISO 8601 UTC timestamp with millisecond precision. closesAt: type: string description: Session closing time as an ISO 8601 UTC timestamp with millisecond precision. TweetCategoryDetails: type: object title: TweetCategoryDetails description: What a tweet count category is about. required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: The account whose posts are counted. tweetCount: type: integer format: int64 description: Posts counted so far within the category's window. TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range UpDownCategoryDetails: type: object title: UpDownCategoryDetails description: What an up down price category is about, whatever the asset class. required: - priceFeedProvider properties: priceFeedProvider: description: Who publishes the price this category settles against. allOf: - $ref: "#/components/schemas/PriceFeedProvider" - description: Who publishes the price this category settles against. priceFeedId: type: string description: The feed's own identifier, where it has one. priceFeedSymbol: type: string description: The symbol the feed publishes under, eg. `BTCUSDT` or `SPYUSDT`. startPrice: type: number format: double description: The price at the start of the window, once the window has opened. endPrice: type: number format: double description: The price at the end of the window, once it has closed. VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get category statistics (/api/categories/get-category-statistics) `GET https://api.predict.fun/v1/categories/{id}/stats` Get statistics about a specific category including volume and liquidity. ```yaml paths: /categories/{id}/stats: get: tags: - Categories summary: Get category statistics description: Get statistics about a specific category including volume and liquidity. parameters: - name: id schema: type: integer format: int64 in: path description: The category's ID. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CategoryStatsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CategoryStatsData: type: object title: CategoryStatsData description: Category statistics data required: - totalLiquidityUsd - volumeTotalUsd - volume24hUsd - holdersCount properties: totalLiquidityUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double holdersCount: type: integer format: int64 CategoryStatsResponse: type: object title: CategoryStatsResponse description: Response for GET /v1/categories/:id/stats required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/CategoryStatsData" ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null ``` # Get category translations (/api/categories/get-category-translations) `GET https://api.predict.fun/v1/categories/{slug}/translations` Fetch all available translations for a category, its markets, and their outcomes for the requested languages in a single call. `languages` is a comma-separated list of BCP 47 language codes (e.g. `zh-CN,ja-JP,ko-KR`). If a language code has no completed translations it is omitted from the response rather than returning an error. ```yaml paths: /categories/{slug}/translations: get: tags: - Categories summary: Get category translations description: >- Fetch all available translations for a category, its markets, and their outcomes for the requested languages in a single call. `languages` is a comma-separated list of BCP 47 language codes (e.g. `zh-CN,ja-JP,ko-KR`). If a language code has no completed translations it is omitted from the response rather than returning an error. parameters: - name: slug schema: type: string maxLength: 256 in: path description: The category's slug. required: true deprecated: false explode: true - name: languages schema: type: string maxLength: 256 in: query description: >- Language codes to return, comma-separated, as `GET /v1/languages` lists them. Without any, `translations` comes back empty. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CategoryTranslationsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CategoryTranslationData: type: object title: CategoryTranslationData description: All translated content for one language. required: - category - markets properties: category: $ref: "#/components/schemas/CategoryTranslationFields" markets: type: array items: $ref: "#/components/schemas/MarketTranslationData" CategoryTranslationFields: type: object title: CategoryTranslationFields description: Translated fields for the category itself. properties: title: type: string shortTitle: type: string CategoryTranslationsResponse: type: object title: CategoryTranslationsResponse description: Response for GET /v1/categories/:slug/translations required: - translations properties: translations: type: object description: Map from language code (e.g. `"zh-CN"`) to its translations. additionalProperties: $ref: "#/components/schemas/CategoryTranslationData" ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketTranslationData: type: object title: MarketTranslationData description: Translated fields for a single market. required: - id - outcomes properties: id: type: integer format: int64 title: type: string question: type: string description: type: string outcomes: type: array items: $ref: "#/components/schemas/OutcomeTranslation" OutcomeTranslation: type: object title: OutcomeTranslation description: Translated fields for a single outcome. required: - index - name properties: index: type: integer format: int32 description: Outcome index (matches `Outcome.index_set`). name: type: string ``` # Get all tags (/api/categories/get-all-tags) `GET https://api.predict.fun/v1/tags` Get all supported tags for filtering categories and markets. ```yaml paths: /tags: get: tags: - Categories summary: Get all tags description: Get all supported tags for filtering categories and markets. responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/TagsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null Tag: type: object title: Tag description: Tag data required: - id - name properties: id: type: string description: bigint string name: type: string level: type: integer format: int32 description: >- Hierarchy level of the tag (1, 2, or 3), or null for flat tags. Tags form a tree up to three levels deep, e.g. Sports -> Soccer -> EPL. parentId: type: string description: >- Id of the parent tag as a bigint string, or null for root and flat tags. Together with `level` this allows rendering nested tag menus. makerRebateBps: type: integer format: int32 description: >- Maker rebate in basis points for fills on categories carrying this tag, or null if no rebate applies. TagsResponse: type: object title: TagsResponse description: Response for GET /v1/tags required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/Tag" ``` # Get markets (/api/markets/get-markets) `GET https://api.predict.fun/v1/markets` Get information about markets with optional filters and sorting. ```yaml paths: /markets: get: tags: - Markets summary: Get markets description: Get information about markets with optional filters and sorting. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 100. Defaults to 20. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: isBoosted schema: type: boolean in: query description: Deprecated. Boosts are no longer used. required: false deprecated: true explode: true - name: hasActiveRewards schema: type: boolean in: query description: >- Only markets with a reward period running now (`true`), or only markets without one (`false`). Omit it to get both. required: false deprecated: false explode: true - name: status schema: $ref: "#/components/schemas/MarketStatusFilter" in: query description: >- Only open or only resolved markets. Paused markets count as neither, so they are only listed when this is omitted. required: false deprecated: false explode: true - name: tagIds schema: type: string in: query description: >- Only markets whose category has at least one of these tag IDs, comma-separated. Entries that are not numbers are ignored. required: false deprecated: false explode: true - name: marketVariant schema: $ref: "#/components/schemas/MarketVariant" in: query description: Only markets of this variant. required: false deprecated: false explode: true - name: sort schema: $ref: "#/components/schemas/MarketSort" in: query description: >- Without it, markets are grouped by category, with categories in the platform's own ranking and markets in their order within the category. required: false deprecated: false explode: true - name: includeStats schema: type: boolean in: query description: >- Whether to fill in `stats` with volume and liquidity figures. Defaults to `false`, which leaves `stats` null. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/MarketsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketSort: type: string description: Market sort options enum: - CHANCE_24H_CHANGE_ASC - CHANCE_24H_CHANGE_DESC - VOLUME_24H_ASC - VOLUME_24H_DESC - VOLUME_24H_CHANGE_ASC - VOLUME_24H_CHANGE_DESC - VOLUME_TOTAL_ASC - VOLUME_TOTAL_DESC - REWARD_RATE_ASC - REWARD_RATE_DESC MarketStatsData: type: object title: MarketStatsData description: Market statistics data required: - totalLiquidityUsd - liquidity3cAskUsd - volumeTotalUsd - volume24hUsd properties: totalLiquidityUsd: type: number format: double liquidity3cAskUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketStatusFilter: type: string description: Market status filter for queries enum: - OPEN - RESOLVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. MarketWithStats: type: object title: MarketWithStats description: Market data with optional inline statistics. required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" stats: $ref: "#/components/schemas/MarketStatsData" MarketsResponse: type: object title: MarketsResponse description: Response for GET /v1/markets required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/MarketWithStats" Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get market by ID (/api/markets/get-market-by-id) `GET https://api.predict.fun/v1/markets/{id}` Get detailed information about a specific market by its ID. ```yaml paths: /markets/{id}: get: tags: - Markets summary: Get market by ID description: Get detailed information about a specific market by its ID. parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true - name: includeStats schema: type: boolean in: query description: >- Whether to fill in `stats` with volume and liquidity figures. Defaults to `false`, which leaves `stats` null. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/MarketResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" MarketResponse: type: object title: MarketResponse description: Response for GET /v1/markets/:id required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/MarketWithStats" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatsData: type: object title: MarketStatsData description: Market statistics data required: - totalLiquidityUsd - liquidity3cAskUsd - volumeTotalUsd - volume24hUsd properties: totalLiquidityUsd: type: number format: double liquidity3cAskUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. MarketWithStats: type: object title: MarketWithStats description: Market data with optional inline statistics. required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" stats: $ref: "#/components/schemas/MarketStatsData" Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get market statistics (/api/markets/get-market-statistics) `GET https://api.predict.fun/v1/markets/{id}/stats` Get statistics about a specific market including volume and liquidity. ```yaml paths: /markets/{id}/stats: get: tags: - Markets summary: Get market statistics description: Get statistics about a specific market including volume and liquidity. parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/MarketStatsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketStatsData: type: object title: MarketStatsData description: Market statistics data required: - totalLiquidityUsd - liquidity3cAskUsd - volumeTotalUsd - volume24hUsd properties: totalLiquidityUsd: type: number format: double liquidity3cAskUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double MarketStatsResponse: type: object title: MarketStatsResponse description: Response for GET /v1/markets/:id/stats required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/MarketStatsData" ``` # Get market last sale (/api/markets/get-market-last-sale) `GET https://api.predict.fun/v1/markets/{id}/last-sale` Get the last sale information for a specific market. ```yaml paths: /markets/{id}/last-sale: get: tags: - Markets summary: Get market last sale description: Get the last sale information for a specific market. parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/LastSaleResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null LastSaleData: type: object title: LastSaleData description: Last sale information required: - quoteType - outcome - priceInCurrency - strategy properties: quoteType: $ref: "#/components/schemas/QuoteType" outcome: $ref: "#/components/schemas/OutcomeSide" priceInCurrency: type: string strategy: $ref: "#/components/schemas/OrderStrategy" LastSaleResponse: type: object title: LastSaleResponse description: Response for GET /v1/markets/:id/last-sale required: - success properties: success: type: boolean data: $ref: "#/components/schemas/LastSaleData" OrderStrategy: type: string description: Order execution strategy. enum: - MARKET - LIMIT OutcomeSide: type: string description: "Outcome side in a binary market: yes or no." enum: - Yes - No QuoteType: type: string description: "Quote type: ask or bid." enum: - Ask - Bid ``` # Get market orderbook (/api/markets/get-market-orderbook) `GET https://api.predict.fun/v1/markets/{id}/orderbook` Get the orderbook for a specific market. The order book stores prices based on the Yes outcome. ```yaml paths: /markets/{id}/orderbook: get: tags: - Markets summary: Get market orderbook description: |- Get the orderbook for a specific market. The order book stores prices based on the Yes outcome. parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/OrderbookResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null LastOrderSettled: type: object title: LastOrderSettled description: Last order settled in orderbook required: - id - price - kind - marketId - side - outcome properties: id: type: string price: type: string kind: type: string marketId: type: integer format: int64 side: $ref: "#/components/schemas/QuoteType" outcome: $ref: "#/components/schemas/OutcomeSide" OrderbookData: type: object title: OrderbookData description: Orderbook data (single object, not array) required: - marketId - updateTimestampMs - asks - bids properties: marketId: type: integer format: int64 updateTimestampMs: type: integer format: int64 lastOrderSettled: $ref: "#/components/schemas/LastOrderSettled" asks: type: array description: Array of [price, size] tuples items: type: array items: type: number format: double bids: type: array description: Array of [price, size] tuples items: type: array items: type: number format: double OrderbookResponse: type: object title: OrderbookResponse description: Response for GET /v1/markets/:id/orderbook required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/OrderbookData" OutcomeSide: type: string description: "Outcome side in a binary market: yes or no." enum: - Yes - No QuoteType: type: string description: "Quote type: ask or bid." enum: - Ask - Bid ``` # Get market quote (/api/markets/get-market-quote) `GET https://api.predict.fun/v1/markets/{id}/quote` Get the best bid and best ask resting on a market's order book, quoted against the outcome whose `indexSet` is 1, as the book itself is. Prefer this over the full order book when only the current price matters, and over the quotes embedded in a market payload, which are deprecated. ```yaml paths: /markets/{id}/quote: get: tags: - Markets summary: Get market quote description: >- Get the best bid and best ask resting on a market's order book, quoted against the outcome whose `indexSet` is 1, as the book itself is. Prefer this over the full order book when only the current price matters, and over the quotes embedded in a market payload, which are deprecated. parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/QuoteResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketQuote: type: object title: MarketQuote description: >- Top of a market's order book: the best price resting on each side. This is the lightweight alternative to fetching a whole order book, or to reading the quotes that used to be embedded in a market payload. Both sides come from a single reading, so the two ends of a spread are always observed at the same instant. Prices are quoted against the outcome whose `indexSet` is 1, the same convention the order book itself uses. To price the complementary outcome, swap the sides and subtract from one: its best bid is `1 - bestAsk.price` and its best ask is `1 - bestBid.price`, at the same sizes. Round the result to six decimal places, or binary floating point will hand you values like `0.17000000000000004`. A quote reports the order book as it stands and says nothing about whether the market is still trading. Read the market itself for that, and note that an absent quote means only that no book is being maintained. required: - marketId - updateTimestampMs properties: marketId: type: integer format: int64 updateTimestampMs: type: integer format: int64 description: >- When this book last changed, in unix milliseconds. Use it to discard a quote that arrives out of order. bestBid: description: Absent when nobody is bidding. allOf: - $ref: "#/components/schemas/PriceLevel" - description: Absent when nobody is bidding. bestAsk: description: Absent when nobody is offering. allOf: - $ref: "#/components/schemas/PriceLevel" - description: Absent when nobody is offering. PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double QuoteResponse: type: object title: QuoteResponse description: Response for GET /v1/markets/:id/quote required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/MarketQuote" ``` # Get market timeseries (/api/markets/get-market-timeseries) `GET https://api.predict.fun/v1/markets/{id}/timeseries` ```yaml paths: /markets/{id}/timeseries: get: tags: - Markets summary: Get market timeseries parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true - name: metric schema: $ref: "#/components/schemas/Metric" in: query description: The metric to query required: true deprecated: false explode: true - name: resolution schema: $ref: "#/components/schemas/Resolution" in: query description: Resolution (bucket size) for aggregation required: false deprecated: false explode: true - name: from schema: type: integer format: int64 in: query description: >- Start of the time range in unix seconds, inclusive. It must be before `to` and at most 365 days earlier. required: true deprecated: false explode: true - name: to schema: type: integer format: int64 in: query description: End of the time range in unix seconds, inclusive. Defaults to now. required: false deprecated: false explode: true - name: limit schema: type: integer format: int32 in: query description: >- Most data points to return, from 1 to 250. Defaults to 50, and a value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/TimeseriesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null Metric: type: string description: The metric to query enum: - chance Resolution: type: string description: Resolution (bucket size) for timeseries data enum: - 1m - 5m - 1h - 1d - 1w - 1M TimeseriesData: type: object title: TimeseriesData description: Inner data for the timeseries response required: - resolution - series properties: resolution: description: The resolution of the returned data points allOf: - $ref: "#/components/schemas/Resolution" - description: The resolution of the returned data points series: type: array description: Data points ordered by time ascending items: $ref: "#/components/schemas/TimeseriesPoint" TimeseriesPoint: type: object title: TimeseriesPoint description: A single timeseries data point required: - x - y properties: x: type: number format: double description: Unix timestamp (seconds) y: type: number format: double description: The metric value TimeseriesResponse: type: object title: TimeseriesResponse description: Response for market timeseries required: - success - data properties: success: type: boolean cursor: type: string data: $ref: "#/components/schemas/TimeseriesData" ``` # Get latest market timeseries value (/api/markets/get-latest-market-timeseries-value) `GET https://api.predict.fun/v1/markets/{id}/timeseries/latest` ```yaml paths: /markets/{id}/timeseries/latest: get: tags: - Markets summary: Get latest market timeseries value parameters: - name: id schema: type: integer format: int64 in: path description: The market's ID. required: true deprecated: false explode: true - name: metric schema: $ref: "#/components/schemas/Metric" in: query description: The metric to query required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/TimeseriesLatestResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null Metric: type: string description: The metric to query enum: - chance TimeseriesLatestData: type: object title: TimeseriesLatestData description: Inner data for the latest timeseries response required: - x - y properties: x: type: number format: double description: Unix timestamp (seconds) y: type: number format: double description: The metric value TimeseriesLatestResponse: type: object title: TimeseriesLatestResponse description: Response for market timeseries latest required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/TimeseriesLatestData" ``` # Get orderbooks for multiple markets (/api/markets/get-orderbooks-for-multiple-markets) `GET https://api.predict.fun/v1/markets/orderbooks` Returns orderbooks for up to 100 markets in a single call. Markets without an orderbook are omitted from the response. ```yaml paths: /markets/orderbooks: get: tags: - Markets summary: Get orderbooks for multiple markets description: |- Returns orderbooks for up to 100 markets in a single call. Markets without an orderbook are omitted from the response. parameters: - name: ids schema: type: array items: type: integer format: int64 in: query description: Up to 100 market IDs, comma-separated or repeated. A longer list is rejected. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/OrderbooksResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null LastOrderSettled: type: object title: LastOrderSettled description: Last order settled in orderbook required: - id - price - kind - marketId - side - outcome properties: id: type: string price: type: string kind: type: string marketId: type: integer format: int64 side: $ref: "#/components/schemas/QuoteType" outcome: $ref: "#/components/schemas/OutcomeSide" OrderbookData: type: object title: OrderbookData description: Orderbook data (single object, not array) required: - marketId - updateTimestampMs - asks - bids properties: marketId: type: integer format: int64 updateTimestampMs: type: integer format: int64 lastOrderSettled: $ref: "#/components/schemas/LastOrderSettled" asks: type: array description: Array of [price, size] tuples items: type: array items: type: number format: double bids: type: array description: Array of [price, size] tuples items: type: array items: type: number format: double OrderbooksResponse: type: object title: OrderbooksResponse description: Response for GET /v1/markets/orderbooks required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/OrderbookData" OutcomeSide: type: string description: "Outcome side in a binary market: yes or no." enum: - Yes - No QuoteType: type: string description: "Quote type: ask or bid." enum: - Ask - Bid ``` # Get quotes for multiple markets (/api/markets/get-quotes-for-multiple-markets) `GET https://api.predict.fun/v1/markets/quotes` Returns a quote for up to 500 markets in a single call. This is the cheapest way to keep a board of prices current. Markets without a live book are omitted from the response rather than returned empty, so the result may be shorter than the request. ```yaml paths: /markets/quotes: get: tags: - Markets summary: Get quotes for multiple markets description: |- Returns a quote for up to 500 markets in a single call. This is the cheapest way to keep a board of prices current. Markets without a live book are omitted from the response rather than returned empty, so the result may be shorter than the request. parameters: - name: ids schema: type: array items: type: integer format: int64 in: query description: Up to 500 market IDs, comma-separated or repeated. A longer list is rejected. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/QuotesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketQuote: type: object title: MarketQuote description: >- Top of a market's order book: the best price resting on each side. This is the lightweight alternative to fetching a whole order book, or to reading the quotes that used to be embedded in a market payload. Both sides come from a single reading, so the two ends of a spread are always observed at the same instant. Prices are quoted against the outcome whose `indexSet` is 1, the same convention the order book itself uses. To price the complementary outcome, swap the sides and subtract from one: its best bid is `1 - bestAsk.price` and its best ask is `1 - bestBid.price`, at the same sizes. Round the result to six decimal places, or binary floating point will hand you values like `0.17000000000000004`. A quote reports the order book as it stands and says nothing about whether the market is still trading. Read the market itself for that, and note that an absent quote means only that no book is being maintained. required: - marketId - updateTimestampMs properties: marketId: type: integer format: int64 updateTimestampMs: type: integer format: int64 description: >- When this book last changed, in unix milliseconds. Use it to discard a quote that arrives out of order. bestBid: description: Absent when nobody is bidding. allOf: - $ref: "#/components/schemas/PriceLevel" - description: Absent when nobody is bidding. bestAsk: description: Absent when nobody is offering. allOf: - $ref: "#/components/schemas/PriceLevel" - description: Absent when nobody is offering. PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double QuotesResponse: type: object title: QuotesResponse description: Response for GET /v1/markets/quotes required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/MarketQuote" ``` # Get timeseries for multiple markets (/api/markets/get-timeseries-for-multiple-markets) `GET https://api.predict.fun/v1/markets/timeseries` Paginated chance points for up to 20 markets in one call. The cursor is a single shared timestamp that advances across all markets. ```yaml paths: /markets/timeseries: get: tags: - Markets summary: Get timeseries for multiple markets description: |- Paginated chance points for up to 20 markets in one call. The cursor is a single shared timestamp that advances across all markets. parameters: - name: ids schema: type: array items: type: integer format: int64 in: query description: Up to 20 market IDs, comma-separated or repeated. A longer list is rejected. required: true deprecated: false explode: true - name: metric schema: $ref: "#/components/schemas/Metric" in: query description: The metric to query. required: true deprecated: false explode: true - name: resolution schema: $ref: "#/components/schemas/Resolution" in: query description: >- Bucket size of the data points, the same for every market. Defaults to one chosen from the length of the time range. required: false deprecated: false explode: true - name: from schema: type: integer format: int64 in: query description: >- Start of the time range in unix seconds, inclusive. It must be before `to` and at most 365 days earlier. required: true deprecated: false explode: true - name: to schema: type: integer format: int64 in: query description: End of the time range in unix seconds, inclusive. Defaults to now. required: false deprecated: false explode: true - name: limit schema: type: integer format: int32 in: query description: >- Most distinct timestamps to return across all markets, from 1 to 250. Defaults to 50, and a value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/TimeseriesBatchResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketTimeseries: type: object title: MarketTimeseries required: - marketId - series properties: marketId: type: integer format: int64 series: type: array items: $ref: "#/components/schemas/TimeseriesPoint" Metric: type: string description: The metric to query enum: - chance Resolution: type: string description: Resolution (bucket size) for timeseries data enum: - 1m - 5m - 1h - 1d - 1w - 1M TimeseriesBatchData: type: object title: TimeseriesBatchData description: "`resolution` is shared across all markets in the request." required: - resolution - markets properties: resolution: $ref: "#/components/schemas/Resolution" markets: type: array items: $ref: "#/components/schemas/MarketTimeseries" TimeseriesBatchResponse: type: object title: TimeseriesBatchResponse required: - success - data properties: success: type: boolean cursor: type: string data: $ref: "#/components/schemas/TimeseriesBatchData" TimeseriesPoint: type: object title: TimeseriesPoint description: A single timeseries data point required: - x - y properties: x: type: number format: double description: Unix timestamp (seconds) y: type: number format: double description: The metric value ``` # Get latest timeseries value for multiple markets (/api/markets/get-latest-timeseries-value-for-multiple-markets) `GET https://api.predict.fun/v1/markets/timeseries/latest` Returns the current chance for up to 20 markets in one call. Missing markets are omitted. ```yaml paths: /markets/timeseries/latest: get: tags: - Markets summary: Get latest timeseries value for multiple markets description: |- Returns the current chance for up to 20 markets in one call. Missing markets are omitted. parameters: - name: ids schema: type: array items: type: integer format: int64 in: query description: Up to 20 market IDs, comma-separated or repeated. A longer list is rejected. required: true deprecated: false explode: true - name: metric schema: $ref: "#/components/schemas/Metric" in: query description: The metric to query. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/TimeseriesLatestBatchResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketTimeseriesLatest: type: object title: MarketTimeseriesLatest required: - marketId - x - y properties: marketId: type: integer format: int64 x: type: number format: double description: Unix timestamp (seconds) y: type: number format: double description: The metric value Metric: type: string description: The metric to query enum: - chance TimeseriesLatestBatchResponse: type: object title: TimeseriesLatestBatchResponse required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/MarketTimeseriesLatest" ``` # Get market status logs (/api/markets/get-market-status-logs) `GET https://api.predict.fun/v1/markets/status-logs` Returns the on-chain status transitions (registration, price proposal, dispute, pause, resolution) of up to 100 markets in a single call, each market's oldest first. Markets that are not listed, or have no transitions yet, are omitted from the response. ```yaml paths: /markets/status-logs: get: tags: - Markets summary: Get market status logs description: |- Returns the on-chain status transitions (registration, price proposal, dispute, pause, resolution) of up to 100 markets in a single call, each market's oldest first. Markets that are not listed, or have no transitions yet, are omitted from the response. parameters: - name: ids schema: type: array items: type: integer format: int64 in: query description: Market ids (comma-separated or repeated) required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/MarketStatusLogsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketStatusLog: type: object title: MarketStatusLog description: One on-chain status transition of a market required: - status - timestamp - transactionHash - resolutionProvider properties: status: $ref: "#/components/schemas/MarketStatus" timestamp: type: string description: ISO 8601 UTC timestamp of the transaction that made the transition. transactionHash: type: string logIndex: type: integer format: int32 description: >- Position of the event within its block. `null` for transitions with no block context. proposedOutcomeId: type: integer format: int32 description: |- The outcome proposed for resolution: 1 and 2 are the outcomes with that `indexSet`, 3 is a draw. Only set on `PRICE_PROPOSED` entries. proposedOutcomeLabel: type: string description: |- Name of the proposed outcome, e.g. `"Yes"` or `"Draw"`. Only set on `PRICE_PROPOSED` entries. resolutionProvider: description: The oracle that made the transition. allOf: - $ref: "#/components/schemas/ResolutionProvider" - description: The oracle that made the transition. MarketStatusLogs: type: object title: MarketStatusLogs description: A market's status transitions, oldest first required: - marketId - logs properties: marketId: type: integer format: int64 logs: type: array items: $ref: "#/components/schemas/MarketStatusLog" MarketStatusLogsResponse: type: object title: MarketStatusLogsResponse description: Response for GET /v1/markets/status-logs required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/MarketStatusLogs" ResolutionProvider: type: string description: Resolution provider for a category (maps to `resolution_provider_type` table). enum: - PREDICT_DOT_FUN - THREE_PO - CHAINLINK - ATLAS ``` # Get series (/api/series/get-series) `GET https://api.predict.fun/v1/series` Get every recurring up/down series, active or retired, sorted by slug. `marketVariant` keeps the series whose rounds are of that variant, as it does on `GET /v1/categories`. ```yaml paths: /series: get: tags: - Series summary: Get series description: |- Get every recurring up/down series, active or retired, sorted by slug. `marketVariant` keeps the series whose rounds are of that variant, as it does on `GET /v1/categories`. parameters: - name: marketVariant schema: $ref: "#/components/schemas/MarketVariant" in: query description: Only series whose rounds are of this variant. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/SeriesListResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN Series: type: object title: Series description: >- A recurring up/down series: one asset at one cadence, rolling over from one round to the next. Each round is a category, listed by `GET /v1/series/{slug}/rounds`. A retired series stays listed, with its past rounds still readable, and simply has no active rounds. required: - slug - symbol - marketVariant - intervalSeconds properties: slug: type: string description: |- Stable identifier, e.g. `"btc-updown-5m"`. A published slug never changes, and every round of the series carries it as `seriesSlug`. symbol: type: string description: Asset symbol, e.g. `"BTC"` or `"NVDA"`. marketVariant: description: >- The market variant every round of the series is created with, the same value each round reports as its own `marketVariant`. allOf: - $ref: "#/components/schemas/MarketVariant" - description: >- The market variant every round of the series is created with, the same value each round reports as its own `marketVariant`. intervalSeconds: type: integer format: int32 description: >- Nominal seconds between the starts of consecutive rounds. Series that follow a trading calendar skip the hours their market is closed, so read each round's own `startsAt` and `endsAt` rather than adding this up. SeriesListResponse: type: object title: SeriesListResponse description: Response for GET /v1/series required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/Series" ``` # Get series by slug (/api/series/get-series-by-slug) `GET https://api.predict.fun/v1/series/{slug}` Get one recurring up/down series by its slug, e.g. `btc-updown-5m`. ```yaml paths: /series/{slug}: get: tags: - Series summary: Get series by slug description: Get one recurring up/down series by its slug, e.g. `btc-updown-5m`. parameters: - name: slug schema: type: string maxLength: 256 in: path description: The series' slug, for example `btc-updown-5m`. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/SeriesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN Series: type: object title: Series description: >- A recurring up/down series: one asset at one cadence, rolling over from one round to the next. Each round is a category, listed by `GET /v1/series/{slug}/rounds`. A retired series stays listed, with its past rounds still readable, and simply has no active rounds. required: - slug - symbol - marketVariant - intervalSeconds properties: slug: type: string description: |- Stable identifier, e.g. `"btc-updown-5m"`. A published slug never changes, and every round of the series carries it as `seriesSlug`. symbol: type: string description: Asset symbol, e.g. `"BTC"` or `"NVDA"`. marketVariant: description: >- The market variant every round of the series is created with, the same value each round reports as its own `marketVariant`. allOf: - $ref: "#/components/schemas/MarketVariant" - description: >- The market variant every round of the series is created with, the same value each round reports as its own `marketVariant`. intervalSeconds: type: integer format: int32 description: >- Nominal seconds between the starts of consecutive rounds. Series that follow a trading calendar skip the hours their market is closed, so read each round's own `startsAt` and `endsAt` rather than adding this up. SeriesResponse: type: object title: SeriesResponse description: Response for GET /v1/series/{slug} required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/Series" ``` # Get series rounds (/api/series/get-series-rounds) `GET https://api.predict.fun/v1/series/{slug}/rounds` Get the rounds of a series, each one a category with its markets. `ACTIVE`, the default, lists the rounds that have not ended yet in start order, so the first is the live round, and the rest are the rounds it rolls over to. Rounds are created ahead of time, so one page is enough to follow a series through several rollovers. `ENDED` lists the rounds that have ended, newest first. Pages are cached for a few seconds, so a round can still be listed as active for a moment after its `endsAt`. ```yaml paths: /series/{slug}/rounds: get: tags: - Series summary: Get series rounds description: >- Get the rounds of a series, each one a category with its markets. `ACTIVE`, the default, lists the rounds that have not ended yet in start order, so the first is the live round, and the rest are the rounds it rolls over to. Rounds are created ahead of time, so one page is enough to follow a series through several rollovers. `ENDED` lists the rounds that have ended, newest first. Pages are cached for a few seconds, so a round can still be listed as active for a moment after its `endsAt`. parameters: - name: slug schema: type: string maxLength: 256 in: path description: The series' slug, for example `btc-updown-5m`. required: true deprecated: false explode: true - name: status schema: $ref: "#/components/schemas/RoundStatusFilter" in: query description: >- Which rounds to list, by whether they have ended. Defaults to `ACTIVE`, the rounds that have not ended yet. required: false deprecated: false explode: true - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 100. Defaults to 20. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: >- The `cursor` from the previous page, to get the next one. A cursor from a page of the other `status` is rejected. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CategoriesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CategoriesResponse: type: object title: CategoriesResponse description: Response for GET /v1/categories required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/CategoryWithStats" CategoryStatsData: type: object title: CategoryStatsData description: Category statistics data required: - totalLiquidityUsd - volumeTotalUsd - volume24hUsd - holdersCount properties: totalLiquidityUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double holdersCount: type: integer format: int64 CategoryStatus: type: string description: Category status enum: - OPEN - RESOLVED - REMOVED CategoryVariantDetails: type: object title: CategoryVariantDetails description: |- What a category is about, one key per kind of category. See [`MarketVariantDetails`]. properties: sports: description: Set for a sports category. allOf: - $ref: "#/components/schemas/SportsCategoryDetails" - description: Set for a sports category. crypto: description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. upDown: description: Set for an up down price category, whatever the asset class. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: Set for an up down price category, whatever the asset class. tweet: description: Set for a tweet count category. allOf: - $ref: "#/components/schemas/TweetCategoryDetails" - description: Set for a tweet count category. CategoryWithStats: type: object title: CategoryWithStats description: Category data with optional inline statistics. required: - id - slug - title - description - imageUrl - isNegRisk - protocol - isYieldBearing - marketVariant - createdAt - publishedAt - markets - startsAt - status - isVisible - resolutionProvider - tags properties: id: type: integer format: int64 slug: type: string title: type: string shortTitle: type: string description: Short, human-readable label, e.g. `"BTC Up or Down 15m"`. `null` when not available. seriesSlug: type: string description: >- Slug of the recurring series this category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. description: type: string imageUrl: type: string isNegRisk: type: boolean protocol: $ref: "#/components/schemas/Protocol" isYieldBearing: type: boolean negRiskOnChainId: type: string description: On-chain NegRisk market ID as a 32-byte hex string. marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this category is about, replacing `variantData`. allOf: - $ref: "#/components/schemas/CategoryVariantDetails" - description: What this category is about, replacing `variantData`. createdAt: type: string publishedAt: type: string markets: type: array items: $ref: "#/components/schemas/Market" startsAt: type: string endsAt: type: string tradingSession: description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. allOf: - $ref: "#/components/schemas/TradingSession" - description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. status: $ref: "#/components/schemas/CategoryStatus" isVisible: type: boolean resolutionProvider: $ref: "#/components/schemas/ResolutionProvider" tags: type: array items: $ref: "#/components/schemas/Tag" teams: type: array description: |- Deprecated in favour of `variantDetails.sports.teams`. The teams playing, where the category is a fixture. items: $ref: "#/components/schemas/Team" deprecated: true parentSlug: type: string description: The slug of the parent category, when this is a prop/child market. stats: $ref: "#/components/schemas/CategoryStatsData" CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double Protocol: type: string description: |- The protocol a category's markets trade on. Every market in a category shares it, and it decides which exchange contracts a client talks to. enum: - LEGACY - PREDICT ResolutionProvider: type: string description: Resolution provider for a category (maps to `resolution_provider_type` table). enum: - PREDICT_DOT_FUN - THREE_PO - CHAINLINK - ATLAS RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string RoundStatusFilter: type: string description: >- Which rounds of a series to list, by time rather than by resolution: a round that has ended can still be awaiting its outcome, which each round's own `status` reports. enum: - ACTIVE - ENDED ScoreboardSource: type: string description: The source a scoreboard is built on. enum: - GRID - POLYMARKET - PREDICT_DOT_FUN SportsCategoryDetails: type: object title: SportsCategoryDetails description: What a sports category is about. required: - provider - teams properties: provider: description: The provider that supplies the fixture. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that supplies the fixture. providerEventId: type: string description: The provider's own id for the fixture, where it has one. teams: type: array description: The two sides of the fixture. items: $ref: "#/components/schemas/SportsEventTeam" scoreboard: description: Absent until a source has reported on the match. allOf: - $ref: "#/components/schemas/SportsScoreboard" - description: Absent until a source has reported on the match. SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsScoreboard: type: object title: SportsScoreboard description: How a match stands. required: - source - score - games properties: source: description: The source the record is built on. allOf: - $ref: "#/components/schemas/ScoreboardSource" - description: The source the record is built on. bestOf: type: integer format: int32 isLive: type: boolean finished: type: boolean currentGame: type: integer format: int32 description: Counted from one. period: type: string description: "As the feed labels it: `1H`, `Q3`, `2/3`." clock: type: string description: >- The game clock as the live feed shows it. Its meaning depends on the sport: time left in the period for basketball, time played for soccer. score: type: array description: Games won in a series, the match score otherwise. items: $ref: "#/components/schemas/SportsTeamScore" games: type: array items: $ref: "#/components/schemas/SportsScoreboardGame" SportsScoreboardGame: type: object title: SportsScoreboardGame description: One game of a best-of series. required: - gameNumber - isLive - finished - scores properties: gameNumber: type: integer format: int32 description: Counted from one. isLive: type: boolean finished: type: boolean winnerTeamId: type: integer format: int64 scores: type: array description: Empty when only the winner is known. items: $ref: "#/components/schemas/SportsTeamScore" SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. SportsTeamScore: type: object title: SportsTeamScore description: One team's count in a series or a game. required: - teamId - score properties: teamId: type: integer format: int64 description: The id the category's `teams` entries carry. score: type: integer format: int32 description: Games won, goals, rounds or kills, by what the entry is. Tag: type: object title: Tag description: Tag data required: - id - name properties: id: type: string description: bigint string name: type: string level: type: integer format: int32 description: >- Hierarchy level of the tag (1, 2, or 3), or null for flat tags. Tags form a tree up to three levels deep, e.g. Sports -> Soccer -> EPL. parentId: type: string description: >- Id of the parent tag as a bigint string, or null for root and flat tags. Together with `level` this allows rendering nested tag menus. makerRebateBps: type: integer format: int32 description: >- Maker rebate in basis points for fills on categories carrying this tag, or null if no rebate applies. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TradingSession: type: object title: TradingSession description: Exchange session during which the settlement price is live, without trading buffers. required: - opensAt - closesAt properties: opensAt: type: string description: Session opening time as an ISO 8601 UTC timestamp with millisecond precision. closesAt: type: string description: Session closing time as an ISO 8601 UTC timestamp with millisecond precision. TweetCategoryDetails: type: object title: TweetCategoryDetails description: What a tweet count category is about. required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: The account whose posts are counted. tweetCount: type: integer format: int64 description: Posts counted so far within the category's window. TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range UpDownCategoryDetails: type: object title: UpDownCategoryDetails description: What an up down price category is about, whatever the asset class. required: - priceFeedProvider properties: priceFeedProvider: description: Who publishes the price this category settles against. allOf: - $ref: "#/components/schemas/PriceFeedProvider" - description: Who publishes the price this category settles against. priceFeedId: type: string description: The feed's own identifier, where it has one. priceFeedSymbol: type: string description: The symbol the feed publishes under, eg. `BTCUSDT` or `SPYUSDT`. startPrice: type: number format: double description: The price at the start of the window, once the window has opened. endPrice: type: number format: double description: The price at the end of the window, once it has closed. VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get order by hash (/api/orders/get-order-by-hash) `GET https://api.predict.fun/v1/orders/{hash}` Get the order information for one of your own orders by its hash. Requires JWT authentication. ```yaml paths: /orders/{hash}: get: tags: - Orders summary: Get order by hash description: |- Get the order information for one of your own orders by its hash. Requires JWT authentication. parameters: - name: hash schema: type: string maxLength: 66 in: path description: The order's hash, in lowercase hex with the `0x` prefix. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/OrderResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ContractOrder: type: object title: ContractOrder description: Contract order data (the signed order) required: - salt - maker - signer - taker - tokenId - makerAmount - takerAmount - expiration - nonce - feeRateBps - side - signatureType properties: hash: type: string maxLength: 66 salt: type: string maker: type: string signer: type: string taker: type: string tokenId: type: string makerAmount: type: string takerAmount: type: string expiration: type: integer format: int64 description: Unix timestamp in seconds nonce: type: string feeRateBps: type: string side: type: integer format: int32 signatureType: type: integer format: int32 signature: type: string description: |- Capped at `order_core::order_create::validate::MAX_SIGNATURE_CHARS`. The attribute takes only a literal, so a test pins the two together. maxLength: 1026 OrderData: type: object title: OrderData description: Order data as returned by the API required: - order - id - marketId - currency - amount - amountFilled - isNegRisk - isYieldBearing - strategy - status - rewardEarningRate properties: order: $ref: "#/components/schemas/ContractOrder" id: type: string marketId: type: integer format: int64 currency: type: string amount: type: string amountFilled: type: string isNegRisk: type: boolean isYieldBearing: type: boolean strategy: $ref: "#/components/schemas/OrderStrategy" status: $ref: "#/components/schemas/OrderStatus" rewardEarningRate: type: integer format: int32 OrderResponse: type: object title: OrderResponse description: Response for GET /v1/orders/:hash required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/OrderData" OrderStatus: type: string description: Order status enum: - OPEN - FILLED - EXPIRED - CANCELLED - INVALIDATED OrderStrategy: type: string description: Order execution strategy. enum: - MARKET - LIMIT ``` # Get orders (/api/orders/get-orders) `GET https://api.predict.fun/v1/orders` Get a list of your own orders with optional filters. Requires JWT authentication. ```yaml paths: /orders: get: tags: - Orders summary: Get orders description: |- Get a list of your own orders with optional filters. Requires JWT authentication. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 150. Defaults to 50. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: status schema: $ref: "#/components/schemas/OrderStatusFilter" in: query description: Only orders with this status. Defaults to `OPEN`. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/OrdersResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ContractOrder: type: object title: ContractOrder description: Contract order data (the signed order) required: - salt - maker - signer - taker - tokenId - makerAmount - takerAmount - expiration - nonce - feeRateBps - side - signatureType properties: hash: type: string maxLength: 66 salt: type: string maker: type: string signer: type: string taker: type: string tokenId: type: string makerAmount: type: string takerAmount: type: string expiration: type: integer format: int64 description: Unix timestamp in seconds nonce: type: string feeRateBps: type: string side: type: integer format: int32 signatureType: type: integer format: int32 signature: type: string description: |- Capped at `order_core::order_create::validate::MAX_SIGNATURE_CHARS`. The attribute takes only a literal, so a test pins the two together. maxLength: 1026 ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null OrderData: type: object title: OrderData description: Order data as returned by the API required: - order - id - marketId - currency - amount - amountFilled - isNegRisk - isYieldBearing - strategy - status - rewardEarningRate properties: order: $ref: "#/components/schemas/ContractOrder" id: type: string marketId: type: integer format: int64 currency: type: string amount: type: string amountFilled: type: string isNegRisk: type: boolean isYieldBearing: type: boolean strategy: $ref: "#/components/schemas/OrderStrategy" status: $ref: "#/components/schemas/OrderStatus" rewardEarningRate: type: integer format: int32 OrderStatus: type: string description: Order status enum: - OPEN - FILLED - EXPIRED - CANCELLED - INVALIDATED OrderStatusFilter: type: string description: Order status filter for queries. enum: - OPEN - FILLED OrderStrategy: type: string description: Order execution strategy. enum: - MARKET - LIMIT OrdersResponse: type: object title: OrdersResponse description: Response for GET /v1/orders required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/OrderData" ``` # Create an order (/api/orders/create-an-order) `POST https://api.predict.fun/v1/orders` Create a LIMIT or MARKET order for the authenticated user. Requires JWT authentication. ```yaml paths: /orders: post: tags: - Orders summary: Create an order description: |- Create a LIMIT or MARKET order for the authenticated user. Requires JWT authentication. requestBody: content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CreateOrderRequest" required: true responses: "201": description: Created content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/CreateOrderResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ContractOrder: type: object title: ContractOrder description: Contract order data (the signed order) required: - salt - maker - signer - taker - tokenId - makerAmount - takerAmount - expiration - nonce - feeRateBps - side - signatureType properties: hash: type: string maxLength: 66 salt: type: string maker: type: string signer: type: string taker: type: string tokenId: type: string makerAmount: type: string takerAmount: type: string expiration: type: integer format: int64 description: Unix timestamp in seconds nonce: type: string feeRateBps: type: string side: type: integer format: int32 signatureType: type: integer format: int32 signature: type: string description: |- Capped at `order_core::order_create::validate::MAX_SIGNATURE_CHARS`. The attribute takes only a literal, so a test pins the two together. maxLength: 1026 CreateOrderData: type: object title: CreateOrderData description: Create order request data required: - pricePerShare - strategy - order properties: pricePerShare: type: string strategy: $ref: "#/components/schemas/OrderStrategy" slippageBps: type: string isFillOrKill: type: boolean isPostOnly: type: boolean reservedBalancePolicy: $ref: "#/components/schemas/ReservedBalancePolicy" isMinAmountOut: type: boolean description: >- Signals that `takerAmount` is deflated and represents a signed minimum, not the actual fill target. When `true` the backend derives the actual share quantity from the expected cost: `amount = makerAmount * 1e18 / priceInCurrency`. selfTradePrevention: $ref: "#/components/schemas/SelfTradePreventionStrategy" order: $ref: "#/components/schemas/ContractOrder" CreateOrderRequest: type: object title: CreateOrderRequest description: Create order request required: - data properties: data: $ref: "#/components/schemas/CreateOrderData" CreateOrderResponse: type: object title: CreateOrderResponse description: Response for POST /v1/orders required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/CreateOrderResponseData" CreateOrderResponseData: type: object title: CreateOrderResponseData description: Create order response data required: - code - orderId - orderHash properties: code: type: string orderId: type: string orderHash: type: string removalLockedUntil: type: string description: RFC 3339 timestamp before which this order cannot be removed. ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null OrderStrategy: type: string description: Order execution strategy. enum: - MARKET - LIMIT ReservedBalancePolicy: type: string description: Reserved balance policy enum: - REJECT_MARKET_ORDER - SKIP_RESERVED_BALANCE_CHECKS SelfTradePreventionStrategy: type: string description: Self-trade prevention strategy enum: - CANCEL_MAKER - CANCEL_TAKER - CANCEL_BOTH ``` # Get order match events (/api/orders/get-order-match-events) `GET https://api.predict.fun/v1/orders/matches` Get a paginated list of order match events with optional filtering. Results are sorted by executedAt DESC. The executedAfter and executedBefore filters are unix timestamps in milliseconds and both bounds are exclusive. The orderHashes filter accepts up to 25 order hashes, comma separated or repeated, and returns the match events in which any of those orders took part as maker or taker. Hashes that do not belong to any order are ignored. A match event describes a whole transaction, so filtering by a maker order also includes the other fills settled in the same transaction. ```yaml paths: /orders/matches: get: tags: - Orders summary: Get order match events description: |- Get a paginated list of order match events with optional filtering. Results are sorted by executedAt DESC. The executedAfter and executedBefore filters are unix timestamps in milliseconds and both bounds are exclusive. The orderHashes filter accepts up to 25 order hashes, comma separated or repeated, and returns the match events in which any of those orders took part as maker or taker. Hashes that do not belong to any order are ignored. A match event describes a whole transaction, so filtering by a maker order also includes the other fills settled in the same transaction. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 150. Defaults to 50. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: categoryId schema: type: string in: query description: Only matches in the category with this slug, which markets return as `categorySlug`. required: false deprecated: false explode: true - name: marketId schema: type: integer format: int64 in: query description: Only matches in this market. required: false deprecated: false explode: true - name: minValueUsdtWei schema: type: string in: query description: >- Only matches with at least one fill worth this much or more, in wei, where 1 USDT is 10^18 units. required: false deprecated: false explode: true - name: signerAddress schema: type: string in: query description: >- Only matches in which this address was the maker or the taker. For a Predict account, use the account's address. required: false deprecated: false explode: true - name: isSignerMaker schema: type: boolean in: query description: >- Set to `true` for only the matches where `signerAddress` was the maker, or `false` for those where it was the taker. Requires `signerAddress`. required: false deprecated: false explode: true - name: orderHashes schema: type: array items: type: string in: query description: >- Only matches in which one of these orders was the maker or the taker. Takes up to 25 hashes, comma-separated or repeated. required: false deprecated: false explode: true - name: executedAfter schema: type: integer format: int64 in: query description: Only include matches executed strictly after this unix timestamp (milliseconds). required: false deprecated: false explode: true - name: executedBefore schema: type: integer format: int64 in: query description: Only include matches executed strictly before this unix timestamp (milliseconds). required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/MatchesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Fee: type: object title: Fee description: Fee information required: - amount - type properties: amount: type: string description: The fee amount in wei type: description: The fee side, either COLLATERAL (e.g. USDT) or SHARES allOf: - $ref: "#/components/schemas/FeeType" - description: The fee side, either COLLATERAL (e.g. USDT) or SHARES FeeType: type: string description: Fee type enum: - COLLATERAL - SHARES Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. MatchData: type: object title: MatchData description: Match event data required: - market - taker - amountFilled - priceExecuted - makers - transactionHash - executedAt properties: market: $ref: "#/components/schemas/Market" taker: $ref: "#/components/schemas/OrderFillData" amountFilled: type: string priceExecuted: type: string makers: type: array items: $ref: "#/components/schemas/OrderFillData" transactionHash: type: string settlementId: type: string description: Identifier of the off-chain settlement that produced this match. executedAt: type: string MatchesResponse: type: object title: MatchesResponse description: Response for GET /v1/orders/matches required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/MatchData" OrderFillData: type: object title: OrderFillData description: Order fill data (for maker/taker in matches) required: - quoteType - amount - price - outcome - signer - hash - fee properties: quoteType: $ref: "#/components/schemas/QuoteType" amount: type: string price: type: string outcome: $ref: "#/components/schemas/Outcome" signer: type: string hash: type: string fee: $ref: "#/components/schemas/Fee" Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double QuoteType: type: string description: "Quote type: ask or bid." enum: - Ask - Bid RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Remove orders (/api/orders/remove-orders) `POST https://api.predict.fun/v1/orders/remove` Remove orders from the orderbook for the authenticated signer (max 100). This will not cancel the order on-chain. Requires JWT or project authentication. ```yaml paths: /orders/remove: post: tags: - Orders summary: Remove orders description: |- Remove orders from the orderbook for the authenticated signer (max 100). This will not cancel the order on-chain. Requires JWT or project authentication. requestBody: content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/RemoveOrdersRequest" required: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/RemoveOrdersResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null RemoveOrdersData: type: object title: RemoveOrdersData description: Remove orders request data required: - ids properties: ids: type: array description: An array of order IDs (max 100) items: type: string maxItems: 100 minItems: 1 RemoveOrdersRequest: type: object title: RemoveOrdersRequest description: Remove orders request required: - data properties: data: $ref: "#/components/schemas/RemoveOrdersData" RemoveOrdersResponse: type: object title: RemoveOrdersResponse description: Response for POST /v1/orders/remove required: - success - removed - noop - rejected properties: success: type: boolean removed: type: array description: Orders that were successfully removed items: type: string noop: type: array description: Orders that were not removed because they were already filled, removed or cancelled items: type: string rejected: type: array description: >- Orders whose removal was rejected because they are still within their market's removal lock window. These may be retried after the order's removal lock expires. items: type: string ``` # Remove orders by hash (/api/orders/remove-orders-by-hash) `POST https://api.predict.fun/v1/orders/remove-by-hash` Remove orders from the orderbook by their hash for the authenticated signer (max 100). This will not cancel the order on-chain. Requires JWT or project authentication. ```yaml paths: /orders/remove-by-hash: post: tags: - Orders summary: Remove orders by hash description: >- Remove orders from the orderbook by their hash for the authenticated signer (max 100). This will not cancel the order on-chain. Requires JWT or project authentication. requestBody: content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/RemoveOrdersByHashRequest" required: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/RemoveOrdersResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null RemoveOrdersByHashData: type: object title: RemoveOrdersByHashData description: Remove orders by hash request data required: - hashes properties: hashes: type: array description: An array of order hashes (0x-prefixed 32-byte hex strings, max 100) items: type: string maxItems: 100 minItems: 1 RemoveOrdersByHashRequest: type: object title: RemoveOrdersByHashRequest description: Remove orders by hash request required: - data properties: data: $ref: "#/components/schemas/RemoveOrdersByHashData" RemoveOrdersResponse: type: object title: RemoveOrdersResponse description: Response for POST /v1/orders/remove required: - success - removed - noop - rejected properties: success: type: boolean removed: type: array description: Orders that were successfully removed items: type: string noop: type: array description: Orders that were not removed because they were already filled, removed or cancelled items: type: string rejected: type: array description: >- Orders whose removal was rejected because they are still within their market's removal lock window. These may be retried after the order's removal lock expires. items: type: string ``` # Get connected account (/api/account/get-connected-account) `GET https://api.predict.fun/v1/account` Get information for the connected user account. `points` are for one season, `season` or the current season by default. Requires JWT authentication. ```yaml paths: /account: get: tags: - Account summary: Get connected account description: |- Get information for the connected user account. `points` are for one season, `season` or the current season by default. Requires JWT authentication. parameters: - name: season schema: type: integer format: int32 in: query description: >- The id of the points season to report `points` for, which must exist. Defaults to the current season, the latest one to have started. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/AccountResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: AccountData: type: object title: AccountData description: Account data required: - name - address - referral - points properties: name: type: string address: type: string imageUrl: type: string referral: $ref: "#/components/schemas/ReferralInfo" points: $ref: "#/components/schemas/PointsInfo" AccountResponse: type: object title: AccountResponse description: Response for GET /v1/account required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/AccountData" ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null PointsInfo: type: object title: PointsInfo description: Points information required: - total properties: total: type: number format: double description: The user's points total for `season`. rank: type: integer format: int32 description: |- The user's rank within `season`, or null if they have no points in it yet. season: type: integer format: int32 description: |- The season these points are for: the one requested, or the current season. Null only before any season exists. ReferralInfo: type: object title: ReferralInfo description: Referral information required: - status properties: code: type: string status: $ref: "#/components/schemas/ReferralStatus" ReferralStatus: type: string description: Referral status enum: - LOCKED - UNLOCKED ``` # Get account activity (/api/account/get-account-activity) `GET https://api.predict.fun/v1/account/activity` Get a paginated list of trading activity including orders, matches, conversions, merges, and splits for the authenticated user. The `occurredAfter` and `occurredBefore` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurredAfter` and strictly before `occurredBefore` are returned. Requires JWT authentication. ```yaml paths: /account/activity: get: tags: - Account summary: Get account activity description: |- Get a paginated list of trading activity including orders, matches, conversions, merges, and splits for the authenticated user. The `occurredAfter` and `occurredBefore` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurredAfter` and strictly before `occurredBefore` are returned. Requires JWT authentication. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 150. Defaults to 50. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: eventTypes schema: type: array items: $ref: "#/components/schemas/ActivityEventName" in: query description: Filter by event types (comma-separated or repeated) required: false deprecated: false explode: true - name: occurredAfter schema: type: integer format: int64 in: query description: Only include events that occurred strictly after this unix timestamp (milliseconds) required: false deprecated: false explode: true - name: occurredBefore schema: type: integer format: int64 in: query description: Only include events that occurred strictly before this unix timestamp (milliseconds) required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ActivityResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ActivityEvent: type: object title: ActivityEvent description: Activity event data required: - name - createdAt - market properties: name: $ref: "#/components/schemas/ActivityEventName" createdAt: type: string transactionHash: type: string amountFilled: type: string priceExecuted: type: string order: $ref: "#/components/schemas/ActivityOrderData" market: $ref: "#/components/schemas/Market" outcome: $ref: "#/components/schemas/Outcome" ActivityEventName: type: string description: Activity event name enum: - CREATE - MATCH_SUCCESS - CANCEL - INVALIDATE - NO_MARKET_MATCH - EXPIRED - REJECTED_POST_ONLY - CONVERT - MERGE - SPLIT - REDEEM - REFUND ActivityOrderData: type: object title: ActivityOrderData description: Order data in activity events required: - quoteType - amount - price properties: quoteType: $ref: "#/components/schemas/QuoteType" amount: type: string price: type: string fee: description: The fee for the order, only present for MATCH_SUCCESS events allOf: - $ref: "#/components/schemas/Fee" - description: The fee for the order, only present for MATCH_SUCCESS events ActivityResponse: type: object title: ActivityResponse description: Response for GET /v1/account/activity and GET /v1/account/{address}/activity required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/ActivityEvent" CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Fee: type: object title: Fee description: Fee information required: - amount - type properties: amount: type: string description: The fee amount in wei type: description: The fee side, either COLLATERAL (e.g. USDT) or SHARES allOf: - $ref: "#/components/schemas/FeeType" - description: The fee side, either COLLATERAL (e.g. USDT) or SHARES FeeType: type: string description: Fee type enum: - COLLATERAL - SHARES Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double QuoteType: type: string description: "Quote type: ask or bid." enum: - Ask - Bid RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get weekly points breakdown (/api/account/get-weekly-points-breakdown) `GET https://api.predict.fun/v1/account/points/weekly` Returns the authenticated user's per-week points history for one season, `season` or the current season by default, alongside each week's points budget. Entries are ordered chronologically from week 1 of the season. The current in-progress round is included with `calculated: false` until it closes. Requires JWT authentication. ```yaml paths: /account/points/weekly: get: tags: - Account summary: Get weekly points breakdown description: |- Returns the authenticated user's per-week points history for one season, `season` or the current season by default, alongside each week's points budget. Entries are ordered chronologically from week 1 of the season. The current in-progress round is included with `calculated: false` until it closes. Requires JWT authentication. parameters: - name: season schema: type: integer format: int32 in: query description: >- The id of the points season to list the weeks of, which must exist. Defaults to the current season, the latest one to have started. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/WeeklyPointsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null WeeklyPointsEntry: type: object title: WeeklyPointsEntry description: A single week of points data required: - week - startTime - endTime - userPoints - userReferralPoints - allocationAmount - calculated properties: week: type: integer format: int32 description: Week number within the season, starting at 1. startTime: type: string description: Round start time, RFC3339. endTime: type: string description: Round end time, RFC3339. userPoints: type: number format: double description: |- Points earned by the authenticated user that week, from every source, as in the account's total. `0` until the weekly calculation is finalised, check `calculated` to disambiguate from a real zero. userReferralPoints: type: number format: double description: Subset of points attributable to referral sources. allocationAmount: type: integer format: int32 description: The budgeted point allocation for the round. calculated: type: boolean description: |- Whether the weekly calculation has been finalised. When `false`, the point fields will be `0`, as the round is still in-progress / "TBD". Distribution happens on a near-weekly cadence. WeeklyPointsResponse: type: object title: WeeklyPointsResponse description: Response for GET /v1/account/points/weekly required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/WeeklyPointsEntry" ``` # Get account by address (/api/account/get-account-by-address) `GET https://api.predict.fun/v1/account/{address}` Get the public profile of any wallet address: its display name, its profile image once it has passed moderation, and its leaderboard points and rank. A wallet that never set up a profile has a `null` name. An address with no wallet returns 404. ```yaml paths: /account/{address}: get: tags: - Account summary: Get account by address description: |- Get the public profile of any wallet address: its display name, its profile image once it has passed moderation, and its leaderboard points and rank. A wallet that never set up a profile has a `null` name. An address with no wallet returns 404. parameters: - name: address schema: type: string in: path description: The wallet's address. For a Predict account, use the account's address. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/PublicAccountResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null PointsInfo: type: object title: PointsInfo description: Points information required: - total properties: total: type: number format: double description: The user's points total for `season`. rank: type: integer format: int32 description: |- The user's rank within `season`, or null if they have no points in it yet. season: type: integer format: int32 description: |- The season these points are for: the one requested, or the current season. Null only before any season exists. PublicAccountData: type: object title: PublicAccountData description: The public profile of a wallet required: - address - points properties: name: type: string description: The name the wallet set on its profile. `null` when it has none. address: type: string imageUrl: type: string description: The profile image, once it has passed moderation. points: $ref: "#/components/schemas/PointsInfo" PublicAccountResponse: type: object title: PublicAccountResponse description: Response for GET /v1/account/{address} required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/PublicAccountData" ``` # Get account activity by address (/api/account/get-account-activity-by-address) `GET https://api.predict.fun/v1/account/{address}/activity` Get a paginated list of the conversions, merges, splits, redemptions and refunds of any wallet address. Order events are not included. The fills of an address are available from `GET /v1/orders/matches` with `signerAddress`. An address with no account returns an empty list. The `occurredAfter` and `occurredBefore` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurredAfter` and strictly before `occurredBefore` are returned. ```yaml paths: /account/{address}/activity: get: tags: - Account summary: Get account activity by address description: |- Get a paginated list of the conversions, merges, splits, redemptions and refunds of any wallet address. Order events are not included. The fills of an address are available from `GET /v1/orders/matches` with `signerAddress`. An address with no account returns an empty list. The `occurredAfter` and `occurredBefore` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurredAfter` and strictly before `occurredBefore` are returned. parameters: - name: address schema: type: string in: path description: The wallet's address. For a Predict account, use the account's address. required: true deprecated: false explode: true - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 150. Defaults to 50. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: eventTypes schema: type: array items: $ref: "#/components/schemas/PublicActivityEventName" in: query description: Filter by event types (comma-separated or repeated) required: false deprecated: false explode: true - name: occurredAfter schema: type: integer format: int64 in: query description: Only include events that occurred strictly after this unix timestamp (milliseconds) required: false deprecated: false explode: true - name: occurredBefore schema: type: integer format: int64 in: query description: Only include events that occurred strictly before this unix timestamp (milliseconds) required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ActivityResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ActivityEvent: type: object title: ActivityEvent description: Activity event data required: - name - createdAt - market properties: name: $ref: "#/components/schemas/ActivityEventName" createdAt: type: string transactionHash: type: string amountFilled: type: string priceExecuted: type: string order: $ref: "#/components/schemas/ActivityOrderData" market: $ref: "#/components/schemas/Market" outcome: $ref: "#/components/schemas/Outcome" ActivityEventName: type: string description: Activity event name enum: - CREATE - MATCH_SUCCESS - CANCEL - INVALIDATE - NO_MARKET_MATCH - EXPIRED - REJECTED_POST_ONLY - CONVERT - MERGE - SPLIT - REDEEM - REFUND ActivityOrderData: type: object title: ActivityOrderData description: Order data in activity events required: - quoteType - amount - price properties: quoteType: $ref: "#/components/schemas/QuoteType" amount: type: string price: type: string fee: description: The fee for the order, only present for MATCH_SUCCESS events allOf: - $ref: "#/components/schemas/Fee" - description: The fee for the order, only present for MATCH_SUCCESS events ActivityResponse: type: object title: ActivityResponse description: Response for GET /v1/account/activity and GET /v1/account/{address}/activity required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/ActivityEvent" CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Fee: type: object title: Fee description: Fee information required: - amount - type properties: amount: type: string description: The fee amount in wei type: description: The fee side, either COLLATERAL (e.g. USDT) or SHARES allOf: - $ref: "#/components/schemas/FeeType" - description: The fee side, either COLLATERAL (e.g. USDT) or SHARES FeeType: type: string description: Fee type enum: - COLLATERAL - SHARES Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double PublicActivityEventName: type: string description: |- Activity event name that can be read for any address. These are the on-chain position changes of a wallet. Order lifecycle events stay private to the wallet owner. enum: - CONVERT - MERGE - SPLIT - REDEEM - REFUND QuoteType: type: string description: "Quote type: ask or bid." enum: - Ask - Bid RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get PnL history by address (/api/account/get-pnl-history-by-address) `GET https://api.predict.fun/v1/account/{address}/pnl` Cumulative profit and loss in USD for any wallet, the series the predict.fun profile chart shows. `period` picks the window and with it the spacing: hourly points for `1d`, daily for `7d` and `30d`, and the whole history for `all` (the default). The latest point is the current PnL. The whole series comes back in one response, so there is no cursor. Results are cached for up to 30 seconds, so polling faster than that returns the same series. An address with no account returns an empty series. ```yaml paths: /account/{address}/pnl: get: tags: - Account summary: Get PnL history by address description: |- Cumulative profit and loss in USD for any wallet, the series the predict.fun profile chart shows. `period` picks the window and with it the spacing: hourly points for `1d`, daily for `7d` and `30d`, and the whole history for `all` (the default). The latest point is the current PnL. The whole series comes back in one response, so there is no cursor. Results are cached for up to 30 seconds, so polling faster than that returns the same series. An address with no account returns an empty series. parameters: - name: address schema: type: string in: path description: The wallet's address. For a Predict account, use the account's address. required: true deprecated: false explode: true - name: period schema: $ref: "#/components/schemas/PnlPeriod" in: query description: Window of history to return. Defaults to `all`. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/WalletPnlResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null PnlPeriod: type: string description: |- Window of PnL history to return. Each value is a period the upstream PnL history service computes natively, so a request maps to exactly one upstream call and nothing is trimmed or resampled here. enum: - 1d - 7d - 30d - all PnlPoint: type: object title: PnlPoint description: One point of a wallet's cumulative PnL required: - x - y properties: x: type: integer format: int64 description: Unix timestamp (seconds) at the start of the bucket y: type: number format: double description: Cumulative PnL in USD as of this bucket WalletPnlData: type: object title: WalletPnlData description: A wallet's PnL history required: - period - points properties: period: $ref: "#/components/schemas/PnlPeriod" points: type: array description: Oldest first. Empty for an address that has never traded. items: $ref: "#/components/schemas/PnlPoint" WalletPnlResponse: type: object title: WalletPnlResponse description: Response for GET /v1/account/{address}/pnl required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/WalletPnlData" ``` # Set a referral (/api/account/set-a-referral) `POST https://api.predict.fun/v1/account/referral` Set a referral code if not already set. Requires JWT or project authentication. Project auth requires an EIP-712 signature in the `x-signature` header. ```yaml paths: /account/referral: post: tags: - Account summary: Set a referral description: |- Set a referral code if not already set. Requires JWT or project authentication. Project auth requires an EIP-712 signature in the `x-signature` header. requestBody: content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/SetReferralRequest" required: true responses: "201": description: Created content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/SetReferralResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null SetReferralData: type: object title: SetReferralData description: Set referral data required: - referralCode properties: referralCode: type: string description: a string 5 character(s) long maxLength: 5 minLength: 5 SetReferralRequest: type: object title: SetReferralRequest description: Set referral request required: - data properties: data: $ref: "#/components/schemas/SetReferralData" SetReferralResponse: type: object title: SetReferralResponse description: Response for POST /v1/account/referral required: - success properties: success: type: boolean ``` # Get maker rebate events (/api/fee-events/get-maker-rebate-events) `GET https://api.predict.fun/v1/maker-rebates` Returns paginated on-chain maker rebate earnings for the authenticated wallet, newest first. The `occurred_after` and `occurred_before` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurred_after` and strictly before `occurred_before` are returned. Neither bound is carried in the pagination cursor, so send the same filters again when requesting the next page. ```yaml paths: /maker-rebates: get: tags: - Fee Events summary: Get maker rebate events description: >- Returns paginated on-chain maker rebate earnings for the authenticated wallet, newest first. The `occurred_after` and `occurred_before` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurred_after` and strictly before `occurred_before` are returned. Neither bound is carried in the pagination cursor, so send the same filters again when requesting the next page. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 150. Defaults to 50. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: fee_in_collateral schema: type: boolean in: query description: >- Set to `true` for the amounts paid in USDT, or `false` for those paid in outcome tokens. required: true deprecated: false explode: true - name: occurred_after schema: type: integer format: int64 in: query description: Only include events that occurred strictly after this unix timestamp (milliseconds) required: false deprecated: false explode: true - name: occurred_before schema: type: integer format: int64 in: query description: Only include events that occurred strictly before this unix timestamp (milliseconds) required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/FeeEventsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null FeeEventData: type: object title: FeeEventData description: A single on-chain fee distribution event (referral fee or maker rebate). required: - transactionHash - createdAt - takerFeeTokenId - amount - price properties: transactionHash: type: string createdAt: type: string takerFeeTokenId: type: string amount: type: string marketQuestion: type: string marketOutcomeName: type: string price: type: string FeeEventsResponse: type: object title: FeeEventsResponse description: Paginated fee event response. required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/FeeEventData" ``` # Get referral fee events (/api/fee-events/get-referral-fee-events) `GET https://api.predict.fun/v1/referral-fees` Returns paginated on-chain referral fee earnings for the authenticated wallet, newest first. The `occurred_after` and `occurred_before` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurred_after` and strictly before `occurred_before` are returned. Neither bound is carried in the pagination cursor, so send the same filters again when requesting the next page. ```yaml paths: /referral-fees: get: tags: - Fee Events summary: Get referral fee events description: >- Returns paginated on-chain referral fee earnings for the authenticated wallet, newest first. The `occurred_after` and `occurred_before` filters are unix timestamps in milliseconds and both bounds are exclusive: only events that occurred strictly after `occurred_after` and strictly before `occurred_before` are returned. Neither bound is carried in the pagination cursor, so send the same filters again when requesting the next page. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 150. Defaults to 50. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: fee_in_collateral schema: type: boolean in: query description: >- Set to `true` for the amounts paid in USDT, or `false` for those paid in outcome tokens. required: true deprecated: false explode: true - name: occurred_after schema: type: integer format: int64 in: query description: Only include events that occurred strictly after this unix timestamp (milliseconds) required: false deprecated: false explode: true - name: occurred_before schema: type: integer format: int64 in: query description: Only include events that occurred strictly before this unix timestamp (milliseconds) required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/FeeEventsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null FeeEventData: type: object title: FeeEventData description: A single on-chain fee distribution event (referral fee or maker rebate). required: - transactionHash - createdAt - takerFeeTokenId - amount - price properties: transactionHash: type: string createdAt: type: string takerFeeTokenId: type: string amount: type: string marketQuestion: type: string marketOutcomeName: type: string price: type: string FeeEventsResponse: type: object title: FeeEventsResponse description: Paginated fee event response. required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/FeeEventData" ``` # Get positions (/api/positions/get-positions) `GET https://api.predict.fun/v1/positions` Get data about your positions. Requires JWT authentication. ```yaml paths: /positions: get: tags: - Positions summary: Get positions description: |- Get data about your positions. Requires JWT authentication. parameters: - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 100. Defaults to 20. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: marketId schema: type: integer format: int64 in: query description: Only positions in this market. Cannot be combined with `marketIds`. required: false deprecated: false explode: true - name: marketIds schema: type: array items: type: integer format: int64 in: query description: |- Only positions in these markets (comma-separated or repeated, up to 100). Cannot be combined with `marketId`. required: false deprecated: false explode: true - name: categoryId schema: type: string maxLength: 256 in: query description: Only positions in the category with this slug, which markets return as `categorySlug`. required: false deprecated: false explode: true - name: isResolved schema: type: boolean in: query description: >- Set to `true` for only positions in resolved markets, or `false` for only those in unresolved markets. Both are listed by default. required: false deprecated: false explode: true - name: includeLost schema: type: boolean in: query description: |- Also list positions in outcomes that settled as losing. Off by default, since those need no action from the holder. required: false deprecated: false explode: true - name: sort schema: $ref: "#/components/schemas/PositionSort" in: query description: >- How to order the positions. Defaults to `EVENT_BLOCK_DESC`, which lists the most recently changed positions first. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/PositionsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PositionData: type: object title: PositionData description: Position data required: - id - market - outcome - amount - valueUsd - averageBuyPriceUsd - pnlUsd properties: id: type: string market: $ref: "#/components/schemas/Market" outcome: $ref: "#/components/schemas/Outcome" amount: type: string description: bigint string valueUsd: type: string averageBuyPriceUsd: type: string pnlUsd: type: string PositionSort: type: string description: Sort options for position queries. enum: - AMOUNT_DESC - EVENT_BLOCK_ASC - EVENT_BLOCK_DESC - SHARES_VALUE_DESC - RETURN_DESC PositionsResponse: type: object title: PositionsResponse description: Response for GET /v1/positions required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/PositionData" PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get positions by address (/api/positions/get-positions-by-address) `GET https://api.predict.fun/v1/positions/{address}` Get positions for a specific wallet address. ```yaml paths: /positions/{address}: get: tags: - Positions summary: Get positions by address description: Get positions for a specific wallet address. parameters: - name: address schema: type: string in: path description: The wallet's address. For a Predict account, use the account's address. required: true deprecated: false explode: true - name: first schema: type: integer format: int32 in: query description: Page size, from 1 to 100. Defaults to 20. A value outside that range is clamped. required: false deprecated: false explode: true - name: after schema: type: string maxLength: 1024 in: query description: The `cursor` from the previous page, to get the next one. required: false deprecated: false explode: true - name: marketId schema: type: integer format: int64 in: query description: Only positions in this market. Cannot be combined with `marketIds`. required: false deprecated: false explode: true - name: marketIds schema: type: array items: type: integer format: int64 in: query description: |- Only positions in these markets (comma-separated or repeated, up to 100). Cannot be combined with `marketId`. required: false deprecated: false explode: true - name: categoryId schema: type: string maxLength: 256 in: query description: Only positions in the category with this slug, which markets return as `categorySlug`. required: false deprecated: false explode: true - name: isResolved schema: type: boolean in: query description: >- Set to `true` for only positions in resolved markets, or `false` for only those in unresolved markets. Both are listed by default. required: false deprecated: false explode: true - name: includeLost schema: type: boolean in: query description: |- Also list positions in outcomes that settled as losing. Off by default, since those need no action from the holder. required: false deprecated: false explode: true - name: sort schema: $ref: "#/components/schemas/PositionSort" in: query description: >- How to order the positions. Defaults to `EVENT_BLOCK_DESC`, which lists the most recently changed positions first. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/PositionsResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PositionData: type: object title: PositionData description: Position data required: - id - market - outcome - amount - valueUsd - averageBuyPriceUsd - pnlUsd properties: id: type: string market: $ref: "#/components/schemas/Market" outcome: $ref: "#/components/schemas/Outcome" amount: type: string description: bigint string valueUsd: type: string averageBuyPriceUsd: type: string pnlUsd: type: string PositionSort: type: string description: Sort options for position queries. enum: - AMOUNT_DESC - EVENT_BLOCK_ASC - EVENT_BLOCK_DESC - SHARES_VALUE_DESC - RETURN_DESC PositionsResponse: type: object title: PositionsResponse description: Response for GET /v1/positions required: - success - data properties: success: type: boolean cursor: type: string data: type: array items: $ref: "#/components/schemas/PositionData" PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Join an activity (/api/activity/join-an-activity) `POST https://api.predict.fun/v1/activities/{activityCode}/join` Registers the signed-in wallet for the activity named by `activityCode`, for example `lol-worlds`. ```yaml paths: /activities/{activityCode}/join: post: tags: - Activity summary: Join an activity description: >- Registers the signed-in wallet for the activity named by `activityCode`, for example `lol-worlds`. parameters: - name: activityCode schema: type: string in: path description: The activity's code, for example `lol-worlds`. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ActivityRegistrationResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ActivityErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ActivityErrorDetails: type: object title: ActivityErrorDetails required: - reason - retryable - violations properties: reason: type: string retryable: type: boolean violations: type: array items: $ref: "#/components/schemas/ActivityViolation" ActivityErrorResponse: type: object title: ActivityErrorResponse description: Activity-specific extension of the standard 400 error response. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs details: $ref: "#/components/schemas/ActivityErrorDetails" ActivityRegistration: type: object title: ActivityRegistration required: - joined properties: joined: type: boolean joinedAt: type: string ActivityRegistrationResponse: type: object title: ActivityRegistrationResponse required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/ActivityRegistration" ActivityViolation: type: object title: ActivityViolation required: - inputIndex - questionId - optionId - code - retryable - message properties: inputIndex: type: integer format: uint64 questionId: type: string optionId: type: string code: type: string retryable: type: boolean message: type: string ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null ``` # Get activity registration (/api/activity/get-activity-registration) `GET https://api.predict.fun/v1/activities/{activityCode}/me/registration` The signed-in wallet's registration for the activity named by `activityCode`, for example `lol-worlds`. ```yaml paths: /activities/{activityCode}/me/registration: get: tags: - Activity summary: Get activity registration description: >- The signed-in wallet's registration for the activity named by `activityCode`, for example `lol-worlds`. parameters: - name: activityCode schema: type: string in: path description: The activity's code, for example `lol-worlds`. required: true deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ActivityRegistrationResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ActivityErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] BearerAuth: [] components: schemas: ActivityErrorDetails: type: object title: ActivityErrorDetails required: - reason - retryable - violations properties: reason: type: string retryable: type: boolean violations: type: array items: $ref: "#/components/schemas/ActivityViolation" ActivityErrorResponse: type: object title: ActivityErrorResponse description: Activity-specific extension of the standard 400 error response. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs details: $ref: "#/components/schemas/ActivityErrorDetails" ActivityRegistration: type: object title: ActivityRegistration required: - joined properties: joined: type: boolean joinedAt: type: string ActivityRegistrationResponse: type: object title: ActivityRegistrationResponse required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/ActivityRegistration" ActivityViolation: type: object title: ActivityViolation required: - inputIndex - questionId - optionId - code - retryable - message properties: inputIndex: type: integer format: uint64 questionId: type: string optionId: type: string code: type: string retryable: type: boolean message: type: string ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null ``` # Search categories and markets (/api/search/search-categories-and-markets) `GET https://api.predict.fun/v1/search` Full-text search across categories and markets. Returns separate arrays for each type, sorted by relevance. ```yaml paths: /search: get: tags: - Search summary: Search categories and markets description: |- Full-text search across categories and markets. Returns separate arrays for each type, sorted by relevance. parameters: - name: query schema: type: string maxLength: 256 in: query description: >- The text to search for. It is matched against titles, market questions and tag names. required: true deprecated: false explode: true - name: includeResolved schema: type: boolean in: query description: >- Whether to also return resolved categories and the markets in them. Defaults to `false`. required: false deprecated: false explode: true - name: limit schema: type: integer format: uint64 in: query description: Most results of each type to return, from 1 to 50. Defaults to 10. required: false deprecated: false explode: true - name: includeStats schema: type: boolean in: query description: >- Whether to fill in `stats` with volume and liquidity figures. Defaults to `false`, which leaves `stats` null. required: false deprecated: false explode: true responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/SearchResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: CategoryStatsData: type: object title: CategoryStatsData description: Category statistics data required: - totalLiquidityUsd - volumeTotalUsd - volume24hUsd - holdersCount properties: totalLiquidityUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double holdersCount: type: integer format: int64 CategoryStatus: type: string description: Category status enum: - OPEN - RESOLVED - REMOVED CategoryVariantDetails: type: object title: CategoryVariantDetails description: |- What a category is about, one key per kind of category. See [`MarketVariantDetails`]. properties: sports: description: Set for a sports category. allOf: - $ref: "#/components/schemas/SportsCategoryDetails" - description: Set for a sports category. crypto: description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: >- Set for a crypto up down category. Superseded by `up_down`, which says the same thing for every asset class. Both are populated for crypto so existing readers keep working. upDown: description: Set for an up down price category, whatever the asset class. allOf: - $ref: "#/components/schemas/UpDownCategoryDetails" - description: Set for an up down price category, whatever the asset class. tweet: description: Set for a tweet count category. allOf: - $ref: "#/components/schemas/TweetCategoryDetails" - description: Set for a tweet count category. CategoryWithStats: type: object title: CategoryWithStats description: Category data with optional inline statistics. required: - id - slug - title - description - imageUrl - isNegRisk - protocol - isYieldBearing - marketVariant - createdAt - publishedAt - markets - startsAt - status - isVisible - resolutionProvider - tags properties: id: type: integer format: int64 slug: type: string title: type: string shortTitle: type: string description: Short, human-readable label, e.g. `"BTC Up or Down 15m"`. `null` when not available. seriesSlug: type: string description: >- Slug of the recurring series this category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. description: type: string imageUrl: type: string isNegRisk: type: boolean protocol: $ref: "#/components/schemas/Protocol" isYieldBearing: type: boolean negRiskOnChainId: type: string description: On-chain NegRisk market ID as a 32-byte hex string. marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this category is about, replacing `variantData`. allOf: - $ref: "#/components/schemas/CategoryVariantDetails" - description: What this category is about, replacing `variantData`. createdAt: type: string publishedAt: type: string markets: type: array items: $ref: "#/components/schemas/Market" startsAt: type: string endsAt: type: string tradingSession: description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. allOf: - $ref: "#/components/schemas/TradingSession" - description: >- Exchange session containing this Up/Down round's entire `startsAt` to `endsAt` window. Session times can differ from the round's times and remain available after closing. Returns `null` when no applicable session covers the complete round. Use each market's `tradingStatus` for its current trading status. status: $ref: "#/components/schemas/CategoryStatus" isVisible: type: boolean resolutionProvider: $ref: "#/components/schemas/ResolutionProvider" tags: type: array items: $ref: "#/components/schemas/Tag" teams: type: array description: |- Deprecated in favour of `variantDetails.sports.teams`. The teams playing, where the category is a fixture. items: $ref: "#/components/schemas/Team" deprecated: true parentSlug: type: string description: The slug of the parent category, when this is a prop/child market. stats: $ref: "#/components/schemas/CategoryStatsData" CryptoUpDownVariantData: type: object title: CryptoUpDownVariantData description: Variant data for CRYPTO_UP_DOWN markets required: - priceFeedProvider properties: priceFeedProvider: $ref: "#/components/schemas/PriceFeedProvider" startPrice: type: number format: double description: Starting price, null before market start endPrice: type: number format: double description: Ending price from Pyth oracle, null if not yet resolved priceFeedId: type: string description: Pyth oracle price feed identifier priceFeedSymbol: type: string description: Price feed symbol (e.g. "BTCUSDT" for Binance, "BTC_USD" for Pyth) ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null EsportsVariantData: type: object title: EsportsVariantData description: Variant data for esports match categories sourced from Grid.gg. required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked series yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" Market: type: object title: Market description: Full market data as returned by the API required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" MarketRewards: type: object title: MarketRewards description: Reward schedule for a market. required: - schedule properties: current: description: Active period at request time, or `null` if no rewards are accruing. allOf: - $ref: "#/components/schemas/RewardPeriod" - description: Active period at request time, or `null` if no rewards are accruing. schedule: type: array description: Full schedule of past and future reward periods for this market. items: $ref: "#/components/schemas/RewardPeriod" MarketStatsData: type: object title: MarketStatsData description: Market statistics data required: - totalLiquidityUsd - liquidity3cAskUsd - volumeTotalUsd - volume24hUsd properties: totalLiquidityUsd: type: number format: double liquidity3cAskUsd: type: number format: double volumeTotalUsd: type: number format: double volume24hUsd: type: number format: double MarketStatus: type: string description: |- Market lifecycle state, from registration through resolution. `Removed` is a synthetic status reported for markets that have been withdrawn, meaning it is not part of the normal lifecycle progression. enum: - REGISTERED - PRICE_PROPOSED - PRICE_DISPUTED - PAUSED - UNPAUSED - RESOLVED - REMOVED MarketTradingStatus: type: string description: Trading status derived from market visibility and deletion state. enum: - OPEN - MATCHING_NOT_ENABLED - CANCEL_ONLY - CLOSED MarketType: type: string description: >- Market type (DB `market.market_type_id` values as repr). Classifies what kind of prediction an individual market represents, independent of the category it belongs to. Works alongside [`MarketVariant`]: the variant describes how the category container renders; the market type describes the prediction mechanic of the individual market. enum: - SPORTS_MONEYLINE - SPORTS_SPREADS - SPORTS_TOTALS - SPORTS_BOTH_TEAMS_TO_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_EXACT_SCORE - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_TEAM_TOTAL_CORNERS - SPORTS_CORNERS_ODD_EVEN - SPORTS_PLAYER_GOALS - SPORTS_PLAYER_ASSISTS - SPORTS_PLAYER_GOALS_PLUS_ASSISTS - SPORTS_PLAYER_SHOTS - SPORTS_PLAYER_SHOTS_ON_TARGET - SPORTS_PLAYER_GOALKEEPER_SAVES - SPORTS_TEAM_TO_ADVANCE - SPORTS_EXTRA_TIME - SPORTS_PENALTY_SHOOTOUT - SPORTS_TEAM_TOTALS - SPORTS_CHILD_MONEYLINE - SPORTS_MAP_HANDICAP - SPORTS_BOTH_TEAMS_TO_SCORE_FIRST_HALF - SPORTS_BOTH_TEAMS_TO_SCORE_SECOND_HALF - SPORTS_FIRST_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - SPORTS_FIRST_CORNER - SPORTS_FIRST_HALF_TOTALS - SPORTS_SECOND_HALF_TOTALS - SPORTS_FIRST_HALF_TEAM_TOTALS - SPORTS_SECOND_HALF_TEAM_TOTALS - SPORTS_FIRST_BLOOD_GAME - SPORTS_KILL_OVER_UNDER_GAME - SPORTS_ROUND_HANDICAP - SPORTS_ROUND_TOTALS - SPORTS_ANYTIME_TOUCHDOWNS - SPORTS_RACE_TO_POINTS - SPORTS_ODD_EVEN MarketVariant: type: string description: |- Classifies the market template type. Controls the UI layout and trading mechanics of a category. See also [`MarketType`] which classifies individual markets within a category. enum: - DEFAULT - SPORTS_MATCH - CRYPTO_UP_DOWN - TWEET_COUNT - SPORTS_TEAM_MATCH - SPORTS_NBA - SPORTS_FIFA_WORLD_CUP - SPORTS_EXACT_SCORE - SPORTS_HALFTIME_RESULT - SPORTS_PROPS - SPORTS_FIFA_FRIENDLIES - ESPORTS_LOL - ESPORTS_CS2 - ESPORTS_DOTA2 - SPORTS_FIRST_TO_SCORE - SPORTS_TOTAL_CORNERS - SPORTS_SECOND_HALF_RESULT - UP_DOWN MarketVariantDetails: type: object title: MarketVariantDetails description: >- What a market is about, one key per kind of market. Grouping by kind rather than discriminating on a type means a kind that gains a payload is a new key, and one that loses it is a key removed, with nothing to keep in step. A market names the kinds that apply to it, which today is at most one. Add a kind by adding a key. Each is filled independently, so a populator sets its own key on the value already there rather than replacing it. properties: sports: description: Set for a market of a sports category. allOf: - $ref: "#/components/schemas/SportsMarketDetails" - description: Set for a market of a sports category. MarketWithStats: type: object title: MarketWithStats description: Market data with optional inline statistics. required: - id - imageUrl - title - question - description - tradingStatus - status - isVisible - isNegRisk - isYieldBearing - feeRateBps - makerFeeBps - oracleQuestionId - conditionId - resolverAddress - outcomes - spreadThreshold - shareThreshold - isBoosted - polymarketConditionIds - categorySlug - createdAt - decimalPrecision - marketVariant - rewards properties: id: type: integer format: int64 imageUrl: type: string title: type: string question: type: string description: type: string tradingStatus: $ref: "#/components/schemas/MarketTradingStatus" status: $ref: "#/components/schemas/MarketStatus" isVisible: type: boolean isNegRisk: type: boolean isYieldBearing: type: boolean feeRateBps: type: integer format: int32 description: Taker fee in basis points, charged on each matched trade. makerFeeBps: type: integer format: int32 description: |- Maker fee in basis points. Always `0`: no market charges makers. Defaults on decode so market entries cached before the field existed still decode rather than reading as misses, and the default is the only value it takes. resolution: $ref: "#/components/schemas/Outcome" oracleQuestionId: type: string conditionId: type: string resolverAddress: type: string outcomes: type: array items: $ref: "#/components/schemas/Outcome" questionIndex: type: integer format: int32 spreadThreshold: type: number format: double shareThreshold: type: integer format: int32 isBoosted: type: boolean boostStartsAt: type: string boostEndsAt: type: string polymarketConditionIds: type: array items: type: string kalshiMarketTicker: type: string categorySlug: type: string seriesSlug: type: string description: |- Slug of the recurring series this market's category is a round of, e.g. `"btc-updown-5m"`. Absent when it is not a round of one. The series and its next rounds are at `GET /v1/series/{slug}`. createdAt: type: string decimalPrecision: type: integer format: int32 marketVariant: $ref: "#/components/schemas/MarketVariant" variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/VariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. allOf: - $ref: "#/components/schemas/MarketVariantDetails" - description: >- What this market is about. Absent when the market is about the fixture as a whole, whose teams its outcomes name. team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this market as a whole is about a single team. deprecated: true marketType: description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. allOf: - $ref: "#/components/schemas/MarketType" - description: |- Classification of the individual market's prediction mechanic. Null for markets created before this field was introduced. rewards: $ref: "#/components/schemas/MarketRewards" stats: $ref: "#/components/schemas/MarketStatsData" Outcome: type: object title: Outcome description: Outcome data (from markets, positions, etc.)/// Outcome data (from markets, positions, etc.) required: - name - indexSet - onChainId properties: name: type: string indexSet: type: integer format: int32 onChainId: type: string description: bigint string status: $ref: "#/components/schemas/OutcomeStatus" bestBid: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: subscribe to the WebSocket firehose for live quotes, or poll `GET /v1/markets/:id/quote` (or `GET /v1/markets/quotes` for a batch), which returns the same price levels without the catalogue around them. Those routes quote the market once, against the outcome whose `indexSet` is 1, so a consumer of the other outcome derives it as described there rather than reading it pre-inverted from here. Top-of-book moves on every trade, and because `Outcome` is embedded in `Market` and `Market` in `Category`, these two fields make every market listing and every category page change from one second to the next. That is what caps the shared-cache TTL on those routes at seconds, and it is why a majority of their cache entries expire into a full origin fetch rather than a revalidation. Nothing else in the catalogue payload changes anywhere near this fast. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true bestAsk: description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true allOf: - $ref: "#/components/schemas/PriceLevel" - description: >- `None` for resolved outcomes or when the side is empty. Deprecated: see [`Outcome::best_bid`] for what to use instead, and why. Still populated; the attribute is a signal to consumers, not a removal. deprecated: true team: description: |- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true allOf: - $ref: "#/components/schemas/Team" - description: >- Deprecated in favour of `variantDetails.sports.team`. Set when this outcome resolves to a specific team. deprecated: true variantData: description: |- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true allOf: - $ref: "#/components/schemas/OutcomeVariantData" - description: >- Deprecated in favour of `variantDetails`, which says the same thing without a type of its own. deprecated: true variantDetails: description: What this outcome is about. Absent when it is about neither team. allOf: - $ref: "#/components/schemas/OutcomeVariantDetails" - description: What this outcome is about. Absent when it is about neither team. OutcomeStatus: type: string description: Resolution status of a market outcome. enum: - WON - LOST OutcomeVariantData: type: object description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. oneOf: - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" - $ref: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" discriminator: propertyName: type mapping: SPORTS_NBA: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_LOL: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_CS2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" ESPORTS_DOTA2: "#/components/schemas/OutcomeVariantData_SportsTeamOutcomeData" OutcomeVariantData_SportsTeamOutcomeData: description: |- Outcome-level variant data, discriminated by sport type. Different leagues carry different outcome metadata. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/SportsTeamOutcomeData" OutcomeVariantDetails: type: object title: OutcomeVariantDetails description: |- What an outcome is about, one key per kind of market. See [`MarketVariantDetails`]. properties: sports: description: Set for an outcome that resolves to a team. allOf: - $ref: "#/components/schemas/SportsOutcomeDetails" - description: Set for an outcome that resolves to a team. PriceFeedProvider: type: string description: Provider of the price feed backing a crypto market. enum: - PYTH - BINANCE - CHAINLINK - ATLAS PriceLevel: type: object title: PriceLevel required: - price - size properties: price: type: number format: double description: In [0.0, 1.0]. size: type: number format: double Protocol: type: string description: |- The protocol a category's markets trade on. Every market in a category shares it, and it decides which exchange contracts a client talks to. enum: - LEGACY - PREDICT ResolutionProvider: type: string description: Resolution provider for a category (maps to `resolution_provider_type` table). enum: - PREDICT_DOT_FUN - THREE_PO - CHAINLINK - ATLAS RewardPeriod: type: object title: RewardPeriod description: A single span during which a market accrues rewards at a fixed hourly rate. required: - hourlyRate - startsAt - endsAt properties: hourlyRate: type: integer format: int32 description: Reward points earned per hour while this period is active. startsAt: type: string endsAt: type: string ScoreboardSource: type: string description: The source a scoreboard is built on. enum: - GRID - POLYMARKET - PREDICT_DOT_FUN SearchData: type: object title: SearchData description: Search results data required: - categories - markets properties: categories: type: array items: $ref: "#/components/schemas/CategoryWithStats" markets: type: array items: $ref: "#/components/schemas/MarketWithStats" SearchResponse: type: object title: SearchResponse description: Response for GET /v1/search required: - success - data properties: success: type: boolean data: $ref: "#/components/schemas/SearchData" SportsCategoryDetails: type: object title: SportsCategoryDetails description: What a sports category is about. required: - provider - teams properties: provider: description: The provider that supplies the fixture. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that supplies the fixture. providerEventId: type: string description: The provider's own id for the fixture, where it has one. teams: type: array description: The two sides of the fixture. items: $ref: "#/components/schemas/SportsEventTeam" scoreboard: description: Absent until a source has reported on the match. allOf: - $ref: "#/components/schemas/SportsScoreboard" - description: Absent until a source has reported on the match. SportsDataProvider: type: string description: Sports data provider. enum: - PREDICT_SPORTS - GRID - POLYMARKET SportsEventTeam: type: object title: SportsEventTeam description: A sportsdata provider team as exposed in category variant data. required: - id properties: id: type: integer format: int64 description: Team identifier. name: type: string abbreviation: type: string description: Team key abbreviation (e.g., "MIN", "SA"). Null when not available. providerTeamId: type: string description: The team identifier used by the sportsdata provider. Null when no game is linked. isHome: type: boolean description: >- Marks the side the fixture lists as home, which may be a neutral venue. Null when the provider does not say. record: description: Omitted when the provider sends none, or sends one whose format is not known. allOf: - $ref: "#/components/schemas/SportsTeamRecord" - description: Omitted when the provider sends none, or sends one whose format is not known. color: type: string description: >- Team colour as a hex string from the provider. Omitted when the provider does not send one. SportsFifaFriendliesVariantData: type: object title: SportsFifaFriendliesVariantData description: Variant data for SPORTS_FIFA_FRIENDLIES categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsFifaWorldCupVariantData: type: object title: SportsFifaWorldCupVariantData description: Variant data for SPORTS_FIFA_WORLD_CUP categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsMarketDetails: type: object title: SportsMarketDetails description: What a market of a sports category is about. properties: provider: description: The provider that links the team. Absent when no team is named. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. Absent when no team is named. team: description: The team this market is about, when it is about one. . allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this market is about, when it is about one. . line: type: number format: double description: >- The line this market is about: a total, a spread, or a player count. TODO which side of a spread the line runs is not stated. It belongs to the market's `team`, so it needs the importer to record the subject: polymarket orders a spread's outcomes subject first, and where the feed carries `marketMetadata.opticOddsTeamId` that agrees with it. Until then only the deprecated `variantData.spreadSide` says anything, and only for soccer, where a slug pattern happens to name the side. gameNumber: type: integer format: int32 SportsNbaVariantData: type: object title: SportsNbaVariantData description: Variant data for SPORTS_NBA categories (SportsDataIO). required: - provider - teams properties: provider: $ref: "#/components/schemas/SportsDataProvider" providerEventId: type: string description: Null when the category has no linked game yet. teams: type: array items: $ref: "#/components/schemas/SportsEventTeam" SportsOutcomeDetails: type: object title: SportsOutcomeDetails description: >- What an outcome of a sports market is about. Carries no type of its own, for the same reason as [`SportsMarketDetails`]. required: - provider - team properties: provider: description: The provider that links the team. allOf: - $ref: "#/components/schemas/SportsDataProvider" - description: The provider that links the team. team: description: The team this outcome resolves to. allOf: - $ref: "#/components/schemas/SportsEventTeam" - description: The team this outcome resolves to. SportsPropsMarketData: type: object title: SportsPropsMarketData description: |- Per-market data for `SPORTS_PROPS` prop markets. Fields are market-type-specific: `spreadSide` is set for spread markets, `line` is set for totals markets. Both-teams-to-score markets emit no `variantData` of this shape. properties: spreadSide: description: Which side of the parent match this spread market is on. Present for spread markets only. allOf: - $ref: "#/components/schemas/TeamSide" - description: Which side of the parent match this spread market is on. Present for spread markets only. line: type: number format: double description: The line value for this totals market. Present for totals markets only. SportsScoreboard: type: object title: SportsScoreboard description: How a match stands. required: - source - score - games properties: source: description: The source the record is built on. allOf: - $ref: "#/components/schemas/ScoreboardSource" - description: The source the record is built on. bestOf: type: integer format: int32 isLive: type: boolean finished: type: boolean currentGame: type: integer format: int32 description: Counted from one. period: type: string description: "As the feed labels it: `1H`, `Q3`, `2/3`." clock: type: string description: >- The game clock as the live feed shows it. Its meaning depends on the sport: time left in the period for basketball, time played for soccer. score: type: array description: Games won in a series, the match score otherwise. items: $ref: "#/components/schemas/SportsTeamScore" games: type: array items: $ref: "#/components/schemas/SportsScoreboardGame" SportsScoreboardGame: type: object title: SportsScoreboardGame description: One game of a best-of series. required: - gameNumber - isLive - finished - scores properties: gameNumber: type: integer format: int32 description: Counted from one. isLive: type: boolean finished: type: boolean winnerTeamId: type: integer format: int64 scores: type: array description: Empty when only the winner is known. items: $ref: "#/components/schemas/SportsTeamScore" SportsTeamOutcomeData: type: object title: SportsTeamOutcomeData description: Outcome variant data for sports markets where the outcome maps to a team. required: - provider - team properties: provider: $ref: "#/components/schemas/SportsDataProvider" team: $ref: "#/components/schemas/SportsEventTeam" SportsTeamRecord: type: object title: SportsTeamRecord description: A team's win-loss record, in one shape whatever the sport or provider. required: - wins - losses properties: wins: type: integer format: int32 losses: type: integer format: int32 draws: type: integer format: int32 description: Ties count as draws. Omitted where the sport's record has no draws, as in basketball. overtimeLosses: type: integer format: int32 description: Ice hockey losses after regulation, counted apart from `losses`. Omitted elsewhere. SportsTeamScore: type: object title: SportsTeamScore description: One team's count in a series or a game. required: - teamId - score properties: teamId: type: integer format: int64 description: The id the category's `teams` entries carry. score: type: integer format: int32 description: Games won, goals, rounds or kills, by what the entry is. Tag: type: object title: Tag description: Tag data required: - id - name properties: id: type: string description: bigint string name: type: string level: type: integer format: int32 description: >- Hierarchy level of the tag (1, 2, or 3), or null for flat tags. Tags form a tree up to three levels deep, e.g. Sports -> Soccer -> EPL. parentId: type: string description: >- Id of the parent tag as a bigint string, or null for root and flat tags. Together with `level` this allows rendering nested tag menus. makerRebateBps: type: integer format: int32 description: >- Maker rebate in basis points for fills on categories carrying this tag, or null if no rebate applies. Team: type: object title: Team description: Sports team identity. required: - id - name - abbreviation - league properties: id: type: integer format: int64 name: type: string abbreviation: type: string alias: type: string league: type: string TeamSide: type: string description: Which side of a sports match a team plays on. enum: - HOME - AWAY TradingSession: type: object title: TradingSession description: Exchange session during which the settlement price is live, without trading buffers. required: - opensAt - closesAt properties: opensAt: type: string description: Session opening time as an ISO 8601 UTC timestamp with millisecond precision. closesAt: type: string description: Session closing time as an ISO 8601 UTC timestamp with millisecond precision. TweetCategoryDetails: type: object title: TweetCategoryDetails description: What a tweet count category is about. required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: The account whose posts are counted. tweetCount: type: integer format: int64 description: Posts counted so far within the category's window. TweetCountVariantData: type: object title: TweetCountVariantData description: Variant data for TWEET_COUNT markets required: - xAccountUsername - tweetCount properties: xAccountUsername: type: string description: X (Twitter) account username being tracked tweetCount: type: integer format: int64 description: Current count of tweets within the category date range UpDownCategoryDetails: type: object title: UpDownCategoryDetails description: What an up down price category is about, whatever the asset class. required: - priceFeedProvider properties: priceFeedProvider: description: Who publishes the price this category settles against. allOf: - $ref: "#/components/schemas/PriceFeedProvider" - description: Who publishes the price this category settles against. priceFeedId: type: string description: The feed's own identifier, where it has one. priceFeedSymbol: type: string description: The symbol the feed publishes under, eg. `BTCUSDT` or `SPYUSDT`. startPrice: type: number format: double description: The price at the start of the window, once the window has opened. endPrice: type: number format: double description: The price at the end of the window, once it has closed. VariantData: type: object description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. oneOf: - $ref: "#/components/schemas/VariantData_CryptoUpDownVariantData" - $ref: "#/components/schemas/VariantData_TweetCountVariantData" - $ref: "#/components/schemas/VariantData_SportsNbaVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" - $ref: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" - $ref: "#/components/schemas/VariantData_SportsPropsMarketData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" - $ref: "#/components/schemas/VariantData_EsportsVariantData" discriminator: propertyName: type mapping: CRYPTO_UP_DOWN: "#/components/schemas/VariantData_CryptoUpDownVariantData" TWEET_COUNT: "#/components/schemas/VariantData_TweetCountVariantData" SPORTS_NBA: "#/components/schemas/VariantData_SportsNbaVariantData" SPORTS_FIFA_WORLD_CUP: "#/components/schemas/VariantData_SportsFifaWorldCupVariantData" SPORTS_FIFA_FRIENDLIES: "#/components/schemas/VariantData_SportsFifaFriendliesVariantData" SPORTS_PROPS: "#/components/schemas/VariantData_SportsPropsMarketData" ESPORTS_LOL: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_CS2: "#/components/schemas/VariantData_EsportsVariantData" ESPORTS_DOTA2: "#/components/schemas/VariantData_EsportsVariantData" VariantData_CryptoUpDownVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - CRYPTO_UP_DOWN examples: - CRYPTO_UP_DOWN - $ref: "#/components/schemas/CryptoUpDownVariantData" VariantData_EsportsVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - ESPORTS_DOTA2 examples: - ESPORTS_DOTA2 - $ref: "#/components/schemas/EsportsVariantData" VariantData_SportsFifaFriendliesVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_FRIENDLIES examples: - SPORTS_FIFA_FRIENDLIES - $ref: "#/components/schemas/SportsFifaFriendliesVariantData" VariantData_SportsFifaWorldCupVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_FIFA_WORLD_CUP examples: - SPORTS_FIFA_WORLD_CUP - $ref: "#/components/schemas/SportsFifaWorldCupVariantData" VariantData_SportsNbaVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_NBA examples: - SPORTS_NBA - $ref: "#/components/schemas/SportsNbaVariantData" VariantData_SportsPropsMarketData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - SPORTS_PROPS examples: - SPORTS_PROPS - $ref: "#/components/schemas/SportsPropsMarketData" VariantData_TweetCountVariantData: description: >- Variant-specific metadata, discriminated by the `type` field. Known variant types are deserialized into typed structs. Unknown variant types are handled gracefully by the [`deserialize_variant_data`] helper on parent structs so that new upstream types don't break deserialization. allOf: - type: object required: - type properties: type: type: string enum: - TWEET_COUNT examples: - TWEET_COUNT - $ref: "#/components/schemas/TweetCountVariantData" ``` # Get available languages (/api/translations/get-available-languages) `GET https://api.predict.fun/v1/languages` Get all enabled translation languages. Use the returned codes with the `/categories/:slug/translations` endpoint. ```yaml paths: /languages: get: tags: - Translations summary: Get available languages description: |- Get all enabled translation languages. Use the returned codes with the `/categories/:slug/translations` endpoint. responses: "200": description: "" content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/LanguagesResponse" "400": description: Bad request (400) - Invalid input or parameters content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Unauthorized (401) - Missing or invalid authentication content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden (403) - Valid auth but insufficient permissions content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Not found (404) - Resource doesn't exist content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "410": description: Gone (410) - Resource has been permanently removed content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many requests (429) - Rate limit exceeded content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error (500) - Unexpected error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "502": description: Bad gateway (502) - Upstream API error content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Service unavailable (503) - Upstream API unreachable content: application/json; charset=utf-8: schema: $ref: "#/components/schemas/ErrorResponse" security: - ApiKeyAuth: [] components: schemas: ErrorCode: type: string description: Machine-readable error codes for all API error responses. enum: - unauthorized - forbidden - not_found - gone - too_many_requests - internal_server_error - bad_gateway - service_unavailable - bad_request - activity_invalid_input - activity_lock_rejected - activity_state_conflict - invalid_cursor - token_not_found - create_order_invalid_fee_rate_bps - create_order_invalid_nonce - create_order_invalid_salt - create_order_invalid_slippage_bps - create_order_invalid_signer_address - create_order_invalid_token_id - create_order_invalid_signature_hex - create_order_invalid_expiration_timestamp - create_order_invalid_numeric_value - create_order_invalid_decimal_value - create_order_market_token_not_found - create_order_market_not_trading - create_order_market_orders_disabled - create_order_trading_disabled - create_order_signer_mismatch - create_order_reserved_balance_policy_invalid - create_order_fill_or_kill_not_supported - create_order_post_only_not_supported - create_order_maker_signer_mismatch - create_order_expiry_too_far - create_order_expiry_too_soon - create_order_min_order_value_not_met - create_order_min_order_size_not_met - create_order_limit_amounts_mismatch - create_order_price_precision_too_high - create_order_market_amounts_mismatch - create_order_slippage_non_market - create_order_maker_fee_out_of_range - create_order_taker_fee_out_of_range - create_order_fee_rate_too_low - create_order_price_field_zero - create_order_amount_field_zero - create_order_price_field_precision_invalid - create_order_contract_order_expired - create_order_contract_invalid_signature - create_order_contract_fee_too_high - create_order_contract_invalid_token_id - create_order_contract_order_filled_or_cancelled - create_order_contract_invalid_nonce - create_order_contract_invalid_signature_length - create_order_contract_paused - create_order_exchange_paused - create_order_unsupported_wallet - create_order_hash_mismatch - create_order_duplicate_order - create_order_max_orders_per_market_exceeded - create_order_insufficient_collateral_allowance - create_order_insufficient_collateral_balance - create_order_exchange_not_approved - create_order_neg_risk_adapter_not_approved - create_order_insufficient_shares_balance - create_order_price_out_of_range - blocktrade_duplicate_order - blocktrade_self_fulfil - blocktrade_fulfil_symmetry - blocktrade_complement_not_supported - blocktrade_max_open_orders_exceeded ErrorResponse: type: object title: ErrorResponse description: |- Structured error response body. Every error path in the API produces this shape. required: - success - code - error - message - timestamp properties: success: type: boolean description: Always `false` for errors code: type: integer format: uint16 description: HTTP status code error: description: |- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). allOf: - $ref: "#/components/schemas/ErrorCode" - description: >- Stable machine-readable error code for client-side matching. For 400 responses this is the granular bad-request code (e.g. `create_order_insufficient_collateral_allowance`). For all other responses it is the HTTP-status-derived code (e.g. `unauthorized`). message: type: string description: Human-readable error message timestamp: type: string description: RFC 3339 timestamp of when the error occurred trace: type: string description: Trace ID for correlating with server logs examples: - code: 500 error: internal_server_error message: Internal server error success: false timestamp: 2026-01-01T00:00:00.000000Z trace: null Language: type: object title: Language required: - code - name properties: code: type: string name: type: string LanguagesResponse: type: object title: LanguagesResponse description: Response for GET /v1/languages required: - success - data properties: success: type: boolean data: type: array items: $ref: "#/components/schemas/Language" ``` # Subscribe (/websocket/requests/subscribe) You send these messages to Predict WebSocket API. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Subscribe request Content type: `application/json` Payload: `SubscribePayload` Example payload: ```json { "method": "subscribe", "requestId": 1, "params": [ "predictOrderbook/123" ] } ``` ## Reply Sent to `/ws`. ### Response Content type: `application/json` Payload: `ResponsePayload` Example payload: ```json { "type": "R", "requestId": 1, "success": true } ``` ## Types ```ts export type SubscribeMessage = SubscribePayload; export interface SubscribePayload { method: "subscribe"; requestId: RequestId; /** The topic to subscribe to. Only the first entry is read. */ params: Topic[]; } /** * The answer to a subscribe, unsubscribe or setLanguage request. A frame the server cannot * read (invalid JSON, an unknown `method`, or a missing `requestId` or `params`) is * answered with `requestId` `-1` and the error `invalid_json`. A frame over 8 KiB closes * the connection. * */ export interface ResponsePayload { type: "R"; requestId: RequestId; success: boolean; error?: Error; } /** * A number you choose, echoed on the response so you can match the two. Use a * non-negative one: the server answers a frame it cannot read with `-1`. * */ export type RequestId = number; /** A topic name and its parameter, such as `predictOrderbook/123`. */ export type Topic = string; /** Present only when `success` is `false`. */ export interface Error { /** * - `invalid_json`: the frame could not be read as a request. * - `invalid_topic`: the topic is missing, unknown or has an invalid parameter. * - `invalid_params`: setLanguage without a language code. * - `invalid_credentials`: the wallet JWT is invalid, expired or revoked. * - `internal_server_error`: the server failed to check the request. * */ code: "invalid_json" | "invalid_topic" | "invalid_params" | "invalid_credentials" | "internal_server_error"; /** What was wrong. Absent for `invalid_json`. */ message?: string; } ``` # Unsubscribe (/websocket/requests/unsubscribe) You send these messages to Predict WebSocket API. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Unsubscribe request Content type: `application/json` Payload: `UnsubscribePayload` Example payload: ```json { "method": "unsubscribe", "requestId": 2, "params": [ "predictOrderbook/123" ] } ``` ## Reply Sent to `/ws`. ### Response Content type: `application/json` Payload: `ResponsePayload` Example payload: ```json { "type": "R", "requestId": 1, "success": true } ``` ## Types ```ts export type UnsubscribeMessage = UnsubscribePayload; export interface UnsubscribePayload { method: "unsubscribe"; requestId: RequestId; /** The topic to unsubscribe from. Only the first entry is read. */ params: Topic[]; } /** * The answer to a subscribe, unsubscribe or setLanguage request. A frame the server cannot * read (invalid JSON, an unknown `method`, or a missing `requestId` or `params`) is * answered with `requestId` `-1` and the error `invalid_json`. A frame over 8 KiB closes * the connection. * */ export interface ResponsePayload { type: "R"; requestId: RequestId; success: boolean; error?: Error; } /** * A number you choose, echoed on the response so you can match the two. Use a * non-negative one: the server answers a frame it cannot read with `-1`. * */ export type RequestId = number; /** A topic name and its parameter, such as `predictOrderbook/123`. */ export type Topic = string; /** Present only when `success` is `false`. */ export interface Error { /** * - `invalid_json`: the frame could not be read as a request. * - `invalid_topic`: the topic is missing, unknown or has an invalid parameter. * - `invalid_params`: setLanguage without a language code. * - `invalid_credentials`: the wallet JWT is invalid, expired or revoked. * - `internal_server_error`: the server failed to check the request. * */ code: "invalid_json" | "invalid_topic" | "invalid_params" | "invalid_credentials" | "internal_server_error"; /** What was wrong. Absent for `invalid_json`. */ message?: string; } ``` # Set language (/websocket/requests/set-language) You send these messages to Predict WebSocket API. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Set language request Content type: `application/json` Payload: `SetLanguagePayload` Example payload: ```json { "method": "setLanguage", "requestId": 3, "params": [ "es" ] } ``` ## Reply Sent to `/ws`. ### Response Content type: `application/json` Payload: `ResponsePayload` Example payload: ```json { "type": "R", "requestId": 1, "success": true } ``` ## Types ```ts export type SetLanguageMessage = SetLanguagePayload; export interface SetLanguagePayload { method: "setLanguage"; requestId: RequestId; /** The language code. Only the first entry is read. */ params: string[]; } /** * The answer to a subscribe, unsubscribe or setLanguage request. A frame the server cannot * read (invalid JSON, an unknown `method`, or a missing `requestId` or `params`) is * answered with `requestId` `-1` and the error `invalid_json`. A frame over 8 KiB closes * the connection. * */ export interface ResponsePayload { type: "R"; requestId: RequestId; success: boolean; error?: Error; } /** * A number you choose, echoed on the response so you can match the two. Use a * non-negative one: the server answers a frame it cannot read with `-1`. * */ export type RequestId = number; /** Present only when `success` is `false`. */ export interface Error { /** * - `invalid_json`: the frame could not be read as a request. * - `invalid_topic`: the topic is missing, unknown or has an invalid parameter. * - `invalid_params`: setLanguage without a language code. * - `invalid_credentials`: the wallet JWT is invalid, expired or revoked. * - `internal_server_error`: the server failed to check the request. * */ code: "invalid_json" | "invalid_topic" | "invalid_params" | "invalid_credentials" | "internal_server_error"; /** What was wrong. Absent for `invalid_json`. */ message?: string; } ``` # Heartbeat (/websocket/connection/heartbeat) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Heartbeat Content type: `application/json` Payload: `HeartbeatPayload` Example payload: ```json { "type": "M", "topic": "heartbeat", "data": 1736696400000 } ``` ## Reply Sent to `/ws`. ### Heartbeat echo Content type: `application/json` Payload: `HeartbeatReplyPayload` Example payload: ```json { "method": "heartbeat", "data": 1736696400000 } ``` ## Types ```ts export type HeartbeatMessage = HeartbeatPayload; export interface HeartbeatPayload { type: "M"; topic: "heartbeat"; /** The server time, to echo back. */ data: number; } export interface HeartbeatReplyPayload { method: "heartbeat"; /** The `data` of the latest heartbeat, unchanged. */ data: number; } ``` # Reconnect notice (/websocket/connection/reconnect-notice) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Reconnect notice Content type: `application/json` Payload: `ReconnectPayload` Example payload: ```json { "type": "M", "topic": "system", "data": { "event": "reconnect", "reason": "server_shutdown", "graceMs": 7431 } } ``` ## Types ```ts export type ReconnectMessage = ReconnectPayload; export interface ReconnectPayload { type: "M"; /** Reserved. It cannot be subscribed to, and its messages arrive unasked. */ topic: "system"; data: { /** `reconnect`. Treat an unknown value as reconnect soon. */ event: string; /** `server_shutdown`. Treat an unknown value as reconnect soon. */ reason: string; /** Roughly how long the connection keeps delivering before it closes. */ graceMs: number; }; } ``` # Order book (/websocket/topics/order-book) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Order book Content type: `application/json` Payload: `OrderbookPayload` Example payload: ```json { "type": "M", "topic": "predictOrderbook/123", "data": { "version": 1, "marketId": 123, "updateTimestampMs": 1736696400000, "orderCount": 42, "asks": [ [ 0.62, 1500 ], [ 0.63, 800 ] ], "bids": [ [ 0.61, 2000 ], [ 0.6, 1200 ] ], "lastOrderSettled": { "id": "987654321", "price": "0.62", "kind": "LIMIT", "marketId": 123, "side": "Ask", "outcome": "Yes" }, "settlementsPending": { "asks": [ [ 0.62, 310 ] ], "bids": [] } } } ``` ## Types ```ts export type OrderbookMessage = OrderbookPayload; export type OrderbookPayload = TopicMessage & { data?: Orderbook; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } export interface Orderbook { version: 1; marketId: number; updateTimestampMs: number; orderCount: number; /** Ask levels, lowest price first. */ asks: PriceLevel[]; /** Bid levels, highest price first. */ bids: PriceLevel[]; /** The latest settled order, or `null` before the first settlement. */ lastOrderSettled: null | SettledOrder; /** * The orders whose settlement is in flight, per price. `asks` holds the resting asks * and `bids` the resting bids, with prices on the YES scale, so an ask for NO appears * under `asks` at its YES price. * */ settlementsPending: { asks: PendingSettlementLevel[]; bids: PendingSettlementLevel[]; }; } /** `[price, quantity]`. The price is the YES price between 0 and 1, the quantity is in shares. */ export type PriceLevel = number[]; export interface SettledOrder { id: string; /** The YES price, with 2 decimals. */ price: string; /** The order's strategy. */ kind: "LIMIT" | "MARKET"; side: "Ask" | "Bid"; outcome: "Yes" | "No"; marketId: number; } /** * `[price, value]`. The price is the YES price between 0 and 1. The value is in USDT: the * shares being settled at that price, multiplied by the price. * */ export type PendingSettlementLevel = number[]; ``` # Top of book (/websocket/topics/top-of-book) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Top of book Content type: `application/json` Payload: `TopOfBookPayload` Example payload: ```json { "type": "M", "topic": "predictTopOfBook/123", "data": { "marketId": 123, "bestBid": [ 0.61, 2000 ], "bestAsk": [ 0.62, 1500 ], "lastOrderSettled": { "id": "987654321", "price": "0.62", "kind": "LIMIT", "marketId": 123, "side": "Ask", "outcome": "Yes" } } } ``` ## Types ```ts export type TopOfBookMessage = TopOfBookPayload; export type TopOfBookPayload = TopicMessage & { data?: TopOfBook; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } /** * The first level on each side of the market's order book. It has no timestamp, because a * message is only sent on a change. To know how fresh the book is, read the order book. * */ export interface TopOfBook { marketId: number; /** The highest bid, or `null` when there are no bids. */ bestBid: null | PriceLevel; /** The lowest ask, or `null` when there are no asks. */ bestAsk: null | PriceLevel; /** The latest settled order, or `null` before the first settlement. */ lastOrderSettled: null | SettledOrder; } /** `[price, quantity]`. The price is the YES price between 0 and 1, the quantity is in shares. */ export type PriceLevel = number[]; export interface SettledOrder { id: string; /** The YES price, with 2 decimals. */ price: string; /** The order's strategy. */ kind: "LIMIT" | "MARKET"; side: "Ask" | "Bid"; outcome: "Yes" | "No"; marketId: number; } ``` # Trading status (/websocket/topics/trading-status) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Trading status Content type: `application/json` Payload: `TradingStatusPayload` Example payload: ```json { "type": "M", "topic": "predictTradingStatus/123", "data": { "kind": "tradingStatusUpdate", "tsMs": 1736696400000, "marketId": 123, "tradingStatus": "OPEN" } } ``` ## Types ```ts export type TradingStatusMessage = TradingStatusPayload; export type TradingStatusPayload = TopicMessage & { data?: { kind: "tradingStatusUpdate"; tsMs: number; marketId: number; tradingStatus: "OPEN" | "MATCHING_NOT_ENABLED" | "CANCEL_ONLY" | "CLOSED"; }; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } ``` # Market status (/websocket/topics/market-status) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Market status Content type: `application/json` Payload: `MarketStatusPayload` Example payload: ```json { "type": "M", "topic": "predictMarketStatus/123", "data": { "kind": "marketStatusUpdate", "tsMs": 1736696400000, "marketId": 123, "status": "RESOLVED", "marketOutcomes": [ { "label": "Yes", "indexSet": 1, "price": "1000000000000000000", "onChainId": "4567890123456789012345678901234567890" }, { "label": "No", "indexSet": 2, "price": "0", "onChainId": "9876543210987654321098765432109876543" } ] } } ``` ## Types ```ts export type MarketStatusMessage = MarketStatusPayload; export type MarketStatusPayload = TopicMessage & { data?: { kind: "marketStatusUpdate"; tsMs: number; marketId: number; status: "REGISTERED" | "PRICE_DISPUTED" | "RESOLVED"; /** Each outcome's final price. Sent with `RESOLVED`. */ marketOutcomes?: ResolvedOutcome[]; }; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } export interface ResolvedOutcome { label: string; indexSet: number; /** The price in wei, where `1000000000000000000` is 1. */ price: "0" | "500000000000000000" | "1000000000000000000"; /** The outcome's on-chain token ID, in decimal. */ onChainId: string; } ``` # Market changed (/websocket/topics/market-changed) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Market changed Content type: `application/json` Payload: `MarketChangedPayload` Example payload: ```json { "type": "M", "topic": "predictMarketChanged/123", "data": { "kind": "marketChanged", "patchKind": null, "tsMs": 1736696400000, "marketId": 123 } } ``` ## Types ```ts export type MarketChangedMessage = MarketChangedPayload; export type MarketChangedPayload = TopicMessage & { data?: { kind: "marketChanged"; /** * What changed. `null` for a general change. `marketRewards` comes without a * `patch`. The other values come with one. * */ patchKind: null | "marketRewards" | "translationUpdate" | "bulletinBoardUpdate" | "cryptoUpDownVariantData"; tsMs: number; marketId: number; /** The changed data, keyed by what it is. */ patch?: TranslationUpdate | { bulletinBoardUpdate: { id: number; marketId: number; content: string; createdAt: string; updatedAt: null | string; }; } | { /** The price feed of an up-or-down crypto market. */ variantData: { priceFeedProvider: "BINANCE" | "CHAINLINK" | "ATLAS"; startPrice: number | null; endPrice: number | null; /** `null` for Binance, a hex feed ID otherwise. */ priceFeedId: string | null; priceFeedSymbol: string | null; }; }; }; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } export interface TranslationUpdate { /** A translation changed. Refetch the text in that language. */ translationUpdate: { languageCode: string; field: string; }; } ``` # Category changed (/websocket/topics/category-changed) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Category changed Content type: `application/json` Payload: `CategoryChangedPayload` Example payload: ```json { "type": "M", "topic": "predictCategoryChanged/45", "data": { "kind": "categoryChanged", "patchKind": null, "tsMs": 1736696400000, "categoryId": 45, "slug": "politics" } } ``` ## Types ```ts export type CategoryChangedMessage = CategoryChangedPayload; export type CategoryChangedPayload = TopicMessage & { data?: { kind: "categoryChanged"; /** What changed, `null` for a general change. */ patchKind: null | "translationUpdate" | "volumeUpdate" | "tradingSessionUpdate"; tsMs: number; categoryId: number; slug: string; /** The changed data, keyed by what it is. */ patch?: TranslationUpdate | { volumeUpdate: { volumeTotalUsd: number; }; } | { tradingSessionUpdate: { /** The current trading session, or `null` outside one. */ tradingSession: null | { opensAt: string; closesAt: string; }; }; }; } | { categoryId: number; tsMs: number; }; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } export interface TranslationUpdate { /** A translation changed. Refetch the text in that language. */ translationUpdate: { languageCode: string; field: string; }; } ``` # Wallet events (/websocket/topics/wallet-events) Predict WebSocket API sends these messages to you. Address: `/ws` ## Servers - `wss://ws.predict.fun` (BNB Mainnet, wss) - `wss://ws-testnet.predict.fun` (BNB Testnet, wss) ## Authorization Connecting to `mainnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. Connecting to `testnet` takes one of: - `apiKeyHeader` (httpApiKey in header `x-api-key`): The API key in the handshake's `x-api-key` header. Use this where you can. - `apiKeyQuery` (httpApiKey in query `apiKey`): The API key as the `apiKey` query parameter, for clients that cannot set headers. ## Messages ### Wallet event Content type: `application/json` Payload: `WalletEventPayload` Example payload: ```json { "type": "M", "topic": "predictWalletEvents/eyJhbGciOi...", "data": { "type": "orderAccepted", "orderId": "123456789", "orderHash": "0x3f1c...9a2b", "walletAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72", "timestamp": 1736696400000, "details": { "marketId": 123, "outcomeIndex": 1, "marketQuestion": "Will X happen?", "outcome": "YES", "outcomeName": "Yes", "quoteType": "BID", "quantity": "100.000", "quantityFilled": "0.000", "price": "0.620", "value": "62.00", "valueFilled": "0.00", "strategyType": "LIMIT", "categorySlug": "politics" } } } ``` ## Types ```ts export type WalletEventsMessage = WalletEventPayload; export type WalletEventPayload = TopicMessage & { data?: OrderAccepted | OrderNotAccepted | OrderMarketMatchPlan | OrderExpired | OrderCancelled | OrderTransactionSubmitted | OrderTransactionSuccess | OrderTransactionFailed; }; export interface TopicMessage { type: "M"; /** The topic string the subscription was made with. */ topic: string; } /** The order was placed in the book. */ export type OrderAccepted = WalletEvent & { type?: "orderAccepted"; }; /** The order was rejected. */ export type OrderNotAccepted = WalletEvent & { type?: "orderNotAccepted"; reason: "noMarketMatch" | "rejectedDuplicate" | "rejectedPostOnly"; /** * Sent with `noMarketMatch`: the settlement that failed, or `null` when nothing * matched. * */ settlementId?: string | null; }; /** How a market order is going to be filled. */ export type OrderMarketMatchPlan = WalletEvent & { type?: "orderMarketMatchPlan"; /** In wei. */ amount: string; /** In wei. */ amountFilled: string; /** In wei. */ plannedFill: string; /** In wei. */ remainderCancelled: string; settlementIds: string[]; }; /** The order reached its end time. */ export type OrderExpired = WalletEvent & { type?: "orderExpired"; }; /** The order was cancelled. */ export type OrderCancelled = WalletEvent & { type?: "orderCancelled"; /** Present when self-trade prevention cancelled the order. */ reason?: "selfTradePrevention"; }; /** * The order matched and its settlement was submitted on-chain. When two of the wallet's own * orders trade with each other, each one gets an event, one as taker and one as maker. * */ export type OrderTransactionSubmitted = WalletEvent & { type?: "orderTransactionSubmitted"; settlementId: string; kind: "PURCHASE" | "SALE"; isMaker: boolean; fill: Fill; }; /** The settlement succeeded on-chain. */ export type OrderTransactionSuccess = WalletEvent & { type?: "orderTransactionSuccess"; settlementId: string; fill: Fill; isMaker?: boolean; fee?: { amountWei: string; type: "COLLATERAL" | "SHARES"; }; }; /** The settlement failed on-chain. */ export type OrderTransactionFailed = WalletEvent & { type?: "orderTransactionFailed"; settlementId: string; fill: Fill; }; /** The fields every wallet event has. */ export interface WalletEvent { type: string; orderId: string; orderHash: string; /** The wallet's checksummed address. Absent when it could not be looked up. */ walletAddress?: string; timestamp: number; details: OrderDetails; } export interface Fill { executedPriceWei: string; executedSizeWei: string; /** The price times the size, divided by 1e18. */ executedValueWei: string; } export interface OrderDetails { marketId: number; /** The outcome's index set, `1` or `2` in a binary market. */ outcomeIndex: number; quoteType: "BID" | "ASK"; /** Shares, with 3 decimals. */ quantity: string; /** Shares, with 3 decimals. */ quantityFilled: string; /** With 3 decimals. */ price: string; /** With 2 decimals. */ value: string; /** With 2 decimals. */ valueFilled: string; marketQuestion: string; categorySlug: string; strategyType: "MARKET" | "LIMIT"; outcome: "YES" | "NO"; /** The outcome's name, in the language chosen with setLanguage. */ outcomeName: string; } ```