# Predictefy — Full Documentation > Unified prediction-market intelligence and execution infrastructure: one normalized REST API, > TypeScript/Python SDKs, and an MCP server across 16 prediction-market venues. This file > concatenates every page of https://docs.predictefy.com for agent consumption. Canonical docs: https://docs.predictefy.com Start here (short version): https://docs.predictefy.com/llms.txt API reference: https://docs.predictefy.com/api/ (generated from the OpenAPI 3.1 spec; the raw spec file is not published as a standalone download today) Generated from apps/docs/src/content/docs by apps/docs/src/integrations/llms-full.ts. Do not edit by hand. --- # Build on every prediction market > Unified prediction-market intelligence and execution infrastructure — one normalized API and SDK for all prediction-market builders. Source: https://docs.predictefy.com/ import { Card, CardGrid, LinkButton } from '@astrojs/starlight/components'; import GlyphCard from '../../components/GlyphCard.astro'; export const venues = [ ['Polymarket', 'polymarket.svg'], ['Polymarket US', 'polymarket-us.svg'], ['Kalshi', 'kalshi.webp'], ['Limitless', 'limitless.svg'], ['Hyperliquid', 'hyperliquid.svg', 'hyperliquid-ondark.svg'], ['Myriad', 'myriad.svg'], ['Opinion', 'opinion.webp'], ['Predict.fun', 'predict-fun.svg'], ['PRED', 'pred.svg'], ['Pascal', 'pascal.svg'], ['Rain', 'rain.jpg'], ['SX Bet', 'sxbet.svg'], ['XO', 'xo.png'], ['Gemini', 'gemini.svg'], ['PredictStreet', 'predictstreet.png'], ['Novig', 'novig.png'], ];

One normalized API and SDK. Change the venue parameter to reach a different venue.

GET /api/{'{exchange}'}/fetchMarkets

Swap that one segment for any of the 16 product venues:

Quickstart API reference
## Pick your path Three ways in, depending on what you are building. Catalog, prices, and history across venues. 1. [Quickstart](/quickstart/) — key, first request, pagination 2. [Venue coverage](/reference/venues/) — what each venue actually supports 3. [Historical data](/guides/history/) — OHLCV candles and current depth Read with a typed client, then sign and submit your own orders. 1. [Quickstart](/quickstart/) — key, first request, error envelope 2. [TypeScript SDK](/guides/sdk/) — the same surface, typed 3. [Trading & execution](/guides/trading/) — opt-in, client-side signing, venue status The docs are machine-readable; the API is agent-ready. 1. [AI agents hub](/guides/agents/) — choose the chat-client or API route 2. [llms.txt](/llms.txt) — the whole corpus indexed for retrieval 3. [Agent skill](/.well-known/agent-skills/predictefy/skill.md) — a published, versioned skill ## One contract, complete infrastructure One normalized method family across venues: `fetchMarkets`, `fetchMarket`, `fetchEvents`, `fetchSeries`, `fetchOrderBook`, and `fetchTrades`. Swap the `{exchange}` path segment — the response shape stays normalized, while unsupported venue/verb combinations fail honestly. Markets and events carry `asOf` + `provenance`; markets add `capabilities`. Order books carry `asOf` + `provenance` + `sourceMetadata`. Cross-venue OHLCV via `fetchOHLCV` (12 resolutions from 1s through 1d, up to 5,000 candles per call). Every candle is labeled with its `source` and `quality` — you always know whether you are looking at a true venue candle or a point-derived one. Coverage is availability-dependent; no uninterrupted capture or per-venue freshness guarantee is made. See [Historical data](/guides/history/) for the current per-venue depth. Clusters of equivalent markets matched across venues, plus **indicative price discrepancies**, typed market relationships, and executable analysis only when every live-price, depth, fee, status, and resolution gate passes. See [Cross-venue intelligence](/guides/cross-venue/). Venue-scoped trader activity, profiles, leaderboards, versioned scores, and a smart-money feed built from public venue-published or on-chain evidence. Unsupported venue concepts stay explicitly unsupported. See [Trader Intelligence](/reference/trader-intelligence/). Isolated hosted execution and client-side signing for supported venues. The normalized read API never receives a builder's private key, and every venue publishes its exact capability and custody model. See [Trading](/guides/trading/). Requests are endpoint-weighted — catalog reads cost 1 credit, live order-book reads 5, history 5. Sign-up is free: every account starts with 25,000 monthly credits. Trading ships on every plan; arbitrage, price-gap, and bulk endpoints start at Builder. See [Credits & billing](/guides/credits/). ## Every prediction market, one integration Universal market access is the destination. Today, the normalized data contract covers 16 served product venues. The table retains the implemented Smarkets contract as a marked dark venue. The `{exchange}` column is the path segment — swap it to reach a different served venue. | Venue | `{exchange}` | Venue | `{exchange}` | | ------------- | --------------- | -------------- | ----------------- | | Polymarket | `polymarket` | Rain | `rain` | | Polymarket US | `polymarket_us` | PredictFun | `predictfun` | | Kalshi | `kalshi` | SX Bet | `sxbet` | | Smarkets (dark)[^smarkets-dark] | `smarkets` | Pascal | `pascal` | | Opinion | `opinion` | XO Market | `xo` | | Hyperliquid | `hyperliquid` | PRED | `pred` | | Limitless | `limitless` | PredictStreet | `predictstreet` | | Myriad | `myriad` | Novig | `novig` | | Gemini | `gemini` | | | A `router` pseudo-exchange unions the list verbs across all served venues. [^smarkets-dark]: **Dark venue.** Smarkets is implemented but not served on this deployment: every `/api/smarkets/…` request returns `404 EXCHANGE_NOT_AVAILABLE`, router fan-outs exclude it, and it is not counted among the served venues. It returns when a commercial API agreement is in place. Execution state differs per venue and changes independently of this page — `GET /v1/exec/venues` is the live truth. As documented: Pascal, PredictStreet, and XO Market are **armed** isolated execution lanes; XO advertises build + submit + cancel, PredictStreet has build + submit with no cancel, and PRED is **darked**, its whole lane unregistered, so even build answers a 404. All four serve catalog and order books (plus a trades tape on Pascal and PredictStreet), none has a hosted account lane, and Pascal and PredictStreet have proven venue-history coverage. The [venue coverage matrix](/reference/venues/) tells you exactly what each venue supports: real CLOB depth vs. reconstructed books, and which venues expose a public trades tape. ## Built for developers and AI agents Four clients over one contract, plus a REST surface every one of them speaks. Typed client over the normalized contract, with signing kept client-side. [TypeScript guide](/guides/sdk/) The same surface for research and backtesting workflows. [Python guide](/guides/python-sdk/) Thirty-three read, intelligence, and platform tools plus ten guardrailed execution tools, all registered by default. [MCP guide](/guides/mcp/) Live capability-qualified order-book, trade, and reference-feed streams. [Streaming guide](/guides/streaming/) **REST API** — exchange-style verbs, one error envelope, cursor pagination. The [API reference](/api/) documents the contract every client above is built on. **Enterprise SQL** — read-only analytical access over the normalized catalog and relationship model. Requires a dedicated `sql` scope that no self-serve plan grants; available on request. :::note[Universal interface, capability-honest] One normalized contract does not mean every upstream venue exposes every verb. Books from venues without a real order book are flagged `synthetic`; missing public trades or trader identity answer an honest `NOT_SUPPORTED`; execution is exposed only for documented venues; and cross-venue price gaps remain _indicative_ unless every executable gate passes. ::: --- # Quickstart > Get an API key, make your first request, page through results, and read the error envelope. Source: https://docs.predictefy.com/quickstart/ ## API origins Use these canonical production origins directly: | Surface | Origin | | ------------------- | --------------------------------------------- | | REST reads | `https://data.predictefy.com` | | REST execution | `https://exec.predictefy.com` | | WebSocket streaming | `wss://stream.predictefy.com` | REST examples below use the reads origin. Execution operations in the [REST API reference](/api/) declare the isolated execution origin themselves. WebSocket clients append `/v1/stream`; see the [WebSocket API reference](/reference/streaming/). ```sh export PREDICTEFY_API_URL="https://data.predictefy.com" ``` Every request is authenticated and metered against your credit balance. Anonymous requests receive `401 UNAUTHORIZED` (only the health endpoints and docs are open). ## 1. Get an API key Keys are created in the [Predictefy developer dashboard](https://portal.predictefy.com/keys). Sign-up is free during the private beta: every new account starts on the Free plan with 25,000 credits, refilled monthly. Trading with your own funds ships on every plan, Free included. During the beta, every plan gets the Pro-grade feature set; credit balances and plan caps remain enforced. Every plan meters all usage in credits, so upgrade when you need more volume. See [Pricing, credits & billing](/guides/credits/). Portal sign-in uses Privy with email or wallet; wallet-only accounts are asked to link an email during onboarding. 1. Sign up (or sign in) via Privy, with your email address or wallet. 2. Open **API keys** and choose **Create API key**. Signing in never creates a key automatically. 3. Copy the raw `pk_live_…` value when it appears. It is shown **once** and only its hash and display prefix are stored; a lost key must be revoked and re-created. Treat the key like a password: send it only in the `Authorization` header, never in URLs (query strings leak into request logs). ## 2. First request List the three highest-volume active Polymarket markets: ```sh curl -s "$PREDICTEFY_API_URL/api/polymarket/fetchMarkets?limit=3&sort=volume" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` Successful responses are always enveloped as `{ success, data, … }`: ```json { "success": true, "data": [ { "marketId": "…", "title": "…", "outcomes": [{ "outcomeId": "…", "label": "Yes", "price": 0.62 }], "volume24h": 123456.78, "liquidity": 98765.43, "url": "https://…", "asOf": "2026-07-03T09:15:00.000Z", "provenance": { "source": "overlay" }, "capabilities": { "read": true, "trade": false, "depth": true, "history": false } } ], "meta": { "asOf": "2026-07-03T09:15:00.000Z", "provenance": { "source": "overlay" } }, "page": { "limit": 3, "offset": 0, "total": 1519, "hasMore": true, "nextCursor": "…" } } ``` Three honest-data fields ride on every record: - **`asOf`** — when the data was snapshotted (never pretend-fresh). - **`provenance.source`** — where it came from. The full enum is `venue-rest`, `predictefy-live`, `fixture`, `overlay`, `archive`, `predictefy-store`, `glide`, and `lifi`; live catalog reads currently report `overlay` on both the market record and response `meta`. - **`capabilities`** — what this venue actually supports (`read` / `trade` / `depth` / `history`). `trade` is `false` on these reads records; public execution availability is documented separately for each [supported venue](/guides/trading/). The `history` flag is conservative — it flips on per venue as coverage is proven, while the [`fetchOHLCV` endpoint](/guides/history/) is already live. Swap the exchange segment for any of the [16 served venues](/reference/venues/), or use `router` to search across all of them at once: ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchMarkets?query=election&status=active" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` ## 3. Pagination List verbs accept `limit` (max 100 — `fetchArbitrage` is the exception, at max 500), `offset`, `page`, and cursor pagination. Prefer cursors: the response's `nextCursor` freezes the catalog snapshot from page one, so a long walk never skips or double-counts rows that move while you page. ```sh # Page 1 curl -s ".../api/kalshi/fetchMarkets?limit=100" -H "Authorization: Bearer pk_live_…" # Page 2 — pass the previous response's nextCursor curl -s ".../api/kalshi/fetchMarkets?limit=100&cursor=CURSOR_FROM_PAGE_1" \ -H "Authorization: Bearer pk_live_…" ``` Cursors expire after 60 seconds by default; tune that with `snapshotTTL` (milliseconds, `0` = the cursor chain never expires). The final page omits `nextCursor`. `page.total` can be `null`. The data page is the product and the total is only metadata, so a count that overruns its own short budget is abandoned and your page is still served — rather than failing the whole request over a number. `null` means "not counted", never "zero matches", and `meta.totalUnavailable` says so explicitly. Drive your loop with `hasMore` / `nextCursor`, not with `total`: `hasMore` is decided by fetching one row past your `limit`, so it stays correct whether or not the total was computed. ## 4. The error envelope Every error — 4xx or 5xx — uses one shape: ```json { "success": false, "error": { "code": "INSUFFICIENT_CREDITS", "message": "…", "retryable": false, "requestId": "…" } } ``` `code`, `message`, `retryable`, and `requestId` are always present — treat anything else as optional. The most common codes: | HTTP | `code` | Meaning | Retry? | | ---- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------- | | 400 | `VALIDATION_ERROR` | Bad or missing parameters. | no | | 400 | `NOT_SUPPORTED` | Accepted-but-unsupported capability (honest gap). | no | | 401 | `UNAUTHORIZED` | Missing, unknown, or revoked API key. | no | | 402 | `INSUFFICIENT_CREDITS` | Balance below the endpoint weight — see [credits & plans](/guides/credits/). | after balance update | | 403 | `PLAN_REQUIRED` | The account does not include this route or history window. | after access update | | 404 | `EXCHANGE_NOT_AVAILABLE`, `MARKET_NOT_FOUND`, `EVENT_NOT_FOUND`, `OUTCOME_NOT_FOUND` | Unknown or dark venue, or unknown record. | no | | 429 | `RATE_LIMITED` | Per-key or per-plan request-rate window exceeded. | yes (back off) | | 501 | `NOT_SUPPORTED` | Venue has no public feed for this verb (e.g. trades tape). | no | | 503 | `EXCHANGE_NOT_AVAILABLE` | A served venue's own upstream failed. | yes (backoff) | | 503 | `CATALOG_UNAVAILABLE`, `PLATFORM_UNAVAILABLE`, `HISTORY_UNAVAILABLE`, `BILLING_UNAVAILABLE` | Temporary outage — fail-closed, never silently wrong. | yes (backoff) | `EXCHANGE_NOT_AVAILABLE` is the one code that carries two statuses: `404` for a venue this deployment does not serve (dark, unknown, or sandbox), and `503` when a served venue's own upstream fails. Branch on the status, not on the code alone. `API_KEY_LIMIT` applies to creating a key in the developer dashboard when the plan's key cap is reached; reads and execution operations do not emit it. The full code enum per endpoint is in the [API reference](/api/). ## Next steps - [TypeScript SDK](/guides/sdk/) — the same surface as a typed client. - [Trading & execution](/guides/trading/) — explicit opt-in, client-side signing, and venue status. - [Historical data](/guides/history/) — OHLCV candles and what depth exists today. - [Pricing, credits & billing](/guides/credits/) — plans, endpoint weights, and overage. - [API reference](/api/) — the complete endpoint contract. --- # TypeScript SDK > The official TypeScript client for the Predictefy API. Source: https://docs.predictefy.com/guides/sdk/ `@predictefy/sdk` is the official TypeScript client — one typed client across all [16 served venues](/reference/venues/), including PredictStreet, plus the cross-venue `router`, matched clusters, and indicative price discrepancies. The package is ESM with bundled types and supports Node `>=20.19 <21 || >=22.12` (Node 22.12+ recommended). Hosted reads and normalization stay server-side; signing and funding remain client-side. :::note[Beta release] Published on npm as **`1.0.0-beta.5`**. The API surface is stable for the documented verbs; pin an exact version while the beta line moves. ::: ## Install ```sh npm install @predictefy/sdk ``` ## Quickstart ```ts import Predictefy from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY }); const markets = await client.polymarket.fetchMarkets({ limit: 5, query: 'fed' }); console.log( markets.map((m) => m.title), markets.page?.total, ); ``` One normalized method family with familiar exchange-style names; each venue serves only the capabilities it supports: ```ts // Books, the tape, and candles are outcome-keyed: resolve an `outcomeId` from that // venue's own catalog first. A venue-native ticker or symbol is not resolved for you. const [market] = await client.kalshi.fetchMarkets({ limit: 1, status: 'active' }); const outcomeId = market.outcomes[0].outcomeId; await client.kalshi.fetchOrderBook({ outcomeId }); await client.kalshi.fetchOHLCV({ outcomeId, resolution: '1h', limit: 500 }); // `client.exchange(id)` is the dynamic form of the same verbs on any venue. const hyperliquid = client.exchange('hyperliquid'); const [hlMarket] = await hyperliquid.fetchMarkets({ limit: 1, status: 'active' }); await hyperliquid.fetchTrades({ outcomeId: hlMarket.outcomes[0].outcomeId }); await client.router.fetchMarkets({ query: 'election', status: 'active' }); // all venues await client.fetchDiscrepancies({ live: true }); // indicative price discrepancies await client.router.fetchArbitrage({ contracts: 100, executableOnly: true }); ``` :::note[Cross-venue routes are live] `fetchClusters` / `fetchCluster` / `fetchDiscrepancies` call the live [cross-venue endpoints](/guides/cross-venue/). `client.router.fetchArbitrage` is the separate executable assessment surface. If live-book assessment is unavailable, the API returns the standard `PlatformUnavailableError` with code `ARBITRAGE_UNAVAILABLE`. ::: ## Auth Pass `apiKey` (or set the `PREDICTEFY_API_KEY` environment variable). The key is sent as `Authorization: Bearer `, is never logged, and is redacted from every error message. ```ts const client = new Predictefy({ apiKey: 'pk_live_…', // falls back to PREDICTEFY_API_KEY baseUrl: process.env.PREDICTEFY_API_URL, // shown in your developer dashboard retryOn429: true, // default: GETs retried once on 429; POSTs are NEVER auto-retried }); ``` ## Execution Trading uses the separate, isolated execution origin and is always an explicit opt-in: ```ts import { Predictefy, PREDICTEFY_EXEC_BASE_URL } from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, execBaseUrl: PREDICTEFY_EXEC_BASE_URL, }); const openOrders = await client.exec.fetchOpenOrders({ venue: 'hyperliquid' }); console.log(openOrders); ``` Every API key includes the required `trade` scope by default; no separate approval is required. `execBaseUrl` has no implicit default, so the reads API never becomes an execution proxy. See [Trading & execution](/guides/trading/) for the non-custodial signing flow, venue status, idempotency rules, spend caps, and limitations. ## Trader Intelligence Trader identity is capability-qualified by venue. Supported venues expose wallet-attributed tapes, holders, leaderboards, wallet profiles, and cross-venue smart-money signals: ```ts const tape = await client.polymarket.fetchTraderTrades('market-id', { limit: 50 }); const leaders = await client.hyperliquid.fetchLeaderboard({ by: 'score', limit: 50 }); const profile = await client.hyperliquid.fetchWalletProfile('0x...'); const signals = await client.fetchSmartMoney({ venue: 'hyperliquid', limit: 20 }); ``` Unsupported venue/verb combinations fail honestly; the SDK does not fabricate trader identity. Scored feed rows exist only where the venue source is producing activity. ## Errors Credit metering is operation-specific. Hosted reads use their route weights. Isolated execution meters submit, acknowledgement, cancel, and modify; build/precheck and execution reads are not debited, and billing-session routes cost zero. Errors are typed — catch `PredictefyError` (the base class) and switch on the class or `.code`. The server's `code` and `message` are always preserved on the thrown error. | Error class | HTTP | Notes | | -------------------------- | ------- | --------------------------------------------------- | | `UnauthorizedError` | 401 | Missing/unknown/revoked key. | | `InsufficientCreditsError` | 402 | `.topUpHint` provides the next balance step. | | `ValidationError` | 400 | Bad params. | | `NotFoundError` | 404 | Unknown venue/record. | | `NotSupportedError` | 400/501 | Honest capability gap (e.g. no public trades tape). | | `RateLimitedError` | 429 | GETs auto-retried once unless `retryOn429: false`. | | `PlatformUnavailableError` | 503 | Temporary outage — retry with backoff. | | `NetworkError` | — | Transport failure / non-envelope body. | Every account starts with 25,000 free credits, refilled monthly. Handle insufficient credits without retrying the request, then upgrade your plan or [contact support](mailto:support@predictefy.com) if the balance looks wrong: ```ts import { InsufficientCreditsError } from '@predictefy/sdk'; try { await client.polymarket.fetchMarkets(); } catch (err) { if (err instanceof InsufficientCreditsError) { console.error('Insufficient credits. Check the balance shown in your dashboard.'); } } ``` ## Pagination List verbs return the data array with `page` / `meta` / `nextCursor` attached. Follow cursors manually, or let the async iterator do it: ```ts // Manual: cursor pagination freezes the catalog snapshot from page one. let page = await client.kalshi.fetchMarkets({ limit: 100 }); while (page.nextCursor) page = await client.kalshi.fetchMarkets({ limit: 100, cursor: page.nextCursor }); // Iterator: walks every page for you (defaults snapshotTTL: 0 so the cursor // chain never expires mid-walk). for await (const market of client.kalshi.iterateMarkets({ status: 'active' })) { console.log(market.title); } ``` ## Surface `fetchMarkets` · `fetchMarket` · `fetchEvents` · `fetchEvent` · `fetchSeries` · `fetchOHLCV` · `fetchOrderBook` · `fetchOrderBooks` · `fetchTrades` · `getExecutionPrice` · `getExecutionPriceDetailed` (per exchange; `router` serves the list verbs, `fetchEvent`, and the stateless execution-price calculators) — plus `fetchClusters` / `fetchCluster` / `fetchDiscrepancies` (`{ live: true }` for the live overlay), router-only `fetchArbitrage`, venue-scoped Trader Intelligence, cross-venue `fetchSmartMoney` / `fetchTopTraders`, execution/account clients, and `billing.checkout`. Public billing checkout opens with the public plans. `UnifiedMarket` records carry `asOf`, `provenance`, and `capabilities`. Other response families use type-specific contracts: series omit those fields, order books make freshness/provenance optional and omit capabilities, and candles expose `source`, `sourceType`, `quality`, and `isTrueCandle`. Cross-venue price gaps are labeled _indicative price discrepancy_. :::note `getExecutionPrice` is a **stateless VWAP calculator** over an order book you pass in — it estimates what a fill of a given size would average. It does not place, route, or prepare orders. Order lifecycle calls use the separate `client.exec` surface documented in [Trading & execution](/guides/trading/). ::: --- # Python SDK > The official synchronous Python client and CLI for the normalized Predictefy API. Source: https://docs.predictefy.com/guides/python-sdk/ The `predictefy` package is the official synchronous Python 3.10+ REST client. It uses one runtime dependency (`httpx`) and covers normalized venue data, history, most of the `client.router` REST surface, discrepancy qualification, and Trader Intelligence. The router currently omits `fetch_event_matches`; use REST or the TypeScript SDK for that operation. Catalog and order-book reads cover all 16 served venues, including PredictStreet; Trader Intelligence is served on a narrower native-venue set. `pascal`, `xo`, and `pred` are **not** data-only venues — each has an execution lane, in its own state — and each one **does** carry Trader Intelligence: all three serve wallet-attributed trades and appear in the scored-trade/smart-money feed, while holders, leaderboards, and wallet profiles stay unsupported. Actual verbs remain capability-qualified by venue. :::caution[Streaming is TypeScript-only] The Python SDK does not expose WebSocket `watch*` or subscription methods today. Poll `fetch_order_book` / `fetch_order_books` for depth, `fetch_trades` for tape updates, `client.router.compare_market_prices` for cross-venue prices, and `client.router.fetch_arbitrage` for the assessed surface. ::: :::note[Beta release] Published on PyPI as **`1.0.0b3`**. Pin an exact version while the beta line moves. ::: ## Quickstart ```bash pip install predictefy ``` ```python from predictefy import Predictefy client = Predictefy(api_key="pk_...") markets = client.polymarket.fetch_markets({"limit": 5, "query": "fed"}) clusters = client.fetch_clusters({"limit": 20}) smart_money = client.fetch_smart_money({"venue": "hyperliquid", "limit": 20}) ``` Change the venue client without changing the normalized method shape: ```python # Books, the tape, and candles are outcome-keyed: resolve an outcomeId from that # venue's own catalog first. A venue-native ticker or symbol is not resolved for you. markets = client.kalshi.fetch_markets({"limit": 1, "status": "active"}) outcome_id = markets[0]["outcomes"][0]["outcomeId"] client.kalshi.fetch_order_book(outcome_id) client.kalshi.fetch_ohlcv({"outcomeId": outcome_id, "resolution": "1h", "limit": 500}) client.router.fetch_markets({"query": "election", "status": "active"}) # exchange(id) is the dynamic form of the same verbs on any venue. hyperliquid = client.exchange("hyperliquid") hl_markets = hyperliquid.fetch_markets({"limit": 1, "status": "active"}) hyperliquid.fetch_trades(hl_markets[0]["outcomes"][0]["outcomeId"]) ``` List methods return a `PageList`: an ordinary list with `.page`, `.meta`, and `.next_cursor`. `iterate_markets` follows snapshot-safe cursors for you. ## Trader Intelligence Trader support is capability-qualified by venue: ```python trades = client.polymarket.fetch_trader_trades("market-id", {"limit": 50}) holders = client.polymarket.fetch_holders("market-id", {"limit": 50}) leaders = client.hyperliquid.fetch_leaderboard({"by": "score", "limit": 50}) profile = client.hyperliquid.fetch_wallet_profile("0x...") ``` An unsupported upstream capability returns `NOT_SUPPORTED`; the client does not fabricate trader identity or venue data. ## CLI The PyPI package installs a `predictefy` console script: ```bash export PREDICTEFY_API_KEY=pk_... predictefy markets polymarket --limit 10 --q fed predictefy discrepancies --limit 10 --live predictefy clusters --limit 20 ``` Add `--json` for raw JSON. The full verb list is `markets `, `market `, `discrepancies`, `clusters`, and `account [account-id]`. :::caution[The name `predictefy` is shared with the Node CLI] The npm package `@predictefy/cli` installs a binary with the **same name** and a **different command shape** — `predictefy markets search `, not `predictefy markets `. If both are installed, whichever comes first on `PATH` wins. Run the Python one unambiguously as `python -m predictefy.cli …`. ::: Public execution and client-side signing support is capability-qualified separately; do not infer execution support from data coverage. --- # Accounts & funding > Capability-qualified account reads, local owner credentials, and non-custodial funding helpers. Source: https://docs.predictefy.com/guides/accounts/ Account and funding support is capability-qualified per venue and per resource. The capability response distinguishes what a venue exposes from what Predictefy currently serves. :::note[Hosted routes are capability-qualified] Hosted account and funding routes fail explicitly when a resource or destination is unavailable. Predictefy does not substitute invented data for an unsupported resource. ::: ## `client.account` and `client.accounts` The two similarly named SDK surfaces have different jobs: - `client.account` is the unified Account Intelligence facade. Public resources use the hosted API. Supported owner resources route locally through the matching direct client. Local calls require the exact `accountId: 'owner'` sentinel because credentials select the account; arbitrary account labels are rejected. - `client.accounts` is the existing collection of direct venue clients. It remains the lower-level surface for venue-specific account reads and trading. This includes the venue-direct SX Bet trading integration at `client.accounts.sxbet`; it is separate from hosted execution. The credential boundary is per resource. Kalshi RSA credentials, Smarkets (dark) login/session data, Polymarket CLOB L2 credentials for open orders, and the Opinion caller key for open orders stay in your process and go directly to the venue. Polymarket's public address is different: the hosted API receives it as the account id for public balances, positions, and fills. Only Polymarket's owner-authenticated open-order flow keeps its credentials and order data local. Default SX Bet V3 trading is proxy-wallet-only. The caller must provide an SX API key, deploy its deterministic proxy with `client.accounts.sxbet.deployProxyWallet()`, and fund it through `depositToProxy(...)` before placing orders. The retired V2 `TokenTransferProxy` approval and `/orders/approve` flow do not apply. Signing and credentials stay in the caller's process. ```ts import Predictefy from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, venueCredentials: { kalshi: { apiKeyId: process.env.KALSHI_API_KEY_ID!, privateKeyPem: process.env.KALSHI_PRIVATE_KEY!, }, }, }); // Routes directly to Kalshi because owner credentials are configured locally. const balance = await client.account.fetchBalances({ venue: 'kalshi', accountId: 'owner', }); ``` Local owner resources: | Venue | Served locally through `client.account` | Important boundary | | ----------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | Kalshi | balances, positions, open orders, fills | All four resources use caller-held RSA credentials. | | Smarkets (dark — hosted account routes return 404) | balances, open orders, fills | Positions are not implemented because the venue has no position resource. | | Polymarket | open orders | Balances, positions, and fills use hosted public routes; CLOB L2 credentials stay local for open orders. | | Opinion | open orders | SDK-local `/order` uses the caller key; hosted positions and fills use Predictefy's venue key for public wallet reads. | | Gemini | positions, open orders, fills | Balance is unsupported because the Prediction Markets specification exposes no balance endpoint. | | Limitless | positions, fills | Open orders remain per-market on `client.accounts.limitless.fetchOpenOrders(slug)`; balance unsupported. | | Predict.fun | open orders | The caller key and EOA-signed JWT stay local; positions use the hosted public route. | Unsupported owner resources return `NotSupportedError` instead of falling through to a hosted owner route. Predict.fun and other direct clients send credentials only to the venue's own origin. ## Hosted account surface These read-scoped, `live`-metered routes are available: ```text GET /v1/accounts/{venue}/capabilities GET /v1/accounts/{venue}/{accountId} GET /v1/accounts/{venue}/{accountId}/balances GET /v1/accounts/{venue}/{accountId}/positions GET /v1/accounts/{venue}/{accountId}/open-orders GET /v1/accounts/{venue}/{accountId}/fills ``` The combined snapshot keeps separate envelopes for balances, positions, open orders, and fills, so one upstream failure never makes another resource look successful. Each reports `available`, `owner_auth_required`, `not_supported`, or `temporarily_unavailable` with its own data time and provenance. Balances are explicitly non-paginated: `limit` bounds the returned rows, `totalCount` reports the complete pre-slice count, and supplying `cursor` returns `400 VALIDATION_ERROR`. The hosted account surface covers exactly 21 venue/resource combinations across 8 of the 16 served venues. The phase-1 venues (`pascal`, `xo`, `pred`) declare every account resource `not_supported`. PredictStreet declares its four account resources `owner_auth_required` and unserved because its portfolio and order endpoints need the caller's API key. Both are known-venue answers, not an unknown venue: | Venue | Hosted resources | Important boundary | | ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Hyperliquid | balances, positions, open orders, fills | Query the master address; agent-wallet open-order queries return empty. | | Polymarket | balances, positions, fills | Balance is positions value, not spendable CLOB cash; open orders use SDK-local L2 owner auth. | | Limitless | balances, positions | Balance is the caller wallet's Base USDC; owner-auth locked balances stay SDK-local. | | Myriad | balances, positions, fills | Balance is the caller wallet's BSC USDT; open orders have no per-address list. | | SX Bet | balances | Default V3 removed public per-wallet positions, open orders, and fills. Proxy-wallet trading and funding remain client-side only. | | Opinion | balances, positions, fills | Balance is the caller wallet's BSC USDT; the server uses its venue key for wallet reads, while own orders use SDK-local caller auth. | | Predict.fun | positions | Server-keyed positions-by-address is public; account orders remain local owner-auth. | | Rain | balances, positions, open orders, fills | Public or derived Arbitrum evidence is served with explicit chain/derived provenance. | | Kalshi | none hosted; four local owner resources | RSA owner credentials stay in the caller process. | | Smarkets (dark — hosted account routes return 404) | none hosted; three local owner resources | Full login/session stays local; positions are not implemented. | | Polymarket US, Gemini, PredictStreet | none | Resources are owner-required or not supported as documented for each venue. | ```ts // Hosted examples use the live, capability-qualified account surface. const capabilities = await client.account.fetchCapabilities({ venue: 'rain' }); const snapshot = await client.account.fetchSnapshot({ venue: 'rain', accountId: '0x...', }); const positions = await client.account.fetchPositions({ venue: 'rain', accountId: '0x...', limit: 50, }); ``` Unknown venues return `404 VENUE_NOT_AVAILABLE`. A dedicated request for a known but unserved resource returns `400 ACCOUNTS_UNSUPPORTED`. Use the capabilities response before requesting a dedicated list. ## Funding and bridge helpers The adjacent `client.funding` surface combines hosted requirements and provider-dispatched bridge reads with local, unsigned builders: ```ts // Hosted methods dispatch to each venue's evidence-pinned funding provider. await client.funding.getRequirements({ venue: 'limitless' }); const quote = await client.funding.getBridgeQuote({ fromChain: 1, fromToken: '0x...', fromAmount: '1000000', fromAddress: '0x...', toVenue: 'limitless', }); // Review quote.transactionRequest, then sign it in the caller's wallet. ``` The hosted proxy supports `GET /v1/funding/{venue}/requirements`, `POST /v1/funding/{venue}/steps`, `GET /v1/funding/transfer-plan`, `GET /v1/bridge/quote`, `GET /v1/bridge/status`, `POST /v1/bridge/session`, `GET /v1/bridge/session/{sessionId}`, and `POST /v1/bridge/session/{sessionId}/payment`. The transfer plan composes those same caller-signed legs into one ordered cross-venue route; it reads only the funding registry, holds no state, and moves no funds. LI.FI responses are passed through with an unsigned `transactionRequest`; Predictefy does not sign or hold funds. LI.FI status is poll-only and preserves provider substates. LI.FI charges 0.25% plus any configured integrator fee; no completion-time promise is made. `getBridgeQuote` takes either a `toVenue` or an explicit `toChain` + `toToken` pair, never both. The explicit form is not limited to Predictefy venues: it forwards to LI.FI unrestricted, so any LI.FI-supported chain and token is a valid destination — including a chain's native gas token, addressed with the zero-address sentinel `0x0000000000000000000000000000000000000000`. ```ts // Arbitrum USDC to native POL on Polygon. Verified against production on 2026-08-18. const gas = await client.funding.getBridgeQuote({ fromChain: 42161, fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', fromAmount: '10000000', fromAddress: '0x...', toChain: 137, toToken: '0x0000000000000000000000000000000000000000', }); ``` A bridge can leave you holding a token on a chain where you have no gas, and the approval and order that follow need native gas on both chains. Quote native gas to the destination chain first, then bridge the collateral you intend to trade. Cross-VM destinations — a Solana `toChain`, for example — additionally require `toAddress`, the recipient on the destination chain. Without it LI.FI defaults the recipient to the EVM `fromAddress` and rejects the quote. `toAddress` is forwarded untouched and not validated — formats differ per VM. REST-only Polymarket callers can build the same finite transactions as the local SDK: ```http POST /v1/funding/polymarket/steps Content-Type: application/json { "amount": "25.5", "sourceAsset": "usdce", "exchange": "ctf", "recipient": "0x1234567890abcdef1234567890abcdef12345678" } ``` The response orders complete `unsignedTransaction` envelopes as USDC.e approval, pUSD wrap, then pUSD exchange approval. Use `sourceAsset: "pusd"` after a bridge already delivers pUSD; that form returns only the exchange approval. Set `exchange` to `ctf` or `neg_risk`; arbitrary spenders are rejected. The caller reviews, signs, and broadcasts every step locally. Hosted Hyperliquid quotes use Glide's `hypercore:mainnet` destination and raw REST token address `0x2000000000000000000000000000000000000000` for Hypercore USDC, so `toVenue=hyperliquid` requests payment options that settle into the caller's Hypercore spot balance instead of stopping on Arbitrum. Glide reports `totalFeeUSD` per option; its public docs do not publish a fixed Hypercore fee or min/max. The old LI.FI route is historical context only: it had to target `1337` rather than HyperEVM `999`, which does not auto-credit margin. For a Hyperliquid quote, pass the USDC funding amount in six-decimal base units; the hosted lane converts it to Glide's human-readable transfer amount. Today's first-party HIP-4 outcome markets spend from spot USDC directly. After the executable bridge-session flow, call `POST /v1/funding/hyperliquid/steps` with the owner and optional target. It reports spendable `total - hold`, any shortfall, and a typed reason with zero transfer steps. Named builder DEX transfers are future-only and require a non-empty server-configured `destinationDex`; current first-party outcomes expose `destinationDex: null`. SX Network is not on LI.FI. Hosted SX Bet quotes also use Glide. When the selected bridge provider has no route for the requested source chain, token, and amount, `GET /v1/bridge/quote` returns `422 BRIDGE_NO_ROUTE` (`retryable: false`) across both Glide- and LI.FI-backed destinations. Do not retry an unroutable request; change the source chain, token, or amount instead. Provider outages or misconfiguration still return `503 BRIDGE_PROVIDER_UNAVAILABLE` or `502/503 BRIDGE_UPSTREAM`, while unsupported (custody-only or fiat) venues return `400 BRIDGE_UNSUPPORTED`. ```json { "success": false, "error": { "code": "BRIDGE_NO_ROUTE", "message": "No bridge route from chain 1 token 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 amount 1000000 to sxbet; try another source chain, token, or amount.", "retryable": false } } ``` Available quotes still require client-side signing and do not give Predictefy custody. Kalshi, Gemini, Smarkets (dark), and Polymarket US are `fiat_custodied` venue properties and are likewise not generic crypto-bridge destinations. Kalshi can accept crypto inside its KYC flow, but those deposits terminate in venue custody. Local builder rules are deliberately narrow: - `buildApprovalTx` emits finite ERC-20 approvals only and rejects unsafe/max-sized amounts, guarding against the 2024 LI.FI infinite-approval incident class. - `buildDepositTx` supports native Arbitrum USDC into Hyperliquid Bridge2 and rejects amounts below 5 USDC. `buildWithdrawRequest` builds `withdraw3` EIP-712 data, but withdrawal submission remains experimental. - Polymarket trades six-decimal pUSD. `buildCollateralWrapTxs` converts Polygon USDC.e (wrap input only, not trading collateral) to pUSD. Before trading, approve pUSD from the order maker to the V2 CTF Exchange `0xE111180000d2663C0091e4f400237545B87B996B`, or to the V2 NegRisk exchange `0xe2222d279d744050d28e00520010520000310F59` for a NegRisk market. The separate `buildDepositWalletFundingTx` account flow funds an explicitly supplied Polymarket deposit wallet. CREATE2 address derivation is deliberately not implemented. The hosted `/v1/funding/polymarket/steps` builder emits byte-equivalent transactions for REST-only callers. - PredictStreet trades six-decimal USDC.e on ADI Chain 36900 from a caller-specific vault. Resolve that vault through VaultFactory `0xc16B8b190064451c2FeEb2e77c4B2aC4c7009552`, approve USDC.e to the vault, then call `depositERC20`. The execution lane performs only the read-only `vaultOf` lookup; it does not build or submit the approval or deposit. - `buildPermitRequest` is V2-only and throws under default V3, which removed `/orders/approve` and the `TokenTransferProxy` approval. For V3, deploy the proxy with `client.accounts.sxbet.deployProxyWallet()`, then use `buildProxyDepositPermit` or `depositToProxy`. Select `SXBET_API_VERSION=v2` only for a legacy sandbox. - `createPolymarketWithdrawalRequest` and `submitPolymarketWithdrawalRequest` implement the withdrawal-address request step; the caller then transfers funds to the returned matching address. - `buildSxOrbitDepositTxs` is **EXPERIMENTAL**. It requires gas inputs from a current reference deposit; validate every returned field before signing. Every returned transaction or typed-data artifact is unsigned. Validate its chain, token, spender, recipient, amount, and calldata before signing locally. ## How funds enter and leave each venue Everything above describes funds going in. Getting them out is not a Predictefy capability: the API publishes no withdrawal and no bridge-out route, so each venue is a custody island. Funds leave only by that venue's own rails, and moving a balance from one venue to another means withdrawing to your own wallet first, then funding the next venue as a fresh deposit. That is the deliberate no-escrow posture rather than a gap — Predictefy never holds the funds, so it has nothing to send back — and unified cross-venue funding remains roadmap, not a shipped capability. `GET /v1/funding/transfer-plan` returns that withdraw-then-fund sequence pre-composed for the pairs it supports, but planning is all it does: every leg is caller-signed, Predictefy submits none of them, and no balance moves through the API. Which exit applies depends on where the collateral actually sits: | Custody model | How funds enter | How funds leave | | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | **Collateral stays in your wallet** — Limitless, Opinion, Predict.fun, Rain, Myriad, Polymarket EOA form | Hold the venue's collateral in your own EOA and approve the exchange. `GET /v1/bridge/quote` delivers it by `toVenue` for Limitless, Opinion, Predict.fun, Rain, and Polymarket. Myriad has no `toVenue` route (`bridgeProvider: none`), so use the explicit `toChain` + `toToken` form. | Nothing to withdraw. Collateral and outcome tokens never leave your key; revoke the approval to end the exchange's spend rights. | | **Balance sits at the venue** — Hyperliquid, PredictStreet, Polymarket deposit-wallet form, SX Bet | `POST /v1/bridge/session` for Hyperliquid; the vault `depositERC20` for PredictStreet; the venue's own deposit flow otherwise. | The venue's own withdrawal, detailed below. Predictefy builds unsigned artifacts for some of these and submits none of them. | | **No hosted funding path** — Pascal, XO, PRED | Not classified. The funding registry records these as `fundingClass: not_applicable` with a **null collateral asset**, which means Predictefy has verified neither where the collateral sits nor how it gets there. Fund through the venue's own documented route and treat the deposit path as **verify with venue**. | Whatever the venue's own rails are. Predictefy publishes no deposit step, no bridge route, and no exit for these three. | | **Venue-custodied account** — Kalshi, Gemini, Polymarket US | The venue's regulated or KYC'd account rails. `fiat_custodied` venues are not crypto-bridge destinations and reject a `toVenue` bridge quote. | The same venue account rails, entirely outside Predictefy. | Per-venue exits worth knowing before you fund: - **Hyperliquid withdrawals reach Arbitrum and nowhere else.** The venue's `withdraw3` action _is_ Hyperliquid's Arbitrum bridge, so the destination chain is not a choice the caller makes. `buildWithdrawRequest` builds the `withdraw3` EIP-712 data against the Arbitrum domain (`chainId 0xa4b1`) and stops there: it is build-only, and you sign and POST it to Hyperliquid yourself. Reaching any other chain is two legs — withdraw to Arbitrum first, then bridge onward with `GET /v1/bridge/quote`. The $1 flat fee and the venue's dispute window apply to leg one. `GET /v1/funding/transfer-plan` returns all three legs in order for Hyperliquid to Polymarket — the withdrawal, the bridge quote, and the destination approval — with every amount after leg one already net of that fee. - **Polymarket** uses the venue's own two-step withdrawal. `createPolymarketWithdrawalRequest`/`submitPolymarketWithdrawalRequest` ask `bridge.polymarket.com` for a destination-specific address; you then send funds from your Polymarket wallet on Polygon to the address it returns. - **SX Bet publishes no withdrawal endpoint at all.** `withdrawFromProxy()` throws `NOT_SUPPORTED` by design; withdrawals and proxy-to-proxy transfers are Safe-authorised in the sx.bet app, and this lane will not emulate a route the venue does not offer. - **PredictStreet** funds leave through the caller-specific vault on ADI Chain. The execution lane performs only the read-only `vaultOf` lookup and builds neither the deposit nor the exit. --- # Trading & execution > Use Predictefy's isolated, non-custodial execution API with client signing or bounded per-request venue authentication. Source: https://docs.predictefy.com/guides/trading/ :::note[Trading on every plan] Trading ships on every plan, including Free. All API keys include the `trade` scope by default; there is no request-access or separate approval step. Execution is caller-authorized against your own funds. Wallet lanes are client-signed; Myriad adds bounded HMAC relay auth after wallet-signature recovery, while Gemini and Polymarket US use bounded caller-owned request auth. Spend caps and credit metering apply in every case. ::: Predictefy's execution API is isolated from the reads API. SDK users opt in with the exported `PREDICTEFY_EXEC_BASE_URL`; direct HTTP clients can copy the execution origin from the developer dashboard. The service never holds user funds or persists user credentials. It builds an unsigned or authless venue-shaped artifact and validates that stored artifact against venue-specific bounds before relay. Wallet lanes use your signer callback in your own process. PredictStreet verifies that signature against the server-stored digest before reading one caller API key for relay. Myriad likewise recovers its EOA signature before HMAC-authenticating one exact order request with the caller's key and secret. Gemini is the explicit no-wallet-signature exception: it HMAC-authenticates one bounded private REST request with the caller's transient key and secret. Polymarket US creates only the venue's fixed Ed25519 request-auth signature from the caller's transient key id and secret. Novig relays one server-built request with the caller's transient account access token. All five discard credentials and generated headers immediately. Funds remain in the caller's wallet or venue account, and there is no generic server-side wallet or order-signing route. :::caution Trading can lose money. Validate every built artifact before authorization, keep keys out of application logs, and start with small orders. A cross-venue indicative price discrepancy is not evidence that a trade is executable or profitable. ::: ## Integration cost by venue Start with `GET /v1/exec/venues` — the authoritative live list for the current deployment, and the one to re-check immediately before integrating, since runtime arming changes independently of this guide. An implementation below may still be absent, geo-blocked, or fail-closed when its runtime gates are not armed. The static tables explain what a builder must integrate when a capability is listed. Predictefy never stores private signing keys and never signs for the caller. Every wallet, order, or transaction signature is created client-side. **Server-built** means that Predictefy performs the order math and returns a signable or authless EIP-712 artifact, transaction, or binary permit; it does not mean server-side signing. Your wallet or agent key never transits Predictefy. Some venues require API credentials for relay, cancellation, or status reads. Gemini and Myriad HMAC secrets, Polymarket US's Ed25519 secret, and Novig account access tokens create request authentication in-process only after artifact validation. These transient values are immediately discarded and never persisted or logged. Pascal needs no relay credential; its private trading key remains client-side. ### Before funding: prerequisites for every armed hosted venue Table cut 2026-08-13, when PRED was darked; dated corrections follow. :::note[Live arming correction — 2026-08-15] Production `GET /v1/exec/venues` returned exactly 13 armed rows: Gemini, Limitless, Myriad, Opinion, Pascal, Polymarket, Polymarket US, Predict.fun, and Rain advertise build + submit + cancel; Hyperliquid also advertises modify + `approveAgent`; Kalshi advertised build + submit in that snapshot, while the 2026-08-23 official REST source adds cancel and `GET /v1/exec/venues` remains the runtime arming truth; PredictStreet and XO advertise build + submit. PRED remains below only as a dated dark-lane reference and is not part of the armed count. ::: :::note[Novig arming correction — 2026-09-01] The current production snapshot adds Novig as an armed build + submit + cancel lane. Its caller access token remains per-request and is also required for status refresh. Re-check `GET /v1/exec/venues` before integrating because runtime arming remains authoritative. ::: :::note[XO superseded twice since — 2026-08-18] The 2026-08-15 observation above is left as production returned it; XO's row has changed twice since. Its hosted build and submit were **disarmed** earlier on 2026-08-18, once the venue's own API documentation showed the orderbook had migrated to a different order struct and exchange contract than the builder targeted; cancel and client-credentialed status refresh were armed in the same pass. Later that day the builder was **rebuilt** to the current contract and verified against the deployed exchange on-chain, so XO now advertises build + submit + cancel. Nothing has ever been submitted to XO, so the first live submit is the confirmation checkpoint. See the XO trading guide for the detail. ::: Every cell is grounded in lane validation, the funding registry, or the repository's no-escrow record. Where those sources do not establish a prerequisite, the table says **verify with venue**. | Venue and current state | Account or transient credentials needed | Wallet, signer, or smart wallet needed | Geo constraint before funding | Funding path | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Hyperliquid — armed | No caller API credential. Approve a browser/mobile agent with the master wallet first, or sign directly with the master. | EVM master wallet or approved EVM agent signs the phantom L1 EIP-712 action. | **Verify with venue.** The repo establishes no stronger country rule. | Land USDC in Hypercore spot through `POST /v1/bridge/session`. HIP-4 spends spot USDC `total - hold`; the funding-steps route reports readiness and returns no internal-transfer step. | | Kalshi — armed; official REST | Funded Kalshi account. The caller supplies `apiKeyId` + RSA `privateKeyPem` for each submit, cancel, or status refresh; optional `subaccount` on submit must be a non-negative integer. Predictefy uses them in memory for that one request and never stores or logs them. | No order-body or on-chain signer; Predictefy creates RSA-PSS auth for one fixed official V2 REST method and path. | Eligibility is venue-controlled and location-dependent; verify account access before funding. | Fund the regulated Kalshi USD account through venue rails. The venue holds the balance; no Solana USDC, SOL, or Predictefy escrow is involved. | | Limitless — armed, geo-gated | No caller API credential or `profileId`; Predictefy holds the partner HMAC credential. | Base EOA signs the order. | **Verified 2026-08-16:** restricted production egress receives `403 GEO_BLOCKED`, including on status reads. A trusted geo edge and venue eligibility are required. | Hold Base USDC in the EOA and approve the market exchange; LI.FI can fund the wallet. | | Opinion — armed, eligible relay | Hosted build/submit uses Predictefy's builder key; a caller user API key is needed for hosted cancel/status. The direct SDK path keeps that key in-process. | BSC EOA signs; an Opinion Safe may be the type-2 maker controlled by that EOA. | Hosted requests use eligible regional egress and reach the venue. A live credentialed submit is not yet verified on that path; venue jurisdiction rules still apply. | Hold the market-authoritative BSC quote token in the maker EOA/Safe and approve the exact exchange; the registry's current route is BSC USDT through LI.FI. | | Polymarket — armed, eligible relay | The direct SDK path derives or accepts the CLOB key, secret, and passphrase locally. The hosted path accepts the triple per request. | Polygon EOA signs; an EOA, Proxy, or Safe may hold funds according to signature type 0/1/2. | Hosted requests use eligible regional egress and reach the venue. A live credentialed submit is not yet verified on that path; venue jurisdiction rules still apply. | Hold Polygon pUSD and approve the V2 CTF or NegRisk exchange. LI.FI can fund pUSD; the documented steps wrap existing USDC.e. | | Predict.fun — armed, eligible relay | Hosted submit/cancel/status needs the caller session bearer. The direct SDK path keeps the venue API key and wallet key in-process. | BSC EOA only; Safe/proxy identity is rejected. | Hosted requests use eligible regional egress and reach the venue. A live credentialed submit is not yet verified on that path; venue jurisdiction rules still apply. | Hold BSC USDT and approve the market-selected exchange; LI.FI can fund the wallet. | | Rain — armed | No caller venue credential. **Verify with venue** whether account registration is required. | Arbitrum EOA signs and broadcasts the returned transaction. | **Verify with venue.** The repo establishes no stronger country rule. | Hold Arbitrum USDT and approve the operator-armed market Diamond; LI.FI can fund the wallet. | | XO — armed | Caller-owned XO CLOB API key, secret, and passphrase are required at submit. | Chain-3223 XO smart account by default (`signatureType: 3`, `maker == signer`). A direct EOA (`signatureType: 0`) is documented by the venue's order schema but its own auth and smart-account guides call type 3 the only model accepted today; that contradiction is unresolved, so type 0 is an explicit opt-in. | **Verify with venue.** Eligibility remains an operator/compliance responsibility. | Hold six-decimal Bridged USDC (XO) and approve the verified exchange. No hosted helper exists; the deposit route is **verify with venue**. | | Gemini — armed | Funded account, accepted terms, and a Trader key/secret with time-based nonce enabled, heartbeat disabled, and trusted-IP mode Unrestricted. | No order-body signer; Predictefy HMAC-authenticates one bounded request. | **Verify with venue.** The repo establishes no country list. | Fund the Gemini venue account through Gemini rails. The registry pins no chain or collateral token. | | PredictStreet — armed | Caller API key plus an existing caller-specific vault. | ADI Chain EOA signs a VAULT-type order. | **Verify with venue.** Eligibility remains an operator/compliance responsibility. | Approve and deposit ADI-chain USDC.e into the caller vault. No hosted funding helper exists. | | Polymarket US — armed | Identity-verified, funded venue account plus caller UUID key ID and base64 Ed25519 API secret. | No order-body signer; the Ed25519 secret is request auth, not a Solana wallet key. | **Verify with venue** for account and geographic eligibility. | Fund the venue-custodied account through regulated in-venue rails; no crypto bridge helper exists. | | Pascal — armed | Eligible Pascal account/custody wallet; use the registered wallet key or a revocable delegated trading key. No relay credential. | Solana-format Ed25519 signer signs the exact permit bytes. | Terms restrict Australia, Belgium, France, Germany, Italy, the Netherlands, Ontario, Poland, Quebec, Russia, Singapore, Spain, Taiwan, Thailand, the UK, the US, and comprehensively sanctioned jurisdictions including Iran, Syria, Cuba, North Korea, Crimea, Donetsk, and Luhansk. | Use the existing Pascal custody wallet. The collateral asset and deposit route are **verify with venue**; no hosted helper exists. | | PRED — **darked 2026-08-13; reference only** | The venue confirmed that Predictefy platform-key submissions for caller-owned Safes are refused. The source-ready shape accepts a caller API key and access/refresh JWTs only for an independently arranged future path. | Caller-owned Base Safe is maker; a distinct controlling EOA signs type 2. | The venue restricts the US, UK, France, Ontario, Singapore, Poland, Thailand, and Taiwan and prohibits location masking. | Do not fund for hosted submit. The collateral asset and Safe funding/enablement route are unverified and no hosted helper exists. | | Myriad — armed | Wallet-bound Myriad API key and secret plus the connected EOA; bare credentials are unsupported. | BSC EOA signs type 0; Safe/proxy identity is unsupported. | **Verify with venue.** The repo establishes no stronger country rule. | Hold manager-selected 18-decimal BSC USD1 or USDT and approve collateral or outcome shares to the exchange. No hosted helper exists. | | Novig — armed | Caller-owned Novig account access token for submit, cancel, and refresh. Predictefy's platform OAuth credentials are used only for build-time venue reads. | No wallet or order-body signature. Predictefy stores an exact authless REST artifact. | Production venue endpoints require US egress. Account and jurisdiction eligibility remain venue-controlled. | Fund the venue-custodied USD account through Novig's regulated banking rails; no crypto bridge helper exists. | Every row above covers getting funds **in**. Getting them out is always the venue's own rail: Predictefy publishes no withdrawal and no bridge-out endpoint, so a balance at one venue cannot be moved to another through this platform. That is the no-escrow custody posture — the platform never holds the funds — and unified cross-venue funding is roadmap, not a shipped capability. See [Accounts & funding](/guides/accounts/) for each venue's exit path, including Hyperliquid's Arbitrum-only two-leg withdrawal. Polymarket, Opinion, and Predict.fun order-build responses include a top-level `warnings` advisory before funding or signing. It says hosted requests use eligible regional egress and reach the venue, while a live credentialed submit on that path remains unverified and venue jurisdiction rules still apply. PRED's execution lane is darked (2026-08-13) because the venue confirmed that platform-key submissions for caller-owned Safes are refused. Its source-ready build/sign contract remains documented, but there is no supported hosted credential path; only callers with their own independently arranged PRED access can use that source lane. ### Build families | Family | Venues | What the builder adds | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Server-built order | Hyperliquid, Limitless, Polymarket (V2), Opinion, Predict.fun, Myriad, XO, PRED (source-ready, darked), PredictStreet, Pascal | Send plain order parameters and the signing owner. Predictefy fixes the remaining order fields and returns the signable EIP-712 artifact or Pascal binary permit. | | Server-built transit-auth order | Gemini, Novig | Send plain catalog-bound order parameters. Predictefy stores the exact authless venue body; submit authenticates it once with the caller's transient HMAC credentials or account access token. | | Client-echoed venue SDK order | Polymarket (V2 EOA echo), Opinion and Predict.fun (legacy) | Existing integrations may send the venue's exact order, EIP-712 domain, and `structHash` as `buildResult`. Polymarket requires the V2 order contract; Opinion and Predict.fun retain their legacy venue-specific shapes. | | Raw/structured chain intent | Rain | Rain returns an unsigned chain transaction for client signing. | | Stateless venue REST | Kalshi, Polymarket US | Send normalized catalog ids, side, contract quantity, and price. Predictefy stores an exact authless venue body; submit/refresh accepts caller credentials only for that request, and Kalshi cancel does the same. | ### Hosted order lanes | Venue and current state | What you add before or during build | What you sign and where | Credentials you must hold | Funding model and external prerequisites | | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | **Hyperliquid — armed: build + submit + cancel + modify + `approveAgent`** | `asset`, or `outcome` + `outcomeSide`, plus `isBuy`, `price`, `size`, and `owner`. | L1 order/cancel/modify action under the phantom EIP-712 `Exchange` v1 domain. | Master wallet and optional approved agent key stay client-side; no venue API key. | Spend Hypercore spot USDC `total - hold`; the bridge session can deliver it and the funding-steps route reports readiness only. | | **Limitless — armed; geo-gated** | `owner`, `tokenId`, side/type, `marketSlug`, price, shares, and any FOK budget; `profileId` is rejected. | EIP-712 CTF order on Base `8453`. | Base EOA key stays local; Predictefy holds the partner HMAC credential. | Base USDC plus exchange approval. **Verified 2026-08-16:** restricted egress gets `403 GEO_BLOCKED`, including on status reads. | | **Polymarket — armed; eligible relay** | Plain V2 intent or a V2 EOA `buildResult`. | V2 EIP-712 order on Polygon `137`; caller-direct SDK remains available. | Direct SDK keeps the EOA and CLOB triple local; hosted submit accepts the triple transiently through eligible regional egress. | Polygon pUSD plus V2 CTF/NegRisk approval; all traffic remains subject to venue eligibility. | | **Polymarket US — armed since 2026-08-15** | Catalog `marketId` + `outcomeId`, side, `type: "limit"`, amount, and price. | No order-body signature; Predictefy creates bounded Ed25519 request auth. | Caller UUID key ID and base64 Ed25519 secret transit only for the authenticated call. | Funded, identity-verified venue account through in-venue rails; no crypto bridge helper. | | **XO — build + submit + cancel; rebuilt and re-armed 2026-08-18** | Outcome selector, `isBuy`, `price`, `size`, the `owner` account, and optional `expiresAt`. | 13-field EIP-712 CTF order on chain `3223`; `signatureType` defaults to `3` (XO smart account, ERC-1271). | Signing key stays local; caller XO CLOB key/secret/passphrase transit submit only. | Six-decimal Bridged USDC (XO) plus exchange approval; deposit route unverified and no hosted helper. | | **PRED — darked 2026-08-13; source-ready reference only** | Outcome selector, `isBuy`, price, size, Safe `owner`, EOA `signer`, and optional `expiresAt`. | Type-2 EIP-712 order on Base `8453`. | Platform-key submits for caller Safes are venue-refused. Only an independently arranged caller API key/JWT set could use the source lane. | Do not fund for hosted submit. Collateral and Safe funding/enablement route remain unverified. | | **PredictStreet — armed since 2026-08-15; no cancel flag** | Outcome selector, `isBuy`, price, size, EOA `owner`, and optional expiry/post-only fields. | VAULT EIP-712 order on ADI Chain `36900`. | EOA key stays local; caller API key transits submit only after signature recovery. | Deposit USDC.e into the caller vault; no hosted funding helper. | | **Pascal — armed 2026-08-12; fleet-verified 2026-08-15** | Catalog market, side, direction, price, integer size, custody `owner`, permit `signer`, and optional timing fields. | Exact Ed25519 place/cancel permit bytes. | Wallet or delegated trading key stays local; no relay credential. | Existing Pascal custody wallet; collateral asset/deposit route unverified and no hosted helper. | | **Opinion — armed; eligible relay** | Plain outcome intent; type `0` EOA or type `2` Opinion Safe maker. | BSC `56` EIP-712 order signed by the controlling EOA. | Direct SDK keeps the user API key local; hosted cancel/status accepts it transiently. | Market-authoritative BSC quote token plus exchange approval; hosted requests use eligible regional egress. | | **Predict.fun — armed; eligible relay** | Plain outcome intent; EOA type `0` only. | BSC `56` EIP-712 order. | Direct SDK keeps API key/wallet key local; hosted submit/cancel/status needs a transient session bearer. | BSC USDT plus market-selected exchange approval; hosted requests use eligible regional egress. | | **Kalshi — armed: official REST build + submit + cancel** | Catalog outcome selector, `isBuy`, cent-exact probability price, positive integer size, and optional `good-til-cancel` time in force. | No order-body or on-chain signature; Predictefy RSA-PSS-authenticates only the exact stored official V2 REST request. | Caller `apiKeyId` + RSA `privateKeyPem` transit only for submit, cancel, or refresh and are discarded; optional non-negative `subaccount` is submit-only. | Fund the regulated Kalshi USD account through venue rails. `client.accounts.kalshi` remains the venue-custodied native REST path. | | **Gemini — armed since 2026-08-15** | Catalog selector, direction, price, size, and optional TIF/maker-only fields. | No order-body signature; Predictefy HMAC-SHA384 authenticates the stored request. | Caller Trader key/secret with time nonce, no heartbeat, and Unrestricted trusted-IP mode. | Funded Gemini account and accepted prediction-market terms; no bridge helper. | | **Novig — armed since 2026-08-22: build + submit + cancel** | Catalog `marketId` + composite `outcomeId`, `type: "limit"`, positive `amount` in contracts, tick-aligned `price`, and optional CASH/TIF fields. | No order-body signature; Predictefy stores the exact authless REST request. | Caller account access token transits submit, cancel, and refresh only; platform OAuth credentials serve build-time reads. | Funded venue-custodied USD account; production endpoints require US egress. | | **Myriad — armed since 2026-08-15** | Model-qualified Order Book outcome, direction, tick-aligned price, size, and EOA owner. | BSC `56` `MyriadCTFExchange` EIP-712 order. | EOA key stays local; wallet-bound HMAC key/secret transit submit/cancel only after recovery. | Manager-selected 18-decimal BSC USD1 or USDT plus required approval; no hosted helper. | | **Rain — armed since 2026-08-15** | Catalog-bound approval, limit/market order, or cancel parameters. | Raw Arbitrum `42161` transaction; caller signs and broadcasts. | No caller venue credential; EOA key/RPC stay local. | Arbitrum USDT plus per-market Diamond approval; hosted submit records `signed` and never claims broadcast. | ### Venues without a hosted trade lane | Venue group | Execution available today | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Smarkets | **Dark venue** — No hosted execution lane, **by owner decision on 2026-08-18** rather than as a build gap. A separate venue-direct client-side SDK trading integration is live at `client.accounts.smarkets` (`createOrder` and `cancelOrder`); the account email and password never transit Predictefy. See [Smarkets](/guides/trading/smarkets/). | | SX Bet | No hosted execution lane. A separate venue-direct client-side SDK trading integration is live at `client.accounts.sxbet`; signing keys and venue credentials remain in the caller's process and requests go directly to the venue. See [SX Bet](/guides/trading/sxbet/). | ## Authentication Send a Predictefy API key as a Bearer token. Every API key includes the `trade` scope by default. A key without that scope receives `403 SCOPE_MISSING` by design. ```http Authorization: Bearer pk_live_YOUR_TRADE_SCOPED_KEY ``` The execution origin is an explicit opt-in in the TypeScript SDK: ```ts import { Predictefy, PREDICTEFY_EXEC_BASE_URL } from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, execBaseUrl: PREDICTEFY_EXEC_BASE_URL, }); const openOrders = await client.exec.fetchOpenOrders({ venue: 'hyperliquid' }); console.log(openOrders); ``` `execBaseUrl` has no implicit default. The reads API does not proxy execution. ## Exchange-style surface | Verb | HTTP route or SDK behavior | | -------------------- | ------------------------------------------------------------------------------------------- | | `buildOrder` | `POST /v1/exec/:venue/orders/build`; add `dryRun: true` for a non-persisted preview | | `submitOrder` | `POST /v1/exec/:venue/orders/submit` | | `createOrder` | SDK composition: `buildOrder` → client signer callback → `submitOrder` | | `cancelOrder` | `POST /v1/exec/:venue/orders/:executionId/cancel` — direct relay or sign → submit, by venue | | `modifyOrder` | Hyperliquid: `POST /v1/exec/:venue/orders/:executionId/modify` → sign → `submitOrder` | | `fetchOrder` | `GET /v1/exec/:venue/orders/:executionId` | | `refreshOrderStatus` | `POST /v1/exec/:venue/orders/:executionId/refresh` — client-authenticated status read | | `fetchOpenOrders` | `GET /v1/exec/:venue/orders?status=open` | | `fetchClosedOrders` | `GET /v1/exec/:venue/orders?status=closed` | | `fetchAllOrders` | `GET /v1/exec/:venue/orders?status=all` | | `fetchMyTrades` | `GET /v1/exec/:venue/trades` | | `fetchPositions` | `GET /v1/exec/:venue/positions` | | `fetchBalance` | `GET /v1/exec/:venue/balance` — honest `501 NOT_SUPPORTED`; see limitations below | Every order-building, submission, cancellation, and modification POST requires an `Idempotency-Key` header. The SDK always sends one and generates a UUID when `idempotencyKey` is omitted. Direct HTTP clients should persist a stable key with the operation they may retry. `refreshOrderStatus` is the read-only exception and does not require that header. A dry-run build still requires the header for the route contract, but the service does not look it up, replay it, or bind it. The same key can therefore be used later for the persisted build. To preview an order, add `"dryRun": true` to any venue build body. The service still performs trade-scope authentication, catalog market resolution, the venue's complete artifact construction, and fail-closed artifact bounds checks. It returns `dryRun: true`, `venue`, `intent`, `unsigned`, `notionalUsd`, and `feeEstimate`, but no `executionId` or status. It inserts no `execution.executions` row and reserves no spend capacity; only a later ordinary build creates intent state. ## Precheck market executability A catalog-active market is not necessarily executable. For each exact order: 1. Discover the market through the catalog and confirm that `GET /v1/exec/venues` currently lists its venue with `build: true`. 2. Send the intended build with `dryRun: true`, or call `client.exec.precheckOrder(params)`. This costs no execution credits, persists nothing, and returns either the existing `ExecDryRunBuildResult` or a typed refusal. 3. Only after a pass, send a normal persisted build, sign the returned artifact locally, and submit it. A dry-run preview has no `executionId` and cannot be submitted itself. ```ts const catalogOutcomeId = '...'; // From a preceding PredictStreet catalog read. const order = { venue: 'predictstreet', asset: catalogOutcomeId, isBuy: true, price: 0.42, size: 10, owner: '0x...', }; const precheck = await client.exec.precheckOrder(order); if (!precheck.ok) { console.log(precheck.refusal.code, precheck.refusal.retryable); return; } // Build again to create the persisted execution that can be signed and submitted. const built = await client.exec.buildOrder(order); ``` `precheckOrder` returns server-authored API refusals instead of throwing them. A transport or invalid-response failure still throws because the server made no executability decision. `retryable` means that retrying the same request can reasonably succeed; a non-retryable refusal may still become passable after changing the order, account, venue state, or deployment. | Code | Meaning during an order dry run | Retryable | Venues that can emit it | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ARTIFACT_BOUNDS_VIOLATION` | The built artifact escaped the lane's registered contract, chain, domain, action, or custom safety bounds. | No | Every armed hosted lane | | `ARTIFACT_INTEGRITY_MISMATCH` | A client-echoed `buildResult` has a `structHash` that does not bind to its order. | No | Opinion, Polymarket, Predict.fun echoed-build paths | | `EXEC_UNAVAILABLE` | API-key verification or required catalog/live execution truth is unavailable or disagrees, so execution fails closed. | Yes | Every lane for API-key verification failure; also Gemini, Myriad, Opinion, Pascal, Polymarket, Polymarket US, PRED, Predict.fun, PredictStreet, and XO for lane checks | | `GEO_BLOCKED` | The trusted caller-country signal is absent or restricted for a geofenced lane. | No | Limitless today | | `IDEMPOTENCY_KEY_REQUIRED` | A direct HTTP build omitted `Idempotency-Key`; the SDK always supplies one. | No | Every hosted lane | | `INTERNAL` | An unexpected, non-authored server failure occurred; quote the request id to support. | Yes | Every hosted lane | | `MARKET_NOT_FOUND` | The lane's market/outcome selector did not resolve to an item in the venue catalog. | No | Every registered order lane | | `NOT_IMPLEMENTED` | The venue is registered only for settlement, so its order-building stub refuses honestly. | No | Rain, Myriad, Polymarket, or Predict.fun when that deployment has only the settlement lane armed | | `NOT_SUPPORTED` | The catalog item belongs to a deliberately unsupported execution model. | No | Myriad AMM or non-BNB Order Book ids | | `RATE_LIMITED` | The authenticated execution request exhausted its token bucket. | Yes | Every hosted lane | | `RAIN_INVALID_AMOUNT` | Rain amount, shares, notional, or quoted proceeds violate the verified source guards. | No | Rain | | `RAIN_INVALID_DEADLINE` | Rain's caller-selected deadline offset is outside the accepted range. | No | Rain | | `RAIN_INVALID_OPTION` | The Rain option is malformed, zero, or outside the inspected pool's option range. | No | Rain | | `RAIN_INVALID_PRICE` | The Rain price is malformed, outside `0.01..0.99`, or not on the exact source tick. | No | Rain | | `RAIN_INVALID_SIDE` | Rain's option side is not `YES`/`1` or `NO`/`2`. | No | Rain | | `RAIN_ORDER_LIMIT_REACHED` | The owner already has the source-capped number of active Rain orders for that option and direction. | No | Rain | | `RAIN_PHASE_NOT_OPEN` | The inspected Rain pool phase does not permit the requested order action. | No | Rain | | `RAIN_POOL_SAFETY_CHECK_FAILED` | Rain pool provenance, deployer, facet routing, base token, or catalog binding failed closed. | No | Rain | | `RAIN_SLIPPAGE_REQUIRED` | A protected Rain market order lacks valid nonzero slippage protection or cannot derive a nonzero floor. | No | Rain | | `SCOPE_MISSING` | The API key is valid but lacks the required `trade` scope. | No | Every hosted lane | | `UNAUTHORIZED` | The Predictefy API key is missing, unknown, or revoked. | No | Every hosted lane | | `VALIDATION_ERROR` | The shared build body or venue-specific order intent is malformed or internally inconsistent. | No | Every hosted lane | | `VENUE_NOT_SUPPORTED` | No execution lane is armed for the requested venue in this deployment. | No | Any unknown, client-side-only, or currently unarmed venue | | `VENUE_RELAY_FAILED` | A required build-time venue API, RPC, quote, or metadata read failed or returned unusable truth. | Yes | Kalshi, Myriad, Opinion, Pascal, Polymarket, Predict.fun, PredictStreet, Rain, and XO | A passing dry run proves only that the builder accepts this exact market and order **now**. It does not guarantee that a later persisted build still passes, that submit credentials or funding are valid, that the venue accepts the relay, or that the order fills. ## Caller-authorized execution flow 1. Run the per-market dry-run precheck above and handle any typed refusal. 2. Call `buildOrder` with the same order, a trade-scoped Predictefy key, and an idempotency key. 3. Inspect the returned unsigned/authless artifact in your process. 4. For a client-signing lane, sign it with your wallet or venue-specific signer callback. For Gemini or Polymarket US, keep the artifact unchanged and provide only that lane's transient caller credential at submit. 5. Call `submitOrder` with the signed artifact, transient authorization, or client-broadcast digest. The service revalidates the stored artifact, owner binding, bounds, and spend caps before reading transient credentials, relaying, or recording a digest. 6. Poll `fetchOrder`. For a user-authenticated venue status read, call `refreshOrderStatus` first, then use the order and trade list verbs as needed. Polymarket's working SDK path intentionally diverges after build: it signs locally, then calls the CLOB directly for submit, status, and cancel. It does not call Predictefy's hosted submit or refresh routes. See [the Polymarket trading guide](/guides/trading/polymarket/). `client.exec.createOrder(params, signer)` composes steps 2–5 for client-signing lanes; it does not run the precheck automatically. The signer receives only the unsigned artifact and returns venue-shaped signed fields. Private signing keys remain client-side, and Pascal's trading private key never transits Predictefy. Gemini's API key/secret, Myriad's HMAC key/secret, Novig's account access token, PredictStreet's API key, legacy hosted Polymarket and XO L2 CLOB credentials, Predict.fun session bearer tokens, Opinion API keys, and Polymarket US Ed25519 credentials transit only when that venue needs them. PRED's source-ready partner/JWT shape would do the same only under a caller-owned arrangement; the hosted PRED lane is darked. They are not persisted or logged. The venue-direct Polymarket SDK path is the exception: its CLOB credentials never transit Predictefy and are sent only from the caller process to the venue. ## Build request schema by venue `POST /v1/exec/{venue}/orders/build` takes one JSON body whose **shape is venue-specific**. The venues do not agree on a single order struct. Each lane returns exactly what that venue signs or authenticates. Five families: | Family | Venues | You send | Price unit | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------- | | Server-built order | Hyperliquid, Limitless, Polymarket (V2), Opinion, Predict.fun, Myriad, XO, PRED (source-ready, darked), PredictStreet, Pascal | plain order parameters; the server fixes every remaining field | probability number | | Server-built transit auth | Gemini, Novig | catalog selector plus plain order parameters; credentials arrive only at submit | probability number | | Client-echoed venue SDK result | Polymarket (V2 EOA echo), Opinion and Predict.fun (legacy) | the venue SDK's exact `buildResult` (order + domain + `structHash`); Polymarket V1 echoes are rejected | atomic `uint256` decimal strings | | Raw/structured chain intent | Rain | chain parameters | atomic integer strings | | Stateless venue REST | Kalshi, Polymarket US | catalog ids plus normalized side, contract amount, and price; the server returns an authless body | probability number | No venue on this route accepts a price in cents. Common to every build call: - `Authorization: Bearer ` and `Idempotency-Key: `. A missing header is `400 IDEMPOTENCY_KEY_REQUIRED`. - Optional `intent`: `"order"` (default) or `"redeem"`. Anything else is `400 VALIDATION_ERROR — intent must be 'order' or 'redeem'`; a venue with no redeem lane answers `501 NOT_IMPLEMENTED`. - Optional `dryRun`: `true` returns the fully resolved and bounds-checked preview without a ledger row. Omit it or send `false` for the existing persisted-build behavior. - A venue with no lane in this deployment answers `404 VENUE_NOT_SUPPORTED`. `GET /v1/exec/venues` is the authoritative live list: every `build`, `submit`, `cancel`, `modify`, and `redeem` flag there is derived from the same lane registry and artifact bounds the lifecycle routes consult, so a venue listed without `build` genuinely cannot build. - Repeating a build with the same `Idempotency-Key` returns the original stored execution with an `Idempotency-Replay: true` response header. It is not rebuilt and spend caps are not re-checked. - Build itself is **not metered**. Credits are charged on submit, cancel, and modify. - Some lanes carry a top-level `warnings` advisory on order builds, including idempotent replays. Polymarket, Opinion, and Predict.fun disclose their eligible regional relay, verified venue reachability, still-unverified live credentialed submit, and the venue's continuing jurisdiction rules. Rain warns when a pool cannot prove its provenance against the source-verified official deployer; Hyperliquid warns when spendable spot USDC is below the order notional. None of these blocks build or client signing for a caller with their own submit path. PRED declares a warning of the same class about platform-key submits, but **no caller can currently receive it**: the PRED lane is darked and unregistered, so its build route answers `404 VENUE_NOT_SUPPORTED` before any warning is composed. That advisory applies only if the lane is re-armed. - The response carries the unsigned artifact, the server's own `notionalUsd` (what the spend caps are checked against — never a caller-declared USD figure), and a fee estimate where the venue has a verified fee model. Rain has none, so its estimate is `null` with a `feeWarning` in the payload. Novig accepts only `marketId`, the catalog composite `outcomeId`, `type: "limit"`, a positive `amount` in contracts, and a live tick-aligned `price`. `currency` may be omitted or set to `"CASH"`. `tif` defaults to `"GTC"` and may be `"GTC"`, `"GTT"`, `"IOC"`, or `"FOK"`; `ttl` is a positive whole number of milliseconds required only for `"GTT"`. Any other build field is rejected. The server converts contracts to the venue's minimum currency units and stores an authless artifact. Each lane below has its own page. The families above tell you which one you are in. - [Hyperliquid](/guides/trading/hyperliquid/) - [Limitless](/guides/trading/limitless/) - [Kalshi (Official REST API)](/guides/trading/kalshi/) - [Polymarket](/guides/trading/polymarket/) - [Polymarket US](/guides/trading/polymarket-us/) - [Opinion](/guides/trading/opinion/) - [Predict.fun](/guides/trading/predict-fun/) - [Pascal](/guides/trading/pascal/) - [XO](/guides/trading/xo/) - [PredictStreet](/guides/trading/predictstreet/) - [Gemini](/guides/trading/gemini/) - [Novig](/guides/trading/novig/) - [Myriad](/guides/trading/myriad/) - [PRED](/guides/trading/pred/) - [Smarkets (dark)](/guides/trading/smarkets/) - [SX Bet (client-side lane)](/guides/trading/sxbet/) - [Echoed build results: Polymarket V2, Opinion, and Predict.fun](/guides/trading/echoed-build-results/) - [Rain](/guides/trading/rain/) - [Settlement claims (intent: "redeem")](/guides/trading/settlement-claims/) ## Submitting the authorized artifact `POST /v1/exec/{venue}/orders/submit` relays what you signed. The body always carries: ```json { "executionId": "", "...": "venue-specific signed fields" } ``` - `executionId` is required and binds the submission to one stored, already cap-checked execution. Omitting it is `400 VALIDATION_ERROR — executionId (uuid) is required`; an id that is malformed or simply not yours answers the same clean `404 EXECUTION_NOT_FOUND` as the sibling routes. No retry can make a non-existent execution exist, so it is never a retryable server error. - The `Idempotency-Key` header is required, and **one caller key binds at most one execution** per account, venue, and action. Reusing a key against a different execution is `409 IDEMPOTENCY_CONFLICT`. An execution that has already left `built` status replays its current state with `Idempotency-Replay: true` — no second relay, no second charge. - The service re-checks the stored notional against the per-order cap and re-sums the key's actual submitted spend for the day before the relay, then reserves the submission. A rejection before the venue was ever contacted frees that reservation; an ambiguous relay failure (`502 VENUE_RELAY_FAILED`) keeps it, so a resubmit can never bypass the cap. Wallet signatures are produced entirely in your process. Predictefy retains no user signing key, and there is no generic signing route to call. Kalshi, like Gemini and Polymarket US, is request-authenticated instead. What each venue expects back: | Venue | Signed fields you post | Signing scheme | | ------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Gemini | `{ apiKey, apiSecret }` | No client order signature. Predictefy HMAC-SHA384 signs one canonical stored body with an epoch-second nonce, then discards both credentials and headers. | | Novig | `{ callerAccessToken }` | No client order signature. Predictefy relays the exact stored authless request with the caller's account token, then discards the token. | | Polymarket US | `{ keyId, secretKey }` | No client order signature. Predictefy creates Ed25519 request auth for the exact stored REST request, then discards both credentials and headers. | | Hyperliquid | `{ action, nonce, signature, owner }` | EIP-712 phantom-agent over the L1 action. `signature` is `{r,s,v}` or `0x` hex. | | Polymarket | `{ signature, owner, apiKey, apiSecret, apiPassphrase }` | EOA ECDSA over the server-stored `structHash` (raw digest — a `personal_sign` wrapper is correctly rejected). | | XO | `{ signature, owner, apiKey, apiSecret, apiPassphrase }` | 65-byte ECDSA over the server-stored 13-field digest. Signature type 3 (XO smart account) is the default and is validated by ERC-1271 at the venue, so the server binds `owner` to the stored maker rather than recovering it; type 0 (direct EOA) must recover to the stored signer. | | PRED (darked) | `{ signature, owner, apiKey, accessToken, refreshToken }` | Source-ready only: EOA ECDSA over the server-stored Base digest for the Safe maker; signature type 2 is fixed. | | Myriad | `{ signature, owner, apiKey, apiSecret }` | EOA ECDSA over the server-stored BSC Order Book digest; only after recovery does Predictefy HMAC-SHA256 authenticate one exact venue request. | | PredictStreet | `{ signature, owner, apiKey }` | EOA ECDSA over the server-stored VAULT digest; the API key is read only after recovery and is then discarded. | | Pascal | `{ signature, owner }` | Base58 Ed25519 signature over the exact stored permit bytes. The delegated `signer` is already bound inside the permit. | | Opinion | `{ signature, owner }`, plus `safeAddress` for Safe mode | Plain EOA ECDSA EIP-712 over the stored `structHash`, even in Safe mode (not EIP-1271). The EOA owner signs; the Safe remains maker. | | Predict.fun | `{ signature, owner, authToken }` | EOA ECDSA over the stored `structHash`. | | Limitless | `{ signature, owner }` | EIP-712 over the server-built order. You post only the signature; the server attaches it to the stored order. | | Kalshi | `{ apiKeyId, privateKeyPem, subaccount? }` | No client order signature. Predictefy creates RSA-PSS request auth for the exact stored official V2 request, then discards the credentials and headers. | | Rain | `{ signedTransaction, owner }` (`0x` hex raw tx) | Signed Arbitrum (chain 42161) transaction; you broadcast it yourself. | Novig cancel builds derive a bodyless `DELETE` from the stored venue order id. Submit the cancel with `{ callerAccessToken }`. A successful relay acknowledges an asynchronous cancellation; call refresh with the same per-request token until venue status confirms that the order left the book. Three rules govern the client-signing lanes: 1. **Identity binding.** The wallet declared at build is stored with the intent. Most lanes require recovered signer = stored owner = submitted owner. Pascal instead verifies the stored delegated `signer` while separately requiring submitted owner = stored custody owner. PRED binds submitted owner to the stored Safe maker and recovery to its separately stored EOA signer. Cancel and modify inherit stored identity; a caller-supplied replacement is never trusted. 2. **No bait-and-switch.** What you submit must be what was built. Hyperliquid re-hashes your posted action and compares it to the stored `connectionId` (`submitted action does not match the built execution`). Rain's and Predict.fun's on-chain cancel paths byte-compare the signed transaction against the stored one. Limitless is structural — you cannot substitute an order you never send. 3. **Verify before relay.** The signature is verified locally first; a tampered ECDSA or Ed25519 signature is rejected before anything reaches the venue. Gemini applies the same no-bait-and-switch principle without a wallet signature: the stored versioned body, allowlisted request path, exact shape, and notional are all revalidated before the code reads `apiKey` or `apiSecret`. Only that canonical body is included in the HMAC payload. Kalshi likewise revalidates the stored official REST order or cancel shape before reading `apiKeyId` or `privateKeyPem`. A legacy Solana-shaped artifact refuses with `409 ARTIFACT_VERSION_CONFLICT`; valid credentials and generated auth headers exist only in memory for the one bounded request. :::caution[Hyperliquid: key order is load-bearing] Hyperliquid hashes the msgpack encoding of the action, and msgpack preserves insertion order — so the key order of the object you sign is part of the digest. The canonical order is `a, b, p, s, r, t` then optional `c` for an order wire; `type, orders, grouping` for a place; `type, cancels` for a cancel; `type, oid, order` for a modify; and `isMarket, triggerPx, tpsl` inside a trigger. A JSON round-trip does not preserve key order, so the service re-canonicalizes your posted action into that exact order before hashing, recovering, and relaying — values still bind the hash, so a changed value is still caught. Build your action in the same order and sign that. ::: Venue credentials that appear above — Gemini and Myriad key/secrets, Novig's account access token, Polymarket US's key ID and Ed25519 secret, the legacy hosted Polymarket and XO L2 CLOB triples, Predict.fun's session bearer, and Opinion's user API key — transit only for that call. PRED's partner key/JWT shape is retained for a caller-owned arrangement but is not available through the darked hosted lane. No transient credential is persisted, logged, or used by a background job. Pascal needs no such relay credential: its private trading key remains client-side and only the signature crosses the API. That is exactly why automatic status polling is impossible for the credentialed venues, as the next section explains. For the venue-direct Polymarket SDK path, the CLOB triple does not cross the Predictefy API at all; the SDK signs its venue requests locally and keeps status/cancel direct. ## Client-direct submission: reserve and ack Hosted `/submit` sends your order to the venue from Predictefy's servers. Some venues geo-wall data centres, so a caller whose own egress is eligible can submit **directly to the venue** and still keep Predictefy's spend caps and execution record. This is a second lane, not a replacement: hosted `/submit` is unchanged and does not use this flow. The sequence is build, reserve, submit yourself, ack. 1. **Build** as usual. Predictefy stores the artifact and returns its `executionId`. 2. **`POST /v1/exec/{venue}/orders/{executionId}/reserve`** — send `{}`. This atomically reserves the stored notional against the per-order and rolling-24h caps, under the same per-key serialization as build, submit and ack. It answers `201` with a single-use `reservationToken`, an `expiresAt`, and the `reservedNotional`. 3. **Submit to the venue yourself**, from your own IP, using the artifact you built. 4. **`POST /v1/exec/{venue}/orders/{executionId}/ack`** — present the token as an `X-Reservation-Token` header and post the venue's order reference or receipt. Both calls need the `trade` scope and a unique `Idempotency-Key`. ### What the reservation is for The caps in [Spend caps and fail-closed bounds](#spend-caps-and-fail-closed-bounds) apply to this lane exactly as they do to hosted submit — reserve applies them **before** the order reaches the venue, and ack consumes the reservation before the execution changes state. Without that step a client-direct order would bypass the caps entirely. The token is returned once and never re-issued. It is bound to this exact account, API key, execution, venue and stored artifact, and it is single-use. Its lifetime is set by `EXEC_RESERVATION_TTL_MS` (default 120 seconds, bounded 15–900); an unconsumed reservation expires and frees its budget automatically, so an abandoned attempt does not strand your allowance. ### What ack will and will not accept Ack moves the stored execution to `acked` and no further. Ordinary venue reconciliation stays authoritative from there, so **client-asserted terminal states and fills are rejected** — you cannot tell Predictefy an order filled. The body is a receipt and carries no venue-authorizing signature. Predictefy cannot verify a signature it never held, and storing an unverified copy would leave a replayable artifact at rest, so it does not ask for one. This consumes the same metered action as hosted submit. ### From the SDK `client.exec.reserveOrder()` and `client.exec.ackOrder()` cover both calls. The MCP server deliberately does not expose either: an agent should not be constructing venue submissions out of band. ## Required scopes by endpoint A `403 SCOPE_MISSING` response deliberately does not name the missing scope, so a caller cannot enumerate the scope surface by probing. This table is the answer instead. | Endpoint group | Required scope | | ------------------------------------------------------------------------------------------------------------------------------- | -------------- | | Every `/api/{exchange}/…` catalog, order-book, trades, history, execution-price, capability, and cross-match verb | `read` | | Every `/api/feeds/…` reference-feed verb | `read` | | `/v1/traders/…` — tape, holders, leaderboards, wallet profiles, smart money | `read` | | `/v1/accounts/…`, `/v1/portfolio`, `/v1/funding/{venue}/requirements`, `/v1/funding/{venue}/steps`, `/v1/bridge/…` | `read` | | `/v1/clusters`, `/v1/clusters/{id}`, `/v1/discrepancies`, `/v1/discrepancies/{clusterId}/qualification`, `/v1/mappings` | `read` | | `/v1/venues/metrics`, `/v1/webhooks`, `/v1/webhooks/{id}`, `/v1/webhooks/{id}/deliveries` | `read` | | `/v1/usage`, `/v1/billing/checkout`, `/v1/billing/subscribe`, `/v1/billing/portal` | `read` | | The WebSocket streaming lane | `read` | | `POST /v1/sql` (`executeSql`) | `sql` | | **Every** `/v1/exec/…` route — build, submit, cancel, modify, refresh, and the order, trade, position, balance, and venue reads | `trade` | A few endpoints sit outside the table by design: the health and status endpoints need no key at all, `POST /v1/billing/webhook/stripe` is authenticated by its Stripe signature rather than an API key, and `GET /api/feeds/{feed}/fetchOrderBook` carries no scope mapping because it only ever answers a capability error — no feed source provides depth. All self-serve API keys carry `read` and `trade`. The `sql` scope is the exception: no self-serve plan grants it, and it is provisioned on request — see the [credits and plans guide](/guides/credits/). A valid key that lacks the scope a route requires is rejected before any credit is debited or usage recorded. ## Client-initiated order-status refresh Automatic status polling is available only when a status path needs no per-user credential. Gemini, Kalshi, Novig, Polymarket, Opinion, and Predict.fun authenticate order reads with the caller's venue credential, which the execution service does not retain. That no-escrow boundary is why the caller must initiate: ```http POST /v1/exec/:venue/orders/:executionId/refresh ``` The supported body is venue-specific: | Venue | JSON body | | ----------- | ------------------------------------------------------------------------------------------------- | | Gemini | `{ "apiKey": "...", "apiSecret": "..." }` — time-based-nonce, non-heartbeat Trader key | | Kalshi | `{ "apiKeyId": "...", "privateKeyPem": "..." }` — transient RSA credentials; no subaccount | | Novig | `{ "callerAccessToken": "..." }` — the caller's own Novig account access token | | Polymarket | `{ "apiKey": "...", "apiSecret": "...", "apiPassphrase": "..." }` | | Opinion | `{ "userApiKey": "..." }` | | Predict.fun | `{ "authToken": "..." }` — the caller's session bearer; uses the order hash created at submission | Hyperliquid's status path needs no user credential and serves the same `POST` with an empty body. Limitless implements the same credential-free shape, but **as verified on 2026-08-16 its restricted production egress receives `403 GEO_BLOCKED` even on status reads**; an empty body does not bypass that venue policy. The TypeScript SDK exposes the same route: ```ts const refreshed = await client.exec.refreshOrderStatus({ venue: 'polymarket', executionId, apiKey, apiSecret, apiPassphrase, }); ``` This is read-only with respect to the venue: it reads status and fills, may advance a non-terminal execution status, and never builds, signs, submits, or cancels an order. Terminal statuses never regress. The route is not metered. It is idempotent and safe to retry after a transient failure; unlike mutating POSTs, it needs no `Idempotency-Key`. POST keeps the transient credential in the request body instead of a URL or query string. ## Spend caps and fail-closed bounds The effective defaults are **100 USD per order** and **1,000 USD per API key across the rolling 24-hour window**. A cap violation is rejected; the service never silently reduces an order. Each available venue integration also enforces venue-specific contract, currency, chain, owner, and artifact bounds. Missing or unknown bounds keep that venue unavailable. ## Venue execution status Status is per venue integration, not a blanket venue claim. | Venue | Current hosted status | Current behavior and limitations | | ------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Gemini | **Armed 2026-08-15: build + submit + cancel** | Limit orders only. Caller-owned time-based-nonce, non-heartbeat Trader credentials transit each authenticated call and are never retained; status refresh is caller-initiated. | | Hyperliquid | **Armed: build + submit + cancel + modify + `approveAgent`** | Mainnet client-signed orders and single-order modify are live. Credential-free status reconciliation and empty-body refresh are implemented. | | Kalshi | **Armed: official REST build + submit + cancel** | Literal `KALSHI_EXECUTION_ENABLED=true` arms catalog-bound official V2 order, cancel, and caller-credentialed refresh. Caller `apiKeyId` + RSA `privateKeyPem` transit one request and are never stored or logged; optional non-negative `subaccount` is submit-only. `client.accounts.kalshi` remains the separate direct, account-backed RSA lane whose credentials stay entirely local. | | Limitless | **Armed; geo-gated** | Build, submit, and cancel are advertised. **Verified 2026-08-16:** restricted production egress receives `403 GEO_BLOCKED`, including on status reads, despite the credential-free status implementation. | | Myriad | **Armed 2026-08-15: build + submit + cancel** | BSC Order Book only. Wallet-bound HMAC credentials transit submit/cancel after EOA recovery; AMM order ids and status refresh remain unsupported. | | Novig | **Armed 2026-08-22: build + submit + cancel** | Limit orders only. Build stores an authless REST artifact; caller `callerAccessToken` transits submit, cancel, and caller-initiated refresh only. Cancel acknowledgement is asynchronous, so refresh confirms final status. Production endpoints require US egress. | | Opinion | **Armed: build + submit + cancel; eligible relay** | Hosted requests use eligible regional egress and reach the venue; a live credentialed submit is not yet verified on that path. The direct SDK path keeps the caller's key in-process; no automatic hosted poll exists. | | Pascal | **Armed 2026-08-12; fleet-verified 2026-08-15: build + submit + cancel** | Client-signed place/cancel permits retain no private key. **Status refresh CORRECTED 2026-08-18:** it does exist and needs no credential — Pascal's account reads are keyless, so the background reconciler polls it too. No account or funding-helper lane exists; collateral funding remains venue-directed. | | Polymarket | **Armed: build + submit + cancel; eligible relay** | Hosted requests use eligible regional egress and reach the venue; a live credentialed submit is not yet verified on that path. `client.accounts.polymarket` remains the caller-direct architecture. | | Polymarket US | **Armed 2026-08-15: build + submit + cancel** | Catalog-bound limit order, transient Ed25519 request auth, cancel, and caller-initiated refresh are implemented. Credentials and generated headers are never retained. | | Predict.fun | **Armed: build + submit + cancel; eligible relay** | Hosted requests use eligible regional egress and reach the venue; a live credentialed submit is not yet verified on that path. The caller-direct SDK path is source-ready and awaits one eligible submit → status live pass. | | PredictStreet | **Armed 2026-08-15: build + submit** | VAULT orders on ADI Chain `36900`. **No cancel: PredictStreet's API publishes no cancel endpoint at all**, so `cancelOrder: false` is venue truth rather than an unarmed Predictefy flag. Submit does fold the venue's own returned order status (`PENDING`/`OPEN`/`FILLED`/`CANCELLED`/`EXPIRED`/`REJECTED`) into the stored execution, but there is no separate hosted status-refresh read, account read, settlement lane, or automatic poll. | | Rain | **Armed 2026-08-15: build + submit + cancel** | Approval, LIMIT/protected-market, cancel, and claim builds are implemented. Submit verifies and parks at `signed`; the caller owns broadcast. | | XO | **Rebuilt and re-armed 2026-08-18: build + submit + cancel** | Chain-3223 orders on the venue's current 13-field CTF contract; cancel and a client-credentialed status refresh are served. No hosted account read, funding helper, or automatic poll — a refresh is caller-initiated. Nothing has been submitted to XO yet, so the first live submit is the confirmation checkpoint. | | PRED | **Darked 2026-08-13; not in `/v1/exec/venues`** | Source-ready Base Safe build/sign code remains documented, but the venue confirmed platform-key submits are refused. `PRED_EXCHANGE_ADDRESSES` is empty and there is no supported hosted credential path. | ## SX Bet client-side SDK execution The table above covers hosted execution. SX Bet has no hosted execution integration. Its separate venue-direct SDK integration is available at `client.accounts.sxbet`. The private key, signatures, and required SX Bet API key stay in the caller's process and go only to sx.bet. The shipped SX Bet write surface defaults to V3: - Limit and market orders both sign one EIP-712 order and post to `POST /orders-v3`. Limit orders use `GTC`; market orders use `IOC`, where `percentageOdds` is the worst accepted price. - Cancellation uses API-key-authenticated `DELETE /orders-v3`, `/orders-v3/event`, and `/orders-v3/all`. By-id cancellation reports its outcome synchronously; event/all acknowledge asynchronous batches, which the SDK drains while `hasMore` remains true. - Funding is proxy-wallet based. Deploy the account proxy, then use `client.accounts.sxbet.depositToProxy` to sign and submit the transfer permit. The old `client.funding.buildPermitRequest` and `POST /orders/approve` path are V2-only and refused on V3. - `armHeartbeat` and `disarmHeartbeat` use `/heartbeat/v3`. The same `x-sx-api-key` credential is mandatory for every V3 trading and cancellation request. :::caution[SX Bet prerequisites and depth status] The trading wallet must have a registered sx.bet account. A key-only wallet is rejected with `INSUFFICIENT_KYC`, even when its signature is correct. Its V3 proxy wallet must be deployed and funded before the venue accepts an order. V2's `TokenTransferProxy` approval is not a V3 precondition. `SXBET_API_VERSION=v2` is an explicit sandbox-only compatibility override. **Healed 2026-08-15:** the earlier Railway-egress honest-empty book incident is historical. Production `fetchOrderBook` returned real two-sided SX Bet depth with 4 bids and 5 asks. That healing changes current book availability, not the execution boundary: SX Bet remains a venue-direct client-side SDK lane with no hosted execution integration. ::: This client-side integration does not imply hosted execution or unlisted verb coverage. ## Smarkets client-side SDK execution **Dark venue.** Smarkets is implemented but not served on this deployment: hosted `/api/smarkets/…`, account and funding routes return 404, it is excluded from router fan-outs and from the served venue count. The venue-direct SDK client remains in the package for customers who hold their own Smarkets API approval, but it is not a supported product lane until a commercial API agreement is in place. The venue-direct SDK integration was made client-side only **by owner decision taken on 2026-08-18**; the hosted execution lane never existed. The decision is a custody one: Smarkets authenticates with a full account **email and password**, and there is no scoped, revocable API credential to hold instead. A hosted lane would therefore mean Predictefy relaying credentials that control the whole account, which crosses the custody line this platform does not cross. A hosted lane remains a future maybe if the venue ever ships scoped keys; it is not planned work today. The shipped write surface at `client.accounts.smarkets` is: - `createOrder({ marketId, outcomeId, side, amount, type })` — `POST /v3/orders/`. `type: 'limit'` sends a `good_til_halted` order; `type: 'market'` sends an aggressive `immediate_or_cancel` limit at buy 9999 / sell 1. - `cancelOrder(orderId)` — `DELETE /v3/orders/{id}/`, then a read-back of the cancelled order. Three venue facts shape it, and none of them is a Predictefy gap: - **Credentials never transit Predictefy.** The email and password are sent only from your process to `api.smarkets.com`. The resulting session token lives in volatile memory and is never logged, returned, persisted, or sent to Predictefy. They still exist in your process, so prefer a dedicated, restricted account. - **The account must have venue API access.** Smarkets rejects an ordinary account that has not been approved as an API user; that approval is granted by the venue, not by Predictefy. - **MFA-enabled accounts are refused.** The SDK does not accept MFA secrets, so a login that returns an MFA factor raises `NOT_SUPPORTED` instead of prompting. There is no modify verb and no hosted relay. `amount` is your own money at risk — back stake on a buy, lay liability on a sell — not Smarkets' `quantity` pot; the client converts and floors so the relevant contribution never exceeds what you asked for. ## Honest limitations - `fetchBalance` on the hosted route intentionally returns `501 NOT_SUPPORTED`. The SDK account path is separate: `client.accounts.` performs supported balance, position, and order reads directly from the venue with credentials that remain in your process. - `fetchPositions` may be derived from this account's recorded fills; inspect `meta.derivation` instead of assuming a venue-native portfolio response. - Cancellation is venue-dependent. Public execution does not imply that every optional lifecycle action is supported by that venue. - Hyperliquid serves one `modifyOrder` action at a time. The venue's separate `batchModify` action is not shipped. - Automatic order-status code exists for Hyperliquid and Limitless. Hyperliquid can use it credential-free; Limitless's restricted production egress currently receives `403 GEO_BLOCKED`, including on status reads. Rain is armed, but hosted submit stops at `signed` and the caller must broadcast before an on-chain receipt can advance it. Gemini, Kalshi, Novig, Polymarket, Opinion, and Predict.fun require caller-initiated credentialed refresh. PRED is darked and has no refresh implementation. - The MCP surface registers `exec_quote`, `exec_prepare`, and `exec_submit` by default as of 2026-08-18; set `MCP_ENABLE_TRADE=false` for a read-only server. No one-shot spend or signing tool exists, and `exec_submit` previews unless passed `confirm: true`. - Live venue status does not guarantee market availability, fill probability, price, or profit. --- # MCP server > Deliberate agent-surface curation — 43 default tools with broad, explicitly bounded REST coverage. Source: https://docs.predictefy.com/guides/mcp/ :::note[Beta release] Published on npm as **`1.0.0-beta.5`**. Pin an exact version while the beta line moves. ::: `@predictefy/mcp` is a guardrailed [Model Context Protocol](https://modelcontextprotocol.io) server for the Predictefy API. It gives AI agents one tool surface over all [16 served venues](/reference/venues/), including PredictStreet. `list_venues` reports each venue's capability notes — read them before assuming a venue has a trades tape or a trading lane. It uses the hosted API via the [Predictefy SDK](/guides/sdk/). The default surface registers **43 tools**: the thirty-three read, intelligence, and platform tools below plus ten guardrailed execution and collateral tools. The ten are on by default; only the literal `MCP_ENABLE_TRADE=false` disables them. See [Turning them off](#turning-them-off) to run a server that genuinely cannot trade or move collateral. This is deliberate agent-surface curation, not REST parity. REST defines **92 operations**. The default MCP surface semantically covers **86/92** and directly invokes **84/92** through those 43 tools. The six absent operations are `listHistoryBookEvents`, `listDiscrepancyHistory`, `getUsage`, `execReserveOrder`, `execAckOrder`, and `stripeWebhook`. The last is deliberately absent because it is an inbound provider callback, not a caller-facing operation. With trade tools disabled, coverage is **69/92 semantic** and **67/92 direct**. Tools are **grouped and parameterized** rather than one-per-endpoint: a `kind` or `scope` enum selects the verb inside a family, so an agent sees a readable tool list instead of ninety near-identical entries. Since a grouped tool shares one schema across its kinds, a parameter the chosen kind cannot honor is **refused with an error naming it**, never silently dropped — an agent must never read an unfiltered or unpaged result as if its filter had applied. Covered operations do not imply parameter parity. `get_orderbook` and `get_trades` clamp their `limit` at 100 versus REST's 1000. `get_ohlcv` exposes three resolutions versus REST's twelve. `get_orderbooks` accepts up to 100 ids even though REST caps the batch at 50. ## Configure Claude Claude Desktop (`claude_desktop_config.json`) or any MCP-compatible client: ```json { "mcpServers": { "predictefy": { "command": "npx", "args": ["-y", "@predictefy/mcp"], "env": { "PREDICTEFY_API_KEY": "pk_live_your_key_here" } } } } ``` Claude Code one-liner: ```sh claude mcp add predictefy -e PREDICTEFY_API_KEY=pk_live_your_key_here -- npx -y @predictefy/mcp ``` ## Environment | Variable | Default | Purpose | | --------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- | | `PREDICTEFY_API_KEY` | — | API key sent as `Authorization: Bearer …`. | | `PREDICTEFY_API_URL` | `https://data.predictefy.com` | API origin override; the hosted reads origin is the default. | | `PREDICTEFY_MCP_TIMEOUT_MS` | `15000` | Per-tool-call time budget (ms). | | `MCP_ENABLE_TRADE` | `true` | Registers the ten execution/collateral tools. Only the literal `false` produces a read-only tool surface. | | `MCP_EXEC_BASE_URL` | `https://exec.predictefy.com` | Isolated execution origin used by the `exec_*` tools. | | `MCP_EXEC_ALLOWED_HOSTS` | — (any valid non-metadata host) | Optional comma-separated host allowlist for `MCP_EXEC_BASE_URL`; the configured host must match when set. | ## Market data tools | Tool | What it does | Server-side caps | | ---------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------- | | `list_venues` | The 16 served venues + the dark one, with capability notes (book tier, trades tape). | — (static, no upstream call) | | `search_markets` | Text search over markets on one venue, or ALL venues when omitted. | max 100 rows | | `screen_markets` | Screen markets with catalog filters and price-change windows. | max 100 rows | | `get_market` | One market by `venue` + `marketId` (or `slug`). | 50KB response budget | | `get_events` | Event-shaped listings on one venue or ALL venues. | max 100 rows | | `get_event` | One event (with markets) by `venue` + `eventId` (or `slug`). | 50KB response budget | | `get_orderbook` | Live order book for one outcome (`venue` + `outcomeId`). | depth clamp 100 when asked | | `get_orderbooks` | Many books in one call (`outcomeIds` csv) → an outcomeId → book map. | max 100 ids | | `get_ohlcv` | OHLCV candles (`resolution` `1m`/`1h`/`1d`, optional `start`/`end`). | max 5000 candles | | `get_trades` | Recent public trades tape where the venue exposes one. | max 100 rows | | `get_catalog_metadata` | `kind`: categories, tags, series, event-metadata, capabilities (`has`), paginated pages. | max 100 rows | | `get_execution_price` | Stateless VWAP calculator over a book you pass; `detailed` adds the partial-fill breakdown. | 50KB response budget | | `filter_catalog` | Pure stateless filter over market or event rows you already have. | 50KB response budget | | `get_feed_data` | Reference feeds — `kind`: list, markets, ticker, tickers, ohlcv, oracle round/history, prices. | candles 5000 / rows 100 | ## Intelligence tools | Tool | What it does | Server-side caps | | ----------------------- | ----------------------------------------------------------------------------------------------------- | -------------------- | | `get_matches` | Cross-venue matches — `scope`: market, event, or related (subset/superset outcome edges). | max 100 rows | | `get_matched_markets` | Browse matched pairs ranked by INDICATIVE price difference. | max 100 rows | | `compare_market_prices` | One anchor across venues — `kind`: venues (per-venue prices) or hedges (opposite-side candidates). | max 100 rows | | `get_clusters` | Cross-venue clusters — `kind`: canonical (plus `clusterId` for one cluster's members), market, event. | max 100 rows | | `get_discrepancies` | Indicative discrepancies; `clusterId` + `size` runs the fail-closed live qualification instead. | stored 100 / live 10 | | `get_arbitrage` | Fully gated live-book execution analysis at a size; `executableOnly` serves only passing rows. | max 500 rows | ## Trader intelligence tools | Tool | What it does | Server-side caps | | -------------------- | ------------------------------------------------------------------------- | -------------------- | | `get_market_traders` | Wallet-attributed trades for one market, with keyset pagination. | max 100 rows | | `get_market_holders` | Top holders per outcome for one market. | max 100 rows | | `get_leaderboard` | Venue profit/volume/score rankings, or cross-venue score rankings. | max 100 rows | | `get_wallet_profile` | Venue-scoped wallet statistics, score, factors, category, and provenance. | 50KB response budget | | `get_wallet_trades` | One wallet's venue-scoped trade history, keyset-paged. | max 100 rows | | `get_smart_money` | Ranked notable scored trades with venue/wallet/market/category filters. | max 100 rows | The six trader tools use the live, capability-qualified Trader Intelligence routes. Unsupported venue/verb combinations fail honestly instead of returning made-up empty data. Scores are informational signals, not financial advice, and tool output is not a recommendation. ## Account, funding, and platform tools | Tool | What it does | Server-side caps | | ------------------------ | -------------------------------------------------------------------------------------------------------- | -------------------- | | `get_account` | Venue account resources — `kind`: capabilities, snapshot, balances, positions, open-orders, fills. | max 100 rows | | `get_funding` | Funding and bridge INFO — `kind`: requirements, bridge-status. Artifact-bearing reads are gated. | 50KB response budget | | `get_platform_metadata` | `kind`: portfolio (public address valuation), mappings, venue-metrics. | 100 mapping pairs | | `run_sql` | One read-only SQL statement. Requires an API key with the `sql` scope, enforced server-side. | 50KB response budget | | `get_webhooks` | This account's endpoints (`kind=endpoints`) or one endpoint's deliveries. | max 100 rows | | `manage_webhook` | **Write.** `action=create` (signing secret returned once) or `action=delete`. Both need `confirm: true`. | — | | `create_billing_session` | **Write.** Opens a Stripe session URL — `kind`: checkout, subscribe, portal. Buys nothing itself. | — | `manage_webhook` and `create_billing_session` are the only non-execution tools that write, and both touch nothing but the caller's own account through the caller's own key. They are annotated for what they are — `readOnlyHint: false`, plus `destructiveHint: true` on the delete-capable one — rather than hidden behind a read-only hint. The Stripe webhook callback route is deliberately absent: it is an inbound provider callback with no client meaning, and the TypeScript SDK omits it too. ## Execution tools (on by default) The package also registers the ten execution and collateral tools below, **on by default** as of 2026-08-18 — every API key already carries the `trade` scope, so a second environment gate protected nothing. `MCP_EXEC_BASE_URL` defaults to the canonical isolated execution origin `https://exec.predictefy.com`; set it explicitly to point at another deployment. A malformed or link-local/cloud-metadata value refuses to boot rather than risk sending a signed order to the wrong host. | Tool | What it does | | ----------------------- | ---------------------------------------------------------------------------------------------------- | | `exec_venues` | Read-only: which lanes are armed now (build/submit/cancel/modify per venue). | | `exec_quote` | Read-only preview of the live-book cost. Nothing is built, signed, or submitted. | | `exec_prepare` | Builds an unsigned order artifact; it never signs. | | `exec_submit` | Relays a client-signed artifact. Dry-run unless `confirm` is exactly `true`. | | `exec_cancel` | Builds an unsigned **cancel** intent. It cancels nothing on its own. | | `exec_modify` | Builds an unsigned **modify** intent for a resting order. | | `exec_refresh` | Re-reads one execution's venue status with a transient, never-stored credential. | | `exec_orders` | Read-only: `kind` order, list, trades, positions, or balance for this account. | | `prepare_funding` | Builds unsigned collateral steps, opens a bridge session, or reports a payment. | | `get_funding_artifacts` | Read-only funding reads that RETURN signable artifacts: transfer plan, bridge quote, bridge session. | Cancel and modify follow exactly the same shape as prepare: they hand back an unsigned artifact for your own wallet to sign, and the signed artifact goes back through `exec_submit`. Nothing is ever signed server-side, and no tool both builds and submits. `prepare_funding` and `get_funding_artifacts` sit with this surface because both hand back caller-signable, collateral-moving transactions. Only the purely informational funding lookups (venue requirements and transfer status) stay ungated, on `get_funding`. One honest caveat: `get_funding kind=bridge-status` relays the bridge provider's status payload verbatim (thin-client), so its artifact-freedom is provider-shaped rather than structurally enforced by Predictefy. ### Turning them off Set `MCP_ENABLE_TRADE=false` in the server's environment. The ten tools above are then not registered at all, and the surface is exactly the thirty-three read, intelligence, and platform tools listed earlier — nothing that can trade or move collateral, and nothing that hands back a transaction to sign. ```json { "mcpServers": { "predictefy": { "command": "npx", "args": ["-y", "@predictefy/mcp"], "env": { "PREDICTEFY_API_KEY": "pk_live_your_key_here", "MCP_ENABLE_TRADE": "false" } } } } ``` No tool both builds and submits an order, and no signing endpoint exists. The execution service still enforces trade scope, spend caps, artifact bounds, and idempotency. ## Guardrails (designed in, enforced server-side) - **Hard limit clamps** — most list tools cap at **100 rows**, live discrepancy lists at **10 rows**, and `get_ohlcv` at **5,000 candles** at the wire, regardless of what the model asks for. - **Response byte budget** — payloads over **~50KB** are truncated with an explicit `"truncated": true` + note (never silently). - **Per-request timeout** — a hung upstream surfaces a clean MCP error after **15s** (configurable), never a hang. - **Input validation** — unknown venues are rejected _before_ any upstream call, with the full valid venue list in the error. - **No secrets in output** — the API key is redacted from every error path. - **Honest annotations** — every read tool is annotated `readOnlyHint: true`, and every tool that writes says so. `exec_quote`, `exec_venues`, and `exec_orders` are read-only; `exec_prepare`, `exec_cancel`, `exec_modify`, `exec_refresh`, `prepare_funding`, and `create_billing_session` are not read-only; `exec_submit` and `manage_webhook` are destructive — and both still preview unless passed `confirm: true` — `manage_webhook` on create as well as delete. - **Regression-locked tool set** — the exact ungated tool list is pinned by test, so nothing that can trade or move collateral can reach the default surface unnoticed. ## Notes for agents - `myriad` has a dual book model: `myriad:ob:*` markets serve native CLOB depth, while AMM markets expose an emulated indicative top-of-book. `gemini` serves real sized CLOB depth. Only `rain` is wholly emulated as a one-level synthetic book. `list_venues` reports the tier per venue. - Cross-venue price gaps served by the platform are labeled **indicative price discrepancy** — observed price gaps (stored Yes prices by default, live order-book mid-prices with `live=true`), not executable opportunities. - `get_trades` returns the server's honest `NOT_SUPPORTED` error on venues without a public tape. - Trader tools return the server's honest `TRADERS_UNSUPPORTED` error when a venue or trader verb is unavailable. Cross-venue `get_leaderboard` calls require `by=score`; wallets stay venue-scoped, and `window=all` means "since collection began" for the scored-trade feed. - **There are no streaming tools.** MCP is request/response, so the [WebSocket surface](/guides/streaming/) — live books, trades, price frames, and the arbitrage feed — has no MCP equivalent, and none is faked. Use `get_arbitrage` for a point-in-time snapshot of the arbitrage stream, and the SDK `watch*` verbs or the raw WebSocket when you need a live subscription. - Data Feeds (`get_feed_data`) are **reference** price sources — Binance spot and Chainlink oracles — not prediction-market venues. Their `orderbook` kind is a permanent capability gap: reference feeds publish prices, not depth, so it always answers `NOT_SUPPORTED`. - `get_account` serves **public** account data. A known but unserved dedicated resource returns the wire code `ACCOUNTS_UNSUPPORTED`, which the SDK maps to the `NotSupportedError` class without changing `.code`. This hosted server holds no venue credentials, and credentials never transit Predictefy. - `run_sql` needs an API key carrying the `sql` scope. The server enforces the scope and read-only access; a key without it gets a plain authorization error. --- # Historical data > Cross-venue OHLCV candles via fetchOHLCV, with honest per-candle provenance and the current per-venue depth. Source: https://docs.predictefy.com/guides/history/ `GET /api/{exchange}/fetchOHLCV` serves historical price candles for one venue outcome. Stored-history coverage is proven for 11 venues: `polymarket`, `kalshi`, `hyperliquid`, `opinion`, `limitless`, `predictfun`, `myriad`, `rain`, `gemini`, `predictstreet`, and `pascal`. The other six report `capabilities.history: false`, but that flag means coverage is unproven, not that `fetchOHLCV` must be empty. In particular, `xo`, `pred`, and `novig` can return candles rolled up from write-forward points (`source: derived`, `sourceType: rollup`, `isTrueCandle: false`). These are derived, not true venue candles, so check `source`, `sourceType`, and `isTrueCandle`. Capture and backfill are availability-dependent. Predictefy makes no uninterrupted-capture, per-venue freshness, or **growing daily** guarantee. Request the exact range you need. SX Bet serves captured, trade-derived candles only; it has no official-history source or catalog-price write-forward coverage. ## Fetching candles ```sh curl -s "$PREDICTEFY_API_URL/api/polymarket/fetchOHLCV?outcomeId=OUTCOME_ID&resolution=1h&limit=500" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` | Parameter | Required | Notes | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `outcomeId` | yes\* | The catalog `outcomeId` returned by market reads, or the venue-native id (`id` is accepted as an alias). | | `resolution` | yes | `1s`/`5s`/`10s`/`30s`: trade-derived for the six venues below. `1m`/`1h`/`1d`: stored. `5m`/`15m`/`30m`/`4h`/`6h`: aggregated (`source: derived`, `sourceType: rollup`). | | `start`/`end` | no | ISO timestamp or epoch milliseconds. | | `limit` | no | Up to **5,000 candles per call** — page longer ranges with `start`/`end`. | | Venue | Sub-minute outcomeId format | Example | | ----------- | ------------------------------------------ | --------------------- | | polymarket | CLOB asset id | `11198861…` | | kalshi | ticker (YES view) / `ticker-NO` (NO view) | `KXBTC15M-…-00` | | hyperliquid | full HIP-4 asset id (venue coin uses `#N`) | `100001730` (`#1730`) | | sxbet | `marketHash#outcomeIndex` | `0x3f…9a#0` | | myriad | `networkId:marketId:outcomeIdx` | `42220:1320:0` | | pascal | `{symbol}#0` (the primary traded outcome) | `PMKT_CEO.26AUG31#0` | REST-derived candles use these verbatim catalog formats (the venue-native id is also accepted): | Venue | Catalog outcomeId format | Example | | ---------- | --------------------------------------------- | --------------------------------------------------- | | predictfun | `predictfun:{marketId}:{indexSet}` | `predictfun:32153:1` | | limitless | `{marketSlug}:{yes\|no}` | `draw-1784184108504:yes` | | gemini | `GEMI:{marketId}:{nativeOutcomeId}:{yes\|no}` | `GEMI:NGAS2607312100:GEMI-NGAS2607312100-HI3D2:yes` | Sub-minute history is forward-only from the start of each venue's capture coverage. Use `start` and `end` to probe the range you need; empty data means the requested range predates available capture, contains no trades, or crosses a capture gap. When the history is unavailable, the route returns retryable `503 HISTORY_UNAVAILABLE`. `router` is not supported for history — candles are venue-scoped. ## Honest candles Every candle carries provenance fields so you can distinguish true venue candles from derived ones: ```json { "timestamp": 1780444800000, "open": 0.61, "high": 0.63, "low": 0.6, "close": 0.62, "volume": null, "source": "write-forward", "sourceType": "point-derived", "quality": "ok", "isTrueCandle": false } ``` - **`source`** — `official` (venue history API), `onchain`, `write-forward` (availability-dependent recorder), or `derived`. - **`sourceType`** — `true-candle` vs `point-derived` / `trade-derived` / `rest-derived` / `book-derived` / `rollup`; `rollup` means query-time aggregation of stored finer candles. - **`quality`** — `ok`, `partial`, `suspect`, or `mixed`; `mixed` means the bucket aggregates inputs of differing quality. Known capture gaps report `partial`: known-damage honesty is part of the API contract. - **`volume`** is `null` where the source doesn't provide it (most point-derived data). REST-derived candles exist only for Gemini, Predict.fun, and Limitless. REST-derived candles are served only when stored and streamed candles are both absent; low-volume accrual is expected. They always report `source: derived`, `sourceType: rest-derived`, `quality: partial`, and `isTrueCandle: false`. Rollups over a uniformly partial REST base also report `partial`. ## The raw order-book tape Candles are aggregates. `GET /v1/history/books/events` returns the lossless capture underneath them — the tape a backtest needs when the question is about book dynamics rather than closing prices. ```sh curl -s "$PREDICTEFY_API_URL/v1/history/books/events?venue=polymarket&outcomeId=0xabc…&since=2026-09-01T00:00:00Z&until=2026-09-02T00:00:00Z" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` `venue`, `outcomeId`, `since`, and `until` are all required: a call without the window returns `400 VALIDATION_ERROR`. `since` and `until` accept an ISO timestamp or epoch milliseconds, and `until` must not precede `since`. `outcomeId` is the **venue-native** outcome id — the same value you would pass to `fetchOrderBook`, not the unified market id. Rows come back ordered by receive time and sequence, and carry four kinds: `snapshot`, `delta`, `gap`, and `heartbeat`. The **`gap` rows are the point**: capture discontinuities are recorded rather than papered over, so a backtest can see where the tape is incomplete instead of silently treating a missing stretch as a quiet market. Paging uses opaque cursors that continue the immutable tape strictly after the last returned `(ts_recv, seq)` key, so a cursor never re-reads or skips a row. No SDK method wraps this yet; call it over REST. ## Coverage depth The coverage matrix states only what is currently available: | Venue | Current coverage | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Polymarket | **Deep official, bounded set:** one-year hourly coverage for a limited market set, plus availability-dependent forward capture. This is not all-market breadth. | | Kalshi | **Deep official:** broad hourly backfill plus availability-dependent write-forward capture. | | Limitless | **REST-derived fallback:** minute-and-up candles report `quality: partial`, plus availability-dependent write-forward capture. No stream capture, so sub-minute tiers are honest-empty. | | Other write-forward venues | Forward-only recording subject to per-venue capture and storage gaps; request the exact range rather than assuming continuous depth. | | SX Bet | **Captured-tape/derived only:** forward-only trade-derived candles. Minute-and-up tiers derive from the same tape. No official or catalog-price write-forward history is claimed. | :::note[No overclaiming] Official breadth differs by venue: Polymarket covers a bounded market set, while Kalshi has broader hourly backfill. We never claim the archive is complete or continuously fresh — if a venue/date range matters to you, [verify with a request](/quickstart/) or [contact support](mailto:support@predictefy.com). ::: ## Credits History reads cost a flat **5 credits** per query. At general availability, Free reaches back 7 days and Builder 12 months; a request before the active cutoff returns `PLAN_REQUIRED`. The other GA windows are listed under [Pricing, credits & billing](/guides/credits/). :::note[Beta] During the beta, every plan, including Free, receives the Pro-grade feature set. The linked table shows general-availability entitlements; rate, API-key, and stream caps are enforced today. ::: --- # Cross-venue data > Matched market clusters across venues and indicative price discrepancies — honestly labeled. Source: https://docs.predictefy.com/guides/cross-venue/ The same real-world question often trades on several venues at once. Predictefy's matching engine (embedding similarity + LLM validation) groups equivalent markets into **clusters**, and computes **indicative price discrepancies** between cluster members. :::note[Live, capability-qualified] Clusters, indicative discrepancy endpoints, and the gate-enforcing `fetchArbitrage` endpoint are live. A row earns the `arbitrage` label only when every live-depth, verified-fee, open-market, positive-edge, and resolution-equivalence gate passes. Every other row remains honestly labeled `indicative price discrepancy` with machine-readable reasons. ::: ## Clusters ```sh curl -s "$PREDICTEFY_API_URL/v1/clusters?limit=20" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` - `GET /v1/clusters` — a page of cross-venue clusters. - `GET /v1/clusters/:id` — one cluster with its per-venue member markets. Cluster members carry a **`similarity`** score — the raw embedding similarity between the matched markets. It is deliberately _not_ called "confidence": it is not a calibrated probability that the markets are equivalent. Cluster ids are stable — they do not change when a member market delists. ### Filtering clusters `GET /v1/clusters` returns a plain page. When you need to narrow the set, `GET /api/{exchange}/fetchMatchedMarketClusters` is the filterable projection of the same store: - **`minSimilarity`** — floor on the stored matcher score. - **venue allow/deny lists** — restrict which venues may appear. - **`category`** — taxonomy scope. - **relation filtering** — identity, subset or superset, asking only what the matcher actually verified. - **`includeRawMatches`** — up to 100 pairwise matches per cluster, for auditing why a cluster formed. Members come back as full `UnifiedMarket` objects with the same delist-stable `clusterId`. Two omissions are deliberate. **`volume24h` is absent**, because the cluster store has no aggregate column and a missing field is honest where a zero would be fabricated. And there is **no per-cluster `relations` field**: the filter can ask what the matcher verified, but the response does not assert a relation it did not compute. `GET /api/{exchange}/fetchMatchedEventClusters` is the event-grain equivalent. ### Matching at event grain Clusters match *markets*. `GET /api/{exchange}/fetchEventMatches` matches **events**, which is a different question — "is this the same election?" rather than "is this the same contract?" It has two modes. Without an `eventId` it browses, returning cross-venue event pairs from a cluster page as `sourceEvent` and `event`. With an `eventId` it looks up co-member events for the canonical `"{venue}:{eventId}"` anchor. Scores are `similarity`, never confidence, and `reasoning` is `null` — the event matcher emits no per-match rationale, so the field is present and empty rather than filled with something invented. There are no prices on this surface; it is discovery, not quoting. Gated by `READS_ENABLE_EVENT_MATCHES`. ## Indicative price discrepancies ```sh curl -s "$PREDICTEFY_API_URL/v1/discrepancies" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` Pass `?live=true` to recompute each discrepancy from **live order-book mids** instead of the latest snapshot prices (the response's `meta.live` tells you which you got). Use `expand=markets` to add the catalog `market` object to every `low` and `high` leg: title, venue, status, close time, image URL when stored, liquidity, and volume. The CSV `expand` query parameter supports only `markets` today; any other value returns `400`. The expansion adds no extra metering weight. Stored mode keeps its existing `limit` default of **20** and maximum of **100**; this documents pre-existing behavior rather than adding headroom. `live=true` now has an explicit maximum of **10** because every live cluster recomputes against real order books. The tighter cap bounds that cost; the old shared maximum of 100 was an accidental abuse vector on the live path. :::note[Performance characteristics] `live=true` recomputes against real order books on every call, so a seconds-scale response is expected by design, not a bug. The response makes that work measurable in `meta.timings` with `booksMs`, `recomputeMs`, and `totalMs`. For anything latency-sensitive, use the [streaming channel](/guides/streaming/) instead of polling `live=true`; streaming is the intended hot path. ::: ### How a gap moved `GET /v1/discrepancies` is a snapshot of now. `GET /v1/discrepancies/history` is the record of how it got there — **change-only** snapshots, newest first, so a run of identical readings does not pad the response. ```sh curl -s "$PREDICTEFY_API_URL/v1/discrepancies/history?clusterId=CLUSTER&from=2026-08-01" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` `from` and `to` accept ISO-8601 timestamps or epoch milliseconds. `to` defaults to now, `from` defaults to 24 hours before `to`, and **every range is capped at 90 days**. Filter to one cluster with `clusterId`. Metered with the `history` weight rather than the cheaper catalog weight. ## Executable assessment `fetchArbitrage` (`GET /api/router/fetchArbitrage`) is router-only and assesses a bounded contract size against live asks: ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchArbitrage?contracts=100&limit=500&executableOnly=true&venues=polymarket,kalshi&minEdge=0.02" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` The endpoint is paged with `limit` up to **500** plus an opaque `cursor` for subsequent pages (`page.nextCursor`). Each base cluster expands into every ordered cross-venue pair (`buyYes` on venue A and `buyNo` on venue B), with 90 rows as a pathology guard. If that guard truncates a pathological cluster, every emitted row is marked `truncated: true`. The engine makes one batched live-book read per venue for the selected outcome books and reuses those results across the pairs. Each row's `clusterId` is a composite key: `${clusterId}:${venueA}:${venueB}`. The base cluster id can contain `:`, so recover it by stripping the last two colon-delimited segments. Do not split at the first colon. The first three query filters match the WebSocket filters. `pairsPerCluster` is REST-only: - `executableOnly=true` — return only rows that passed every executable gate. The default returns both qualifying rows and rejected candidates with per-leg and pair-level reasons such as `synthetic_book`, `insufficient_depth`, `unverified_fees`, `market_not_open`, or an equivalence conflict. - `venues` (or `venue`) — comma-separated venue filter. A row remains when either its `buyYes` or `buyNo` leg uses one of those venues; the other leg may use a venue outside the list. - `minEdge` — filter by minimum net edge. - `pairsPerCluster` — optional positive integer limiting rows per base cluster. Invalid values return `400`; omitting it keeps all generated pairs. On the published surface, the cap is applied after `executableOnly`, `venues`, and `minEdge`, then before cursor pagination. The response `meta` includes `{ asOf, seq, source }` where `source` is `'published'` (served from the live published surface, fresh within the publisher's self-declared cadence (about 30s; 3s floor)) or `'computed-fallback'` (bounded on-demand computation, top 10 candidate clusters) when the published surface is unavailable. Re-check the live result immediately before acting because books, depth, and market status can change after the response. ## Why "indicative" — and never anything stronger A price gap between two venues is only _tradeable_ if executable asks (not midpoints), order-book depth at those prices, per-venue fees and gas, market open-status, and **resolution equivalence** (the two markets truly settle on the same terms) all check out — live, at execution time. The discrepancy endpoints do **not** apply those gates. They tell you _where to look_, not _what to trade_: - By default, prices compared are each cluster member's **stored Yes price** from the catalog snapshot; `live=true` instead overlays current **order-book mid-prices**, and on some venues the book itself is [reconstructed](/reference/venues/) (`synthetic: true`) — indicative either way. - Two markets in a cluster may resolve on subtly different terms. - Fees, gas, spread, and depth routinely exceed a small headline gap. Treat the output as a research signal and do your own verification. Existing cross-match lookups cost **5 credits**; price-gap queries and cross-venue comparisons cost **10 credits**. --- # Streaming > The live Predictefy WebSocket service — endpoint, protocol, auth handshake, and close codes. Source: https://docs.predictefy.com/guides/streaming/ :::note[Live, capability-qualified] Use the WebSocket origin shown in your developer dashboard and connect to `/v1/stream`. Venue and channel support is capability-qualified: unsupported subscriptions return an honest `NOT_SUPPORTED` frame instead of a synthetic stream. ::: :::caution[SDK availability] The `watch*` helpers on this page are **TypeScript-only today**. The synchronous Python SDK has no WebSocket client; poll `fetch_order_book` / `fetch_order_books`, `fetch_trades`, `client.router.compare_market_prices`, or `client.router.fetch_arbitrage` instead. ::: Predictefy streams four things over one WebSocket, one auth handshake, and one metered connection: - **Prediction-market streaming** — live venue **order books and trades**. Multiple clients watching the same market share **one** upstream venue subscription, so you never burn a venue's rate limits by scaling out consumers. - **Venue option-price streaming** — live price frames from capability-qualified venue lanes (`subscribePrice`; TypeScript `watchPrices`). Rain is the current lane and requires both a `marketId` and its on-chain `marketAddress`. - **Data Feeds streaming** — auxiliary **Binance/Chainlink reference tickers** (`subscribeFeedTicker`), the streaming analogue of the REST `fetchTicker` verb. See [Data Feeds streaming](#data-feeds-streaming) below. - **Cross-venue arbitrage streaming** — one shared **executable-arbitrage surface** (`subscribeArbitrage`), recomputed server-side on a disclosed cadence rather than tick by tick, and gated on the same plan feature as the REST verb. See [Cross-venue arbitrage streaming](#cross-venue-arbitrage-streaming) below. - Endpoint path: `/v1/stream` (WebSocket upgrade). - Trade subscriptions use a native venue stream when available, or a disclosed chain-scan tape for Rain, XO, and PRED. A venue with neither answers an **honest `NOT_SUPPORTED` error without closing the socket** — never a fake stream. ## Auth handshake Connect with the same `pk_live_…` API key as the REST API: - `Authorization: Bearer pk_live_…` header (preferred), or - a **first-frame auth message** for browser clients (which cannot set WebSocket headers): `{ "op": "auth", "apiKey": "pk_live_…" }`. It must be the first frame, within 10 seconds, or the socket closes `4001`. **Keys are never read from the URL** — `?apiKey=` is deliberately unsupported (raw keys in URLs leak into proxy and observability logs). Auth is checked **before any venue subscription is created**. Failures close the socket: | Close code | Meaning | | ---------- | ------------------------------------------------------------------------------------------------ | | `1001` | Server shutting down (going away). | | `1008` | Policy violation — pre-auth message budget (count/bytes) exceeded. | | `1009` | Inbound frame exceeded the payload size cap. | | `4001` | `UNAUTHORIZED` — missing, unknown, or revoked API key. | | `4002` | `INSUFFICIENT_CREDITS` — balance cannot cover the next connection-minute. | | `4003` | `PLATFORM_UNAVAILABLE` — metering store unreachable. **Fail-closed**: never an unmetered stream. | | `4004` | `CONNECTION_LIMIT` — global, per-IP, or per-account concurrent-connection cap reached. | | `4008` | `RATE_LIMITED` — too many failed auth attempts from this IP. | Idle peers that stop answering heartbeat pings are terminated. ## Wire protocol (JSON text frames) Client → server: ```jsonc // `marketId` is the VENUE-NATIVE book id (see "Which id goes in marketId?" // below) — for polymarket the CLOB asset/token id = the outcome's `outcomeId`. { "op": "subscribe", "channel": "orderbook", "venue": "polymarket", "marketId": "" } { "op": "unsubscribe", "channel": "orderbook", "venue": "polymarket", "marketId": "" } { "op": "subscribe", "channel": "trades", "venue": "", "marketId": "" } { "op": "subscribeAll", "channel": "orderbook", "venue": "" } { "op": "unsubscribeAll", "channel": "orderbook", "venue": "" } // Venue option-price frames — marketAddress is the on-chain market contract: { "op": "subscribePrice", "venue": "rain", "marketId": "", "marketAddress": "0x…" } { "op": "unsubscribePrice", "venue": "rain", "marketId": "", "marketAddress": "0x…" } // Data Feeds reference tickers — feed + symbol, no venue/marketId: { "op": "subscribeFeedTicker", "feed": "binance", "symbol": "BTC/USDT" } { "op": "unsubscribeFeedTicker", "feed": "chainlink", "symbol": "BTC/USD" } // Cross-venue executable arbitrage — optional filters (executableOnly, venues, minEdge): { "op": "subscribeArbitrage", "executableOnly": true, "venues": ["polymarket", "kalshi"], "minEdge": 0.02 } { "op": "subscribeArbitrage" } { "op": "unsubscribeArbitrage" } ``` Server → client: ```jsonc { "type": "subscribed", "channel": "orderbook", "venue": "polymarket", "marketId": "…" } { "type": "unsubscribed", "channel": "orderbook", "venue": "polymarket", "marketId": "…" } // books: full-state frames; prices are probabilities in [0,1] { "type": "snapshot", "venue": "polymarket", "marketId": "…", "data": { "bids": [{ "price": 0.4, "size": 10 }], "asks": [], "timestamp": 1780000000000 }, "ts": 1780000000123 } { "type": "update", /* same shape as snapshot */ } { "type": "trade", "venue": "…", "marketId": "…", "data": { /* trade */ }, "ts": 1780000000123 } // Venue option-price ack + data: { "type": "subscribed", "channel": "price", "venue": "rain", "marketId": "…" } { "type": "price", "venue": "rain", "marketId": "…", "marketAddress": "0x…", "data": { /* venue option prices */ }, "ts": 1780000000123 } // Data Feeds ticker ack + data (feed/symbol, not venue/marketId): { "type": "subscribed", "channel": "feedTicker", "feed": "binance", "symbol": "BTC/USDT" } { "type": "feedTicker", "feed": "binance", "symbol": "BTC/USDT", "data": { "symbol": "BTC/USDT", "last": 61714.63, "asOf": "…", "provenance": { "source": "binance-ws" }, "sourceMetadata": { "transport": "websocket" } }, "ts": 1780000000123 } { "type": "error", "code": "NOT_SUPPORTED", "message": "…", "venue": "…", "marketId": "…" } ``` Notes: - `snapshot` is the first frame after (re)subscribe and after backpressure coalescing; `update` marks live ticks. Both carry the **full book**, never deltas. - Protocol-level problems (`BAD_MESSAGE`, `NOT_SUPPORTED`, `NOT_SUBSCRIBED`, `SUBSCRIPTION_LIMIT`) are **non-fatal**: the socket stays open. - `subscribeAll` requires a venue-wide firehose upstream; venues without one answer an honest `NOT_SUPPORTED`. - Trades stream from a native fills channel or the disclosed chain-scan tape served for Rain, XO, and PRED. Other venues without either capability return `NOT_SUPPORTED`. - Active logical subscriptions are capped by plan: 2 Free, 20 Builder, 100 Pro, and 500 Scale. A lower service safety cap can also apply. Exceeding either returns a non-fatal `SUBSCRIPTION_LIMIT`. ## Which id goes in `marketId`? `marketId` is the **venue-native** id for the exact book you want — it is passed straight through to the venue and is **not** always the unified `marketId` from `fetchMarkets`. For **polymarket** it is the CLOB asset/token id, which the unified API returns as the outcome's **`outcomeId`** (`fetchMarket` → `outcomes[].outcomeId`) — the same id you pass to `fetchOrderBook`. Other venues use their own native id (for example **hyperliquid** uses the coin symbol like `BTC`). Subscribing with the wrong id (e.g. a polymarket market-level `marketId`) returns a `subscribed` ack and then **zero data frames** — the venue never recognizes it as a book, so it looks identical to a quiet market. When in doubt, use the value you'd pass to `fetchOrderBook`. ### Going the other way: venue id to canonical identity Streaming frames carry venue-native ids, so correlating them back to Predictefy's catalog needs a lookup. `POST /v1/mappings` does it in bulk: ```sh curl -s "$PREDICTEFY_API_URL/v1/mappings" \ -H "Authorization: Bearer pk_live_YOUR_KEY" \ -H "content-type: application/json" \ -d '{"pairs":[{"venue":"polymarket","marketId":"0xabc…"}]}' ``` It accepts **1 to 200 `{venue, marketId}` pairs** and returns one result per pair **in input order**, so you can zip the response straight onto your request array. A known market returns its canonical `marketPk`, its current `status`, and its `clusterId` when it has been matched to markets on other venues. Unknown pairs and sandbox-venue pairs are **not dropped**: they stay in the response with `marketPk`, `clusterId` and `status` set to `null`. Positional correlation therefore holds even when some ids are unrecognised — you never have to re-align two arrays of different lengths. The route is gated by `READS_ENABLE_MAPPINGS` and answers 404 where it is not enabled. ## Data Feeds streaming `subscribeFeedTicker` streams auxiliary **reference tickers** — the WebSocket analogue of the REST [`GET /api/feeds/{feed}/fetchTicker`](/api/operations/feedfetchticker/) verb. Here `{feed}` means one of the curated reference feeds (`binance` or `chainlink`), not a venue id. This is **reference data, separate from prediction-market venues** (it never touches markets, clusters, or history) and rides the same auth and per-connection-minute credits as venue subscriptions. - **`binance`** — spot reference prices over Binance's public market-data WebSocket; frames carry `sourceMetadata.transport = "websocket"`. Symbols: `BTC/USDT`, `ETH/USDT`, `SOL/USDT`, `XRP/USDT`. - **`chainlink`** — on-chain oracle prices, **poll-backed** (there is no Chainlink push stream): the service polls the on-chain feed on a bounded interval and emits only when the round advances. Every frame **discloses `sourceMetadata.transport = "poll"`** so you always know it is poll-derived, not a push. The official SDK wraps the handshake: ```ts import { Predictefy } from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY }); const close = client.watchFeedTicker({ feed: 'binance', symbol: 'BTC/USDT' }, (ticker) => console.log(ticker.last, ticker.sourceMetadata?.transport), ); // later: close(); ``` The key is sent over the safe first-frame handshake — **never** a `?apiKey=` URL. An unknown feed or unsupported symbol answers a non-fatal `NOT_SUPPORTED` (the socket stays open). :::caution[Poll-backed Chainlink] Chainlink feed streaming is **poll-backed**, not a native push stream. Full Data Feeds parity is not claimed for this integration. No latency guarantees are made. ::: ## Cross-venue arbitrage streaming `subscribeArbitrage` streams the **executable-arbitrage surface** — the WebSocket analogue of the REST [`fetchArbitrage`](/guides/cross-venue/) verb. It is cross-venue, so it takes no `venue` and no `marketId`. **Optional filters.** `subscribeArbitrage` accepts optional filters: `{ executableOnly?: boolean, venues?: string[], minEdge?: number }`. Filters apply server-side; omitting them preserves the default unfiltered stream. **Pair rows.** Each base cluster emits every ordered cross-venue pair, with 90 rows as the pathology guard. The publisher makes one batched live-book read per venue for the selected outcome books and reuses those results across the pairs. Each row's `clusterId` is the composite `${clusterId}:${venueA}:${venueB}`. Because the base cluster id can contain `:`, strip the last two colon-delimited segments to recover it. The REST-only `pairsPerCluster` query parameter can cap these rows for `fetchArbitrage`; it is not a `subscribeArbitrage` filter. **What the subscription delivers.** There is one client operation: `subscribeArbitrage`. It sends the complete selected surface — the full surface when no filters are set — as snapshots and sequence-ordered deltas. A complete snapshot is due every 30 seconds; on that recompute pass it is sent immediately before the pass's delta. Every successful recompute sends a delta, including an empty `upserts`/`removes` delta when no row changed. Either kind can span messages no larger than 200KiB, tagged with `part: { i, n }`. Every arbitrage data message carries `type: "arbitrage"`, the frame in `data`, and a socket-write `ts`. Both data variants carry `exchange`, `seq`, `computedAt`, `publishedAt`, `intervalMs`, `heartbeatMs`, `contracts`, `limit`, and `part`. New and reconnecting subscribers receive the current coherent snapshot after the acknowledgement; if a client falls behind, the relay repairs it with a current snapshot before resuming deltas. The full frame shape is in the [WebSocket API reference](/reference/streaming/). **The cadence is honest, not tick-by-tick.** This is one shared server-side recompute: `intervalMs` is the real cadence (3000 ms by default). Every successful pass publishes a delta; when the priced surface did not change, that delta has empty `upserts` and `removes`. A pass with a due snapshot publishes the snapshot first and then its delta under the next `seq`, so the timestamps keep publisher liveness observable without pretending the market moved. **Read `heartbeatMs` off the frame; it is a conservative liveness bound, not a fixed constant.** It is the first recompute tick at or after the server's 30000 ms target: 30000 ms at the default 3000 ms interval, but 40000 ms if the interval were 20000 ms. Successful per-pass deltas normally arrive more often, at `intervalMs`; an empty delta is liveness, not a market change. **Sustained silence beyond the `heartbeatMs` you were actually sent means a publisher outage or an entitlement teardown** (below), not a quiet market. **You can measure the lane yourself.** Each frame carries `computedAt` (the pass finished), `publishedAt` (the server handed it to its relay), and the envelope's `ts` (written to your socket). On a live delivery, those gaps measure compute-to-publish and relay-to-socket latency; nothing on that path deliberately buffers, batches, or waits for a timer. On retained replay, `ts − publishedAt` instead measures the last-known frame's age. The recompute interval is the live lane's only deliberate delay, and it is there to bound upstream venue API cost. **The label discipline is the REST verb's.** A row is labeled `arbitrage` only with positive net edge, both legs depth-executable at the requested size against live asks, and resolution equivalence verified. Everything else is served as an _indicative price discrepancy_ with the per-leg `reasons` codes explaining why. The surface is never trimmed down to the winners — the indicative rows are part of it, with their evidence. A new subscriber gets a snapshot of the current surface right after its `subscribed` ack. The replay keeps the frame's original `publishedAt`; only the outer `ts` records the new socket write. Compare that age with `heartbeatMs`: a retained frame older than the advertised heartbeat is stale and does not claim that the publisher is still live. **Staleness contract.** The retained frame's `publishedAt` is the publisher-liveness signal: a healthy publisher refreshes it on every successful recompute, including an empty delta. If `publishedAt` trails the envelope's `ts` by more than roughly 90 seconds (the current SDK default), treat the publisher as stale and fall back to REST. A publisher that goes permanently dark after publishing once therefore continues to yield an aging retained frame instead of reverting to `NOT_SUPPORTED`; that is intentional, and client-side age detection is the safeguard. The TypeScript SDK will surface this condition as a staleness event in the current SDK release train. Publisher readiness requires at least one valid frame observed by this relay. If none has ever arrived, `subscribeArbitrage` gets a non-fatal `NOT_SUPPORTED` error instead of a success ack, even when Redis itself is reachable; the client can retry after the publisher is enabled. A deployment with no Redis relay returns the same code. The channel is separately gated on the same `arbitrage` plan feature as the REST verb — a key whose plan does not include it gets `PLAN_UPGRADE_REQUIRED`, and the Free plan does not include it. **The entitlement is re-checked while the socket is open**, roughly once a minute. If the plan stops entitling the feature mid-stream, the arbitrage subscription is torn down and you get the same `PLAN_UPGRADE_REQUIRED` frame; if the key itself is revoked or rotated you get `UNAUTHORIZED` instead, since that needs re-authentication rather than an upgrade. Either way the socket stays open and every other subscription on it keeps streaming, and a re-check that cannot complete never interrupts you. The TypeScript SDK wraps it like the ticker lane. Pass `onError` for any long-lived watcher: an entitled socket with nothing to report and a refused one look identical without it. ```ts const close = client.watchArbitrage( ({ frame }) => { for (const row of frame.rows) { console.log(row.label, row.executable, row.reasons); } }, { onError: (e) => console.error(e.code, e.message) }, ); // later: close(); ``` ## Backpressure A slow consumer never causes unbounded buffering: - **Order books are coalesced** — intermediate updates are dropped and only the latest book per subscribed market is parked; when your socket drains, that latest book arrives as a single `snapshot`. - **Trades are dropped** (not parked) while backpressured — the trade stream is lossy under backpressure by design. - **Price frames are dropped** (not parked) while backpressured — the next venue price frame supersedes the last. - **Feed tickers are dropped** (not parked) while backpressured — same policy as trades. Reference tickers are low-frequency and the next frame supersedes the last. - **Arbitrage frames** — clients that fall behind under backpressure receive a fresh snapshot automatically through hub-side stale-client recovery once the connection catches up. ## Credits Streaming costs **2 credits per connection-minute**, prepaid: minute #1 is charged at connect, each subsequent minute on the minute. When your balance cannot cover the next minute you get an `INSUFFICIENT_CREDITS` error frame, then close `4002`. See [Credits & billing](/guides/credits/). --- # TradingView charts > Build a capability-qualified TradingView Advanced Charts datafeed on Predictefy from fetchOHLCV history and supported live streams. Source: https://docs.predictefy.com/guides/tradingview-charts/ :::note[Advanced Charts is licensed separately] TradingView licenses Advanced Charts and gives you access to `charting_library` directly. Predictefy ships the data only — no charting library, chart component, or hosted chart UI. ::: ## What you're building An Advanced Charts datafeed has two data paths: historical bars from `fetchOHLCV`, then updates to the forming bar from the `trades` WebSocket channel. The chart integration stays the same across venues, but live-trade availability does not. Switch `venue` and supply that venue's native `outcomeId` / `marketId` to change the market source, then check the per-venue table below. This guide wires one configured outcome into the five datafeed methods Advanced Charts calls: `onReady`, `resolveSymbol`, `getBars`, `subscribeBars`, and `unsubscribeBars`. ## Datafeed contract Advanced Charts names these resolutions `1S`, `5S`, `10S`, `30S`, `1`, `5`, `15`, `30`, `60`, `240`, `360`, and `1D`. Predictefy's matching server resolutions are `1s`, `5s`, `10s`, `30s`, `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `6h`, and `1d`: ```ts const SERVER_RESOLUTION = { '1S': '1s', '5S': '5s', '10S': '10s', '30S': '30s', '1': '1m', '5': '5m', '15': '15m', '30': '30m', '60': '1h', '240': '4h', '360': '6h', '1D': '1d', } as const; const BAR_MS = { '1S': 1_000, '5S': 5_000, '10S': 10_000, '30S': 30_000, '1': 60_000, '5': 300_000, '15': 900_000, '30': 1_800_000, '60': 60 * 60_000, '240': 14_400_000, '360': 21_600_000, '1D': 24 * 60 * 60_000, } as const; const DATAFEED_CONFIGURATION = { supported_resolutions: ['1S', '5S', '10S', '30S', '1', '5', '15', '30', '60', '240', '360', '1D'], }; function createPredictefyDatafeed(options) { const getBars = createGetBars(options); const { subscribeBars, unsubscribeBars } = createLiveBars(options); return { onReady(callback) { setTimeout(() => callback(DATAFEED_CONFIGURATION), 0); }, resolveSymbol(_symbolName, onResolved) { setTimeout(() => onResolved(options.symbolInfo), 0); }, getBars, subscribeBars, unsubscribeBars, }; } ``` Pass `symbolInfo` in the shape required by your licensed Advanced Charts build, with the same `supported_resolutions`. This factory represents one outcome, so every symbol lookup resolves to that configured object. | Venue | Sub-minute outcomeId format | Example | | ----------- | ------------------------------------------ | --------------------- | | polymarket | CLOB asset id | `11198861…` | | kalshi | ticker (YES view) / `ticker-NO` (NO view) | `KXBTC15M-…-00` | | hyperliquid | full HIP-4 asset id (venue coin uses `#N`) | `100001730` (`#1730`) | | sxbet | `marketHash#outcomeIndex` | `0x3f…9a#0` | | myriad | `networkId:marketId:outcomeIdx` | `42220:1320:0` | :::caution[Resolution floor] Sub-minute history is forward-only from the start of each venue's capture coverage. Requests before that point return empty data rather than fabricated bars. 1m/1h/1d are stored; 5m/15m/30m/4h/6h are aggregated server-side. Live trades update the forming bar at the selected resolution. Capture and storage availability do not imply continuous freshness: gaps can exist, and temporarily unavailable history returns retryable `503 HISTORY_UNAVAILABLE`. ::: ## History via fetchOHLCV The upstream REST endpoint is `GET https://data.predictefy.com/api/{venue}/fetchOHLCV`. Your server calls it with a server-side API key. The endpoint accepts the venue-native `outcomeId`, `resolution`, optional `start` / `end` as ISO timestamps or epoch milliseconds, and an optional `limit` of up to 5,000 candles. :::caution data.predictefy.com does not send CORS headers and API keys must never ship in a browser bundle — route history calls through your own server. ::: ### Proxy history through your server Set `PREDICTEFY_API_KEY` in your server environment, then mount this Express route: ```ts import express from 'express'; const app = express(); if (!process.env.PREDICTEFY_API_KEY) throw new Error('PREDICTEFY_API_KEY is required'); const FORWARDED_PARAMS = ['outcomeId', 'resolution', 'start', 'end', 'limit'] as const; app.get('/api/predictefy/ohlcv', async (req, res) => { const venue = typeof req.query.venue === 'string' ? req.query.venue : ''; if (!venue) return res.status(400).json({ error: 'venue is required' }); const url = new URL( `/api/${encodeURIComponent(venue)}/fetchOHLCV`, 'https://data.predictefy.com', ); for (const name of FORWARDED_PARAMS) { const value = req.query[name]; if (typeof value === 'string') url.searchParams.set(name, value); } const upstream = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PREDICTEFY_API_KEY}` }, }); const body = await upstream.json(); return res.status(upstream.status).json(body); }); ``` The browser datafeed calls that route on its own origin. `periodParams.from` and `periodParams.to` are converted from seconds to epoch milliseconds. `countBack` becomes `limit`, capped at 5,000: ```ts type HistoryCandle = { timestamp: number; open: number; high: number; low: number; close: number; volume: number | null; source: 'official' | 'onchain' | 'write-forward' | 'derived'; sourceType: 'true-candle' | 'point-derived' | 'trade-derived' | 'book-derived' | 'rollup'; quality: 'ok' | 'partial' | 'suspect' | 'mixed'; isTrueCandle: boolean; }; type HistoryResponse = { data: HistoryCandle[]; meta?: unknown; }; function createGetBars({ venue, outcomeId, onHistoryMeta = () => undefined }) { return async function getBars( _symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback, ) { const serverResolution = SERVER_RESOLUTION[resolution]; if (!serverResolution) { onErrorCallback(`Unsupported resolution: ${resolution}`); return; } const params = new URLSearchParams({ venue, outcomeId, resolution: serverResolution, start: String(periodParams.from * 1000), end: String(periodParams.to * 1000), limit: String(Math.max(1, Math.min(periodParams.countBack, 5000))), }); try { const response = await fetch(`/api/predictefy/ohlcv?${params.toString()}`); if (!response.ok) throw new Error(`History request failed: HTTP ${response.status}`); const payload = (await response.json()) as HistoryResponse; if (!Array.isArray(payload.data)) throw new Error('History response has no data array'); onHistoryMeta(payload.meta); const bars = payload.data .map((row) => ({ time: row.timestamp, open: row.open, high: row.high, low: row.low, close: row.close, volume: row.volume ?? 0, })) .sort((a, b) => a.time - b.time); onHistoryCallback(bars, { noData: bars.length === 0 }); } catch (error) { onErrorCallback(error instanceof Error ? error.message : 'History request failed'); } }; } ``` The response `meta` carries request-level provenance. Each row also keeps its own `source`, `sourceType`, `quality`, and `isTrueCandle` fields, even though Advanced Charts only needs the OHLCV fields above. Page a longer range with `start` / `end`; one call never returns more than 5,000 candles. ## Live bars from the trades channel Browser clients connect to the WebSocket origin shown in the dashboard, using the `/v1/stream` path. The first frame authenticates; the second subscribes to the venue-native market id: For a direct browser connection, create a dedicated key for this page with only the `read` scope. The key must not carry `trade` or `sql`. Because the page receives it, treat it as disclosed and revocable, and never reuse the server-side history key: ```json { "op": "auth", "apiKey": "pk_live_…" } ``` ```json { "op": "subscribe", "channel": "trades", "venue": "polymarket", "marketId": "" } ``` The server's trade frame is `{ type, venue, marketId, data, ts }`, where `data` is a `MarketTrade`: ```ts type MarketTrade = { id: string; time: string; timestamp: number; type: 'Buy' | 'Sell'; usd: number; outcome: string; outcomeIndex?: number | null; shares: number; price: number; maker: 'polymarket' | 'kalshi' | 'gemini' | 'limitless' | 'myriad' | 'hyperliquid' | 'sxbet'; transactionHash: string; wallet?: string | null; counterparty?: string | null; }; type TradeFrame = { type: 'trade'; venue: string; marketId: string; data: MarketTrade; ts: number; }; ``` This implementation has the three required bar transitions: cold-start from the first trade, extend the current bucket, or roll into a new bucket whose open is the prior close. Trade `shares` accumulate into the forming bar's volume. ```ts function createLiveBars({ streamUrl, streamReadKey, venue, marketId }) { const sockets = new Map(); function subscribeBars( _symbolInfo, resolution, onRealtimeCallback, subscriberUID, _onResetCacheNeededCallback, ) { const bucketMs = BAR_MS[resolution]; if (!bucketMs) throw new Error(`Unsupported resolution: ${resolution}`); sockets.get(subscriberUID)?.close(); const socket = new WebSocket(streamUrl); sockets.set(subscriberUID, socket); let currentBar; socket.addEventListener('open', () => { socket.send(JSON.stringify({ op: 'auth', apiKey: streamReadKey })); socket.send(JSON.stringify({ op: 'subscribe', channel: 'trades', venue, marketId })); }); socket.addEventListener('message', (event) => { let frame: Partial; try { frame = JSON.parse(String(event.data)); } catch { return; } if ( frame.type !== 'trade' || frame.venue !== venue || frame.marketId !== marketId || !frame.data ) { return; } const { timestamp, price, shares } = frame.data; const bucketStart = Math.floor(timestamp / bucketMs) * bucketMs; if (!currentBar) { currentBar = { time: bucketStart, open: price, high: price, low: price, close: price, volume: shares, }; } else if (bucketStart === currentBar.time) { currentBar = { ...currentBar, high: Math.max(currentBar.high, price), low: Math.min(currentBar.low, price), close: price, volume: currentBar.volume + shares, }; } else if (bucketStart > currentBar.time) { const open = currentBar.close; currentBar = { time: bucketStart, open, high: Math.max(open, price), low: Math.min(open, price), close: price, volume: shares, }; } else { return; } onRealtimeCallback({ ...currentBar }); }); } function unsubscribeBars(subscriberUID) { sockets.get(subscriberUID)?.close(); sockets.delete(subscriberUID); } return { subscribeBars, unsubscribeBars }; } ``` For headerless browser connections, the auth frame must be first and arrive within 10 seconds. Never put the API key in the URL. ## Per-venue availability Native `trades` streams are available today for: | Venue | Live-bar source | | ------------- | --------------- | | `polymarket` | Native trades | | `kalshi` | Native trades | | `hyperliquid` | Native trades | | `sxbet` | Native trades | | `myriad` | Native trades | Other venues answer a non-fatal `NOT_SUPPORTED` error and leave the WebSocket open. For those venues, the fallback pattern is the `orderbook` channel: turn each full `snapshot` or `update` into a mid-price tick from the best `bids` and `asks`. That is a book-derived mid, not a trade, and it has no trade volume. See the [Streaming capability notes](/guides/streaming/#wire-protocol-json-text-frames) before choosing that fallback. ## A live trade tape for free The same subscription already carries the full `MarketTrade`. Add another message handler beside the bar builder to keep the newest `N` trades for a tape: ```ts const MAX_TRADES = 50; const tape: MarketTrade[] = []; socket.addEventListener('message', (event) => { const frame = JSON.parse(String(event.data)) as Partial; if (frame.type !== 'trade' || !frame.data) return; tape.unshift(frame.data); if (tape.length > MAX_TRADES) tape.length = MAX_TRADES; renderTradeTape(tape); }); ``` Polymarket's native channel does not expose a wallet or transaction hash. Those fields degrade honestly to `wallet: null` and `transactionHash: ''`; prices, shares, side, and timestamps still drive the bars and tape. --- # Pricing, credits & billing > Predictefy plans, feature access, endpoint-weighted credits, and overage units. Source: https://docs.predictefy.com/guides/credits/ Predictefy meters API actions in **credits**. Each plan includes a monthly allowance, a request limit, API-key and WebSocket-stream caps, and a defined product-access level. :::note[Private beta] **Private beta — sign up free, no invite required.** Every new account starts on the Free plan with 25,000 credits at signup, refilled monthly. Trading ships on every plan, Free included. Every plan meters all usage in credits, and Free's monthly allowance and plan caps are enforced during the beta. Free is sized for evaluation by design; running strategies or a production app consistently is what the paid plans are for. Upgrade when you need more volume. ::: ## Public plans The table below is the settled public plan structure, and it is enforced today. Every new account starts on the Free plan. | Plan | Monthly price | Included credits | Extra 100K | Requests/min | API keys | WS streams | | ---------------------- | ---------------------: | ---------------: | ------------: | -----------: | -------: | ---------: | | Free | $0 | 25,000 | Not available | 60 | 1 | 2 | | Builder | $49 | 500,000 | $11 | 300 | 3 | 20 | | **Pro — Most Popular** | **$249** | **5,000,000** | **$5.50** | **3,000** | **10** | **100** | | Scale | $999 | 25,000,000 | $4.50 | 10,000 | 25 | 500 | | Enterprise | Custom, $2,500 minimum | Custom | Custom | Custom | Custom | Custom | Pro includes a three-day free trial. Enterprise remains contract-only. When an available balance is exhausted, requests stop with `402 INSUFFICIENT_CREDITS`. Upgrade to a paid plan for a larger monthly allowance, or contact [support@predictefy.com](mailto:support@predictefy.com) if a balance looks wrong. Free has no paid overage unit; Builder, Pro, and Scale can add credits in the plan-specific 100K units shown above. ## Feature access :::note[Beta] During the beta, every plan, including Free, gets the Pro-grade feature set. Plan caps and credit balances are enforced now. The table below is the general-availability contract. ::: | Feature | Free | Builder | Pro | Scale | Enterprise | | --------------------- | ------------------------------- | --------- | --------- | --------- | ---------- | | Historical data | 7 days | 12 months | Unlimited | Unlimited | Unlimited | | Cross-market matching | Limited; results may be delayed | Yes | Real-time | Real-time | Real-time | | Price-gap feed | None | Basic | Full | Full | Full | | Arbitrage feed | None | Full | Full | Full | Full | | Bulk endpoints | No | Yes | Yes | Yes | Yes | | Execution (trading) | Yes | Yes | Yes | Yes | Yes | | Commercial use | No | Yes | Yes | Yes | Yes | | Support | Community | Community | Priority | Dedicated | Custom | Commercial use is a documentation and terms entitlement. It is not enforced by an API response. Trading ships on every plan: all API keys carry the `trade` scope by default, with no separate approval or request-access step, and execution is metered in credits like every other action. At general availability, the remaining tier splits above take effect: a Free key calling an arbitrage, price-gap, or bulk endpoint will get `403 PLAN_REQUIRED`. The `/v1/sql` analytical surface is separate from these plans: it requires a dedicated `sql` scope that no self-serve plan grants. SQL access is available on request — [contact support](mailto:support@predictefy.com). ## Action costs | Action | Credits | | ------------------------------------------------------------------------------------------------------------------------ | ------: | | Metadata, search, or price snapshot | 1 | | Account balance, current-period usage, and ledger (`GET /v1/usage`) | 1 | | Latest venue metrics (`/v1/venues/metrics`) | 1 | | Webhook delivery polling | 1 | | Trades, or reference-feed candles (`/api/feeds/…`) | 2 | | Live venue-proxy read — account snapshot, funding requirements or steps, transfer plan, bridge quote, session, or status | 3 | | Order-book snapshot | 5 | | Historical query — venue candles (`fetchOHLCV`) and the raw book tape | 5 | | Existing cross-match lookup | 5 | | Order submission, cancellation, modification, or client-direct acknowledgment | 5 | | Discrepancy qualification | 7 | | Cross-venue comparison | 10 | | Price-gap query | 10 | | Smart-money analytics | 10 | | Cross-venue portfolio valuation (`/v1/portfolio`) | 10 | | Enterprise SQL (`POST /v1/sql`) | 10 | | Arbitrage query | 15 | `ai_cross_match` is a reserved pricing key in the database, but no request route maps to it, so it is not charged today. Matching runs as a background job; stored cross-match lookups use the 5-credit row above. Qualification is priced at 7 rather than the 10 of the comparison class it once shared: one qualification call performs two live order-book reads (3 credits each) plus its own computation. Enterprise SQL is metered like any other action even though the `sql` scope is provisioned separately. Billing checkout, subscribe, and portal calls are metered at **0** credits — they are recorded as usage but buying a plan costs nothing. Bulk requests cost the action's base credits multiplied by `ceil(items / 100)`, with a minimum multiplier of one. For example, 101 order-book snapshots cost 10 credits. Streaming remains metered at **2 credits per connection-minute**, prepaid. The WS-stream value in the plan table limits active logical subscriptions. :::note[Building and validating orders is free] Building, prechecking, and dry-run previewing an order cost nothing — none of those calls consume credits. As the [Trading & execution](/guides/trading/) guide states, build itself is not metered; credits are charged only when an order is actually submitted, cancelled, or modified, at the weight shown in the Action costs table above. Venue trading fees are yours; ours is a flat 5-credit operation — we meter infrastructure, not your trading. ::: ## Programmatic usage and credit headers `GET /v1/usage` returns the authenticated account's post-charge balance, current billing-period request and credit totals grouped by endpoint weight class, and a newest-first credit-ledger page. It accepts `limit` (default 25, maximum 100) and an opaque `cursor`; follow `nextCursor` until it is omitted. The API key always selects its own account — the request has no account-id parameter. Paid accounts use the active subscription mirror's current period. The exact start comes from the matching monthly grant; a zero-grant or trial period falls back to one month before the mirrored period end. Accounts without a current paid period use the UTC calendar month. Usage totals read the existing buffered metering ledger, so they can trail newly served requests by the normal flush interval and can reflect the documented rare restart-window undercount. Every reads-gateway response that reaches a metered route includes `X-Credits-Charged`, reporting the net charge after automatic error refunds. `X-Credits-Remaining` is also included when the existing debit or refund operation already produced the balance. Predictefy does not add a database round trip only to populate that optional header. Health, unmetered, and pre-meter rejections do not carry credit headers. The isolated execution origin meters eligible lifecycle calls but does not add credit headers to its responses. ## Running out When a balance cannot cover an action, the REST API returns `402 INSUFFICIENT_CREDITS`; streaming closes with code `4002`. The action is not served, it costs nothing, and the balance is not pushed below zero. ## What a failed request costs On the reads origin, any metered request answered with a `4xx` or `5xx` error is refunded automatically and costs nothing. On the isolated execution origin, every `5xx` and replay-served duplicate is refunded. For a `4xx`, only `SPEND_CAP_EXCEEDED`, `ARTIFACT_VERSION_CONFLICT`, and `VENUE_NOT_SUPPORTED` are refunded; any other metered execution `4xx` keeps the 5-credit order-lifecycle charge. If a refund write itself fails, the original charge stands. A refunded request records zero credits in your usage. Requests we reject before metering them at all are never charged: a missing or invalid API key, a missing scope, a plan that does not include the endpoint, a blocked region, a missing `Idempotency-Key`, and rate limiting. One related behavior to know: after 3 consecutive `401 VENUE_CREDENTIAL_REJECTED` responses on a venue, the platform fast-fails further submits to that venue for 15 minutes instead of re-asking the venue. The three venue-rejected `401` responses that trip the breaker each keep the 5-credit execution charge. Once the breaker is open, further fast-fails happen before metering and are free; fix the credential and retry after the window. A replayed request served from a stored result (`Idempotency-Replay: true`) also records zero credits — you pay for an action once, not once per retry. ## Billing Self-serve plan and eligible overage checkout use Stripe; Predictefy does not receive card details. Subscription management opens Stripe's hosted customer portal. Enterprise continues to require a contract. The developer dashboard's `/usage` page shows the same balance and accounting data. Server-side clients can read it through `GET /v1/usage`. --- # Venue coverage > The 16 served venues and exactly what each one supports — real vs reconstructed order books, public trades tapes, and the implemented dark venues. Source: https://docs.predictefy.com/reference/venues/ Predictefy serves **16 venues**, including PredictStreet and Novig, through one normalized API. Use any served venue id below as the `{exchange}` path segment, or `router` for the all-served-venues union on the list verbs. ## Dark venues > **Dark venue.** Smarkets is implemented but not served on this deployment: every `/api/smarkets/…` request returns > `404 EXCHANGE_NOT_AVAILABLE`, router fan-outs exclude it, and it is not counted among the served venues. It returns > when a commercial API agreement is in place. > **Dark venue.** PredictIt is implemented as a data-only read path but is not served on this deployment: > every `/api/predictit/…` request returns `404 EXCHANGE_NOT_AVAILABLE`, router fan-outs exclude it, and it > is not counted among the served venues. No production lane walks it. It returns when a commercial data > licence is in place. Not every venue exposes the same raw data, and we do not paper over the differences — the table below is mirrored from the capability flags the API itself returns (`capabilities` on markets, `sourceMetadata.synthetic` on order books). The `/has` `buildOrder` and `submitOrder` flags report implemented lanes, not deployment arming; use `GET /v1/exec/venues` for the armed hosted lanes. Market `volume24h` and `liquidity` are always present but nullable. For 24-hour volume, `null` no longer means only "the venue does not publish it": it means Predictefy has no accepted value for that market now. A derived mechanism may still be warming or may have stale, gapped, unavailable, or restated evidence. `0` is reserved for a real venue-published zero or a complete derived window that honestly computes to zero. ## 24-hour volume `GET /api/{exchange}/has` exposes the venue-level mechanism at `volume24h`. `venue` uses a venue-published 24-hour value. `derived_cumulative` differences typed cumulative snapshots without silently converting their units. `derived_tape` sums accepted canonical fills only after the whole trailing window passes its coverage gate. `false` means no honest mechanism is registered. | Venue id | `has.volume24h` | Unit | Cumulative input | | --- | --- | --- | --- | | `polymarket` | `venue` | `USD` | — | | `kalshi` | `venue` | `CONTRACTS` | — | | `smarkets` (dark) | `derived_cumulative` | `GBP` | `volume` | | `opinion` | `venue` | `USD` | — | | `hyperliquid` | `venue` | `USDC` | — | | `limitless` | `derived_cumulative` | `USDC` | `volumeFormatted ?? volume / 1e6` | | `polymarket_us` | `false` | — | — | | `sxbet` | `derived_tape` | `USD` | — | | `myriad` | `venue` | `USD` | — | | `gemini` | `venue` | `CONTRACTS` | — | | `rain` | `derived_cumulative` | `COLLATERAL` | `totalVolume` | | `predictfun` | `venue` | `USD` | — | | `pascal` | `venue` | `USD` | — | | `xo` | `false` | — | — | | `pred` | `derived_cumulative` | `USD` | `volume` | | `predictstreet` | `venue` | `USD` | — | | `novig` | `derived_cumulative` | `CASH` | `volume` | When present, market `volume24hSource` is `venue`, `derived_cumulative`, or `derived_tape` and describes only that metric; the separate market `provenance.source` still says which implementation served the object. A null market can carry `venue` when that row's venue field is unavailable, or a derived source while no value is accepted. The current public market DTO does not expose the internal warm-up/degradation status. Opinion child rows are also null because the parent volume is never copied or apportioned to children. Polymarket US and XO have `volume24hSource` absent because their registry mechanism is `false`. Event and canonical-category sums expose `volume24hSource: { sources: [...], partial: boolean }`. `sources` is the distinct set from non-null contributing markets; `partial` is true when any member volume is null. Cluster members carry the current market's value and source. A `/has` mechanism is capability truth, not proof that an individual market is warmed or currently available. As verified live on 2026-09-02, SX Bet markets now carry `volume24h` with `volume24hSource: derived_tape`. | Venue id | Order book | Real depth (`depth`) | Public trades tape | | --------------- | ---------------------------------- | :------------------: | :----------------: | | `polymarket` | Real CLOB | yes | yes | | `kalshi` | Real CLOB | yes | yes | | `smarkets` (dark) | Real CLOB (delayed quotes) | yes | yes — 5 fills | | `opinion` | Real CLOB | yes | no | | `hyperliquid` | Real CLOB | yes | yes | | `limitless` | Real CLOB (CLOB markets only) | per-market | yes | | `polymarket_us` | Real CLOB (~30s CDN cache) | yes | no | | `sxbet` | Real CLOB | yes — degraded | yes | | `myriad` | Dual (native CLOB / synthetic AMM) | per-market | yes | | `gemini` | Real CLOB (keyless sized depth) | yes | yes | | `rain` | Emulated (on-chain price, 1 level) | no | no | | `predictfun` | Real CTF/CLOB | yes | yes | | `pascal` | Real CLOB (Solana) | yes | yes | | `xo` | Real CLOB (own rollup) | yes | no — see below | | `pred` | Real CLOB (Base) | yes | no | | `predictstreet` | Real CLOB (ADI Chain) | yes | yes | | `novig` | Real CLOB (bids-only complement) | yes | no | For Myriad, Order Book markets return native CLOB depth; AMM markets return a synthetic top-of-book. :::note[Novig] Novig is a CFTC-regulated US sports prediction exchange (`api.novig.us`). Reads require OAuth 2.0 Client Credentials token minting (`NOVIG_CLIENT_ID`/`NOVIG_CLIENT_SECRET`). Catalog walks ~22 sports leagues, and order books expose a bids-only ladder with the offer side derived as the exact 1-p binary complement. There is no public market-wide trades tape. The hosted execution lane is armed for build, submit, and cancel. ::: :::note[PredictStreet] PredictStreet serves a keyless paged catalog, per-market detail, native two-sided outcome books, and the venue's keyless public `/trades` tape. Missing venue prices remain `null`; catalog prices are venue last-trade values and no complement outcome is fabricated. Its server-built VAULT execution lane is armed for build and submit, with no hosted cancel. Predict Street Limited states it operates under Gibraltar licence 167, and FIFA names it the official prediction-market partner of the FIFA World Cup 2026. Venue and jurisdiction eligibility remain the operator's and integrator's compliance responsibility. ::: :::note[Pascal, XO, and PRED execution — three different states] These three used to share one "source-ready but off" sentence. They no longer share a state, so read them one at a time: - **Pascal — armed.** Server-built place and cancel permits are live in production (armed 2026-08-12; fleet-verified 2026-08-15). It also serves a **credential-free status refresh** — Pascal's account reads are keyless, so the background reconciler polls it too — and a public trades tape. - **XO Market — armed: build + submit + cancel (rebuilt and re-armed 2026-08-18).** The order builder now signs the venue's current 13-field contract against the on-chain-verified exchange; cancel and the client-credentialed status refresh are live. Nothing has been submitted to XO yet, so the first live submit is the confirmation checkpoint. Check `GET /v1/exec/venues` for the live row. - **PRED — production-darked (2026-08-13).** The venue confirmed it refuses platform-key submits for caller-owned Safe makers, so `PRED_EXCHANGE_ADDRESSES` was emptied. That allowlist registers the **whole** lane, so PRED is absent from `GET /v1/exec/venues` and every PRED execution route, **build included**, answers `404 VENUE_NOT_SUPPORTED`. Client-side signing helpers stay valid for callers with their own venue arrangement, and PRED never had a cancel lane at all. None of the three has a hosted account or funding lane, so those verbs answer an honest `NOT_SUPPORTED`. Pascal does have proven venue-history coverage. PRED restricts the US, UK, France, Ontario, Singapore, Poland, Thailand, and Taiwan and prohibits location masking. Venue-side access limits and the trading guide's owner/compliance gates still apply. ::: :::note[XO has no public trades tape — that is the venue, not a gap] XO's own trade endpoints are **caller-scoped**: they return the authenticated caller's own fills, not a market-wide tape. There is therefore no public XO tape in existence to normalize, so `fetchTrades` answers an honest `501 NOT_SUPPORTED` rather than synthesizing one from the book. This is a property of the venue's API, not a missing Predictefy integration, and it will not change by arming anything on our side. ::: :::caution[SX Bet order books degraded] SX Bet has a real CLOB, but hosted `fetchOrderBook` can currently return an empty book. That book issue does not change SX Bet's separate tape, OHLCV, WebSocket capture, or `fetchTrades` capabilities; their runtime availability remains independently capability-qualified. ::: ## Venue size metrics `GET /v1/venues/metrics` returns the latest stored reporting date for each available venue and metric pair — venue-level size and liquidity figures, distinct from the per-market `volume24h` above. ```sh curl -s "$PREDICTEFY_API_URL/v1/venues/metrics?venue=polymarket" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` `venue` is optional; omit it for every tracked venue. **Nothing is estimated.** A missing venue or a missing metric means the configured source does not track it, and an untracked venue returns an empty object rather than a zero. The route is gated by `READS_ENABLE_VENUE_METRICS` and answers 404 where it is not enabled. It costs one credit; see [Credits & billing](/guides/credits/). ## Reading the flags - **`capabilities.depth`** (market-level): a real, executable-quality price-level order book exists for this venue. - **`sourceMetadata.synthetic`** (per order-book response): `true` means the returned book was _reconstructed_ — a single top-of-book level synthesized from a spot price, AMM pool, parimutuel odds, or P2P offer odds. `false` means a real CLOB depth snapshot. - An **empty real book** is a valid "no liquidity right now" state — check the levels, not just the flags. - **`capabilities.trade`** is `false` on every venue by design. It describes the legacy unified `trade` verb on the data API, which Predictefy does not expose. Live execution is served separately for supported venues; see [Trading & execution](/guides/trading/). ## Trades tape `fetchTrades` is a **recent public tape** read (a snapshot of recent trades), not a time-ranged history query. The ten served venues marked "yes" above expose a verified public tape; the Smarkets row records an implemented dark capability, and every other served venue answers an honest `501 NOT_SUPPORTED` rather than synthesizing trades. `pascal`'s tape carries no time parameters at the venue, so `start`/`end` are applied after the fetch — an older window simply returns fewer rows, never invented ones. `smarkets` (dark — not served) is the same shape with a harder ceiling: the venue accepts no tape parameters at all and returns **at most five fills per outcome**, so `limit` is applied locally and a window older than those five reachable fills returns fewer rows rather than a fabricated one. SX Bet's `fetchOrderBook` degradation is documented above. Tape/OHLCV, Trader Intelligence, and public account capabilities are independent of that book issue, while hosted execution and official history remain unsupported. Runtime availability is not inferred from capability alone. ## Price change windows & tape coverage Market outcomes served from the hot catalog snapshot expose **eight price-change windows** alongside per-outcome tape coverage metadata: - `priceChange1m`, `priceChange5m`, `priceChange15m` — computed from top-of-book order-book ticks (`tob_ticks.last`) with zero freshness tolerance. - `priceChange1h`, `priceChange6h`, `priceChange24h` — computed from 1m stored candle history (1h fallback). - `priceChange7d`, `priceChange30d` — computed from official venue candles where available (1h then 1d fallback, then 1m). Every window key is **always present** on snapshot-enriched market outcomes (a numeric probability delta or `null` when reference history is insufficient; `null` never means zero). Each outcome includes a `coverage` object (or `null` when uncomputed) detailing tape mechanism (`ws-lossless`, `ws-top20`, `rest-adaptive`, `rest-top-of-book`, `synthetic-spot`), `tapeSince`, `effectiveIntervalMs`, `gaps24h`, and `lastGapAt`. ## Freshness notes - Every response carries `asOf` (when the data was snapshotted) and `provenance.source`. - `polymarket_us` reads are served through a ~30-second public CDN cache: `asOf` is the fetch time, so treat its book data with a ≥30s freshness tolerance (the venue's own transaction time is preserved in the raw metadata). - `smarkets` (dark — not served) public quotes are delayed by the venue. ## Sandbox A `kalshi-demo` sandbox venue (Kalshi's paper-trading environment) exists for integration testing. It serves **paper data, not real market data** — it is excluded from the public venue list, `router` results, and all cross-venue outputs. --- # Trader Intelligence API > Live wallet-attributed public activity, venue-scoped profiles, versioned scores, and the scored-trade feed. Source: https://docs.predictefy.com/reference/trader-intelligence/ :::note[Live, capability-qualified] Trader Intelligence is publicly available. Scored-trade coverage includes Polymarket, Limitless, Myriad, Hyperliquid, Predict.fun, Pascal, XO, PRED, and Rain. Opinion is lookup-only. The table below reports each venue's live `has` contract; unsupported venue concepts return `TRADERS_UNSUPPORTED` instead of synthetic data. ::: > **Dark venue.** Smarkets is implemented but not served on this deployment: every `/api/smarkets/…` request returns > `404 EXCHANGE_NOT_AVAILABLE`, router fan-outs exclude it, and it is not counted among the served venues. It returns > when a commercial API agreement is in place. Trader Intelligence organizes public venue-published/on-chain activity by wallet and venue. Scores are versioned informational signals, not financial advice, and nothing on this page is a recommendation. Wallets remain venue-scoped; Predictefy does not claim that addresses on different venues belong to the same person. All endpoints are authenticated, metered `GET` requests. List endpoints default to `limit=20` and cap it at 100. ## Endpoints ### Market trader trades `GET /v1/traders/{venue}/markets/{marketId}/trades` | Parameter | Required | Meaning | | ---------- | :------: | --------------------------------------------- | | `venue` | yes | One supported Trader Intelligence venue id. | | `marketId` | yes | Venue-native market id. | | `limit` | no | 1–100 rows. | | `cursor` | no | Opaque venue/keyset cursor from `nextCursor`. | Rows contain `venue`, `marketId`, `outcomeId`, `tradeId`, `ts`, `wallet`, optional `displayName`, `side`, `price`, `amount`, and `usdSize`. Nullable fields stay `null` when the venue payload cannot prove them. ### Market holders `GET /v1/traders/{venue}/markets/{marketId}/holders` Parameters: `venue`, `marketId`, and optional `limit`. Rows contain `wallet`, optional `displayName`, `outcomeId`, `shares`, and nullable `usdValue`. Venues without a public holder/outcome concept return `TRADERS_UNSUPPORTED`. ### Venue leaderboard `GET /v1/traders/{venue}/leaderboard` | Parameter | Required | Values | | --------- | :------: | ------------------------------------------------- | | `by` | no | `profit`, `volume`, or `score`; default `profit`. | | `window` | no | `day`, `week`, `month`, or `all`; default `all`. | | `limit` | no | 1–100 rows. | Native profit/volume rows contain `venue`, `wallet`, optional `displayName`, `rank`, `window`, nullable `profitUsd`, and nullable `volumeUsd`. `by=score` uses Predictefy scores and instead includes `score`, `scoreVersion`, `factors`, `category`, `stats`, `refreshedAt`, `refreshState`, `asOf`, and `provenance`. Wallet `factors` contain the versioned `trackRecord`, `experience`, `scale`, and `discipline` components when a `w1` score is available. The exact score note is: > Score ranking uses current stored scores and is all-time only; any other window is rejected rather than silently ignored. ### Cross-venue score leaderboard `GET /v1/traders/leaderboard` This endpoint supports `by=score` only. `window` may be omitted to use its `all` default or set explicitly to `all`; every other value is rejected with HTTP 400. `limit` is optional and accepts 1–100 rows. Profit and volume are venue-native and are never merged into a synthetic ranking. Rows use the score-leaderboard shape above and remain venue-tagged. The response carries these exact honesty notes: > Wallets are venue-scoped; the cross-venue leaderboard interleaves venue-tagged entries without claiming same-person identity. > Score ranking uses current stored scores and is all-time only; any other window is rejected rather than silently ignored. ### Wallet profile `GET /v1/traders/{venue}/wallets/{addr}` Profiles contain `venue`, `wallet`, optional `displayName`, `score`, `scoreVersion`, `factors`, `category`, `stats`, `refreshedAt`, `refreshState`, `asOf`, and `provenance`. `stats` contains `walletAgeDays`, `marketsTraded`, `totalVolumeUsd`, `winRate`, `realizedPnlUsd`, `firstSeen`, `lastSeen`, and `depositFirstAt`. A live fill-in can return `score`, `scoreVersion`, `factors`, and `category` as `null` until Predictefy scores that wallet. It says so in `note`; the API does not invent a score from incomplete venue data. ### Wallet trades `GET /v1/traders/{venue}/wallets/{addr}/trades` Parameters: `venue`, `addr`, optional `limit`, and optional `cursor`. The list uses the same wallet-attributed trade fields as the market tape. A venue without keyless wallet history returns `TRADERS_UNSUPPORTED`. ### Smart-money feed `GET /v1/traders/smart-money` The route name is part of the API; the response is an informational scored-trade feed, not a recommendation. | Parameter | Required | Meaning | | ---------- | :------: | ------------------------------------------------ | | `venue` | no | One Trader Intelligence venue. | | `minScore` | no | Minimum trade score, 0–100. | | `market` | no | Exact market id. | | `wallet` | no | Exact venue-scoped wallet. | | `category` | no | `bot`, `whale`, `smart`, `fresh`, or `fish`. | | `window` | no | `day`, `week`, `month`, or `all`; default `all`. | | `limit` | no | 1–100 rows. | | `cursor` | no | Opaque `(ts, tradeId)` keyset cursor. | Rows add `tradeScore`, `tradeFactors`, `scoreVersion`, `walletScoreAtTrade`, and `categoryAtTrade` to the normal trader-trade fields. Version `t1` trade factors are `walletScore`, `size`, `entry`, and `timing`. The response carries these exact honesty notes: > window=all means since lane launch; feed coverage begins at deploy, no backfill. ## Error honesty An unknown venue or unsupported venue/verb returns HTTP 400: ```json { "success": false, "error": { "code": "TRADERS_UNSUPPORTED", "message": "market holders are unsupported for hyperliquid", "retryable": false } } ``` An unknown wallet returns `404 TRADER_NOT_FOUND`. If Trader Intelligence is unavailable, `/v1/traders/*` returns 404. ## Venue capabilities Hyperliquid market selection follows the active catalog and refreshes periodically as that catalog changes. A listed capability is not a claim that every venue currently has collected rows: scored-trade and smart-money coverage requires activity from that venue. The five columns mirror `has.traderTrades`, `has.holders`, `has.leaderboard`, `has.walletProfile`, and `has.smartMoney`. Smart-money capability follows trader-trade capability. The source column names the venue definition behind `TRADER_CLIENTS`; `traderCapabilities` supplies the all-false fallback for product venues without one. | Venue | Trader trades | Holders | Leaderboard | Wallet profile | Scored-trade feed | Capability source | | --------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | :------------: | :---------------: | ----------------------------- | | `polymarket` | yes | yes | yes | yes | yes | `polymarketTraders` | | `kalshi` | no | no | no | no | no | `traderCapabilities` fallback | | `smarkets` (dark) | no | no | no | no | no | `traderCapabilities` fallback | | `opinion` | no | no | no | yes | no | `opinionTraders` | | `myriad` | yes | yes | no | yes | yes | `myriadTraders` | | `gemini` | no | no | no | no | no | `traderCapabilities` fallback | | `hyperliquid` | yes | no | yes | yes | yes | `hyperliquidTraders` | | `limitless` | yes | yes | `by=volume` + `window=all` only | yes | yes | `limitlessTraders` | | `polymarket_us` | no | no | no | no | no | `traderCapabilities` fallback | | `rain` | yes | no | no | no | yes | `rainTraders` | | `predictfun` | yes | no | yes | yes | yes | `predictfunTraders` | | `sxbet` | no | no | no | no | no | `sxbetTraders` | | `pascal` | yes | no | no | no | yes | `pascalTraders` | | `xo` | yes | no | no | no | yes | `xoTraders` | | `pred` | yes | no | no | no | yes | `predTraders` | | `predictstreet` | no | no | no | no | no | `traderCapabilities` fallback | | `novig` | no | no | no | no | no | `traderCapabilities` fallback | Opinion is a lookup-only venue (no public per-market tape, so no scored-trade or smart-money coverage). Kalshi, Smarkets (dark — not served), Gemini, Polymarket US, PredictStreet, and Novig do not register a Trader Intelligence client, so all five capability fields are false. Venue-specific limits matter: - PredictFun ranks venue points only. `by`/sort has no venue effect, only `all` exists, and the board contains no profit or volume figures. Position pages do not prove lifetime totals, and match collateral is unknown, so `usdSize` is null. - Limitless has no keyless wallet history, and its board is all-time volume only: the leaderboard serves `by=volume` with `window=all` and answers `400` for anything else, including this reference's own `by=profit` default. This is a fixed venue contract, not an outage. - Myriad serves trader-trade and holder reads but has no board. Sizes are token-denominated, so USD fields are null. - Hyperliquid has no holders. Its tape is push-based, and profile totals cover only the recent fills window returned by the venue. - SX Bet reports no trader-intelligence capability on the shipped V3 API: its default tape carries no bettor identity, so `has.traderTrades` and every flag derived from it are `false`. The client advertises trader trades only when `SXBET_API_VERSION=v2` selects the retired sandbox API, where `side` is null and `usdSize` exists only for SX USDC. - Opinion wallet lookups require a venue API key held server-side; the venue hides order ids for privacy. Wallet trades paginate with an opaque cursor. ## SDK examples TypeScript uses venue subclients for venue lookups and root methods for merged score/feed reads: ```ts const tape = await client.polymarket.fetchTraderTrades(conditionId, { limit: 25 }); const holders = await client.limitless.fetchHolders(marketSlug, { limit: 10 }); const board = await client.hyperliquid.fetchLeaderboard({ by: 'profit', window: 'week', limit: 20, }); const profile = await client.polymarket.fetchWalletProfile(wallet); const history = await client.sxbet.fetchWalletTrades(wallet, { limit: 25 }); const feed = await client.fetchSmartMoney({ venue: 'polymarket', minScore: 70, window: 'week' }); const top = await client.fetchTopTraders({ by: 'score', window: 'all', limit: 20 }); ``` Python exposes the same surface in snake case: ```python tape = client.polymarket.fetch_trader_trades(condition_id, {"limit": 25}) holders = client.limitless.fetch_holders(market_slug, {"limit": 10}) board = client.hyperliquid.fetch_leaderboard( {"by": "profit", "window": "week", "limit": 20} ) profile = client.polymarket.fetch_wallet_profile(wallet) history = client.sxbet.fetch_wallet_trades(wallet, {"limit": 25}) feed = client.fetch_smart_money( {"venue": "polymarket", "minScore": 70, "window": "week"} ) top = client.fetch_top_traders({"by": "score", "window": "all", "limit": 20}) ``` --- # AI agents > Connect Predictefy to a chat client or build an agent on the API. Source: https://docs.predictefy.com/guides/agents/ **Using an LLM (Claude, Cursor, ChatGPT)?** Predictefy ships an MCP server exposing **43 tools** — **33 read, intelligence, and platform tools** for normalized markets, order books, history, cross-venue intelligence, and trader analytics across **16 served prediction-market venues**, plus **10 guardrailed execution tools** for a non-custodial trade surface. One-line install: `npx -y @predictefy/mcp` (get a free API key at [portal.predictefy.com/keys](https://portal.predictefy.com/keys)). No install? Paste `https://docs.predictefy.com/llms.txt` into your LLM — it is a self-contained integration brief. Building an autonomous agent? Start with the [TypeScript SDK](/guides/sdk/) or [Python SDK](/guides/python-sdk/) and the [published agent skill](/.well-known/agent-skills/predictefy/skill.md). **Pick your route below.** ## Which piece is which | Layer | Asset | Reach for it when | | ------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | | **Context** | [`llms.txt`](/llms.txt) | You want a curated, self-contained integration brief to paste into an LLM. | | **Context** | [`llms-full.txt`](/llms-full.txt) | All 56 authored pages plus the generated API operation catalogue. | | **Context** | Per-page `.md` twins | Clean Markdown for any of the 56 authored pages; API pages have no twin. | | **Tools** | [`@predictefy/mcp`](/guides/mcp/) | A chat client or agent needs callable Predictefy tools at runtime. | | **Tools** | [REST API](/api/) | You need the normalized HTTP contract directly. | | **Tools** | [`@predictefy/sdk`](/guides/sdk/) | You are building a typed TypeScript agent. | | **Tools** | [`predictefy` on PyPI](/guides/python-sdk/) | You are building a Python research, automation, or backtesting agent. | | **Tools** | [`@predictefy/cli`](/guides/cli/) | An agent or operator needs the same API from a shell. | | **Discovery** | [`/.well-known/agent-card.json`](/.well-known/agent-card.json) | An A2A platform needs Predictefy's identity and capability card. | | **Discovery** | [`/.well-known/agent-skills/`](/.well-known/agent-skills/index.json) | A platform needs the published skill URL and integrity digest. | | **Discovery** | [`/.well-known/mcp/server-card.json`](/.well-known/mcp/server-card.json) | An MCP directory or platform builder needs the local stdio install contract. | | **Discovery** | The `llms.txt` convention | A crawler checks the docs origin for an LLM-readable starting point. | | **Discovery** | [`sitemap-index.xml`](/sitemap-index.xml) | A crawler needs the complete index of docs URLs. | ## Route A — use Predictefy from your chat client Start read-first. Use the MCP server for research, market screening, cross-venue comparison, and Trader Intelligence. Treat price gaps as **indicative price discrepancies** unless every live executability gate passes. Create a free key at [portal.predictefy.com/keys](https://portal.predictefy.com/keys), then choose your client. ### Claude Code ```sh claude mcp add predictefy -e PREDICTEFY_API_KEY=pk_live_your_key_here -- npx -y @predictefy/mcp ``` ### Claude Desktop Add this server to `claude_desktop_config.json`: ```json { "mcpServers": { "predictefy": { "command": "npx", "args": ["-y", "@predictefy/mcp"], "env": { "PREDICTEFY_API_KEY": "pk_live_your_key_here" } } } } ``` ### Cursor Create `.cursor/mcp.json` in your project: ```json { "mcpServers": { "predictefy": { "command": "npx", "args": ["-y", "@predictefy/mcp"], "env": { "PREDICTEFY_API_KEY": "pk_live_your_key_here" } } } } ``` ### VS Code Create `.vscode/mcp.json` in your workspace: ```json { "servers": { "predictefy": { "type": "stdio", "command": "npx", "args": ["-y", "@predictefy/mcp"], "env": { "PREDICTEFY_API_KEY": "pk_live_your_key_here" } } } } ``` ### Advanced: trading from chat The **10 execution tools are registered by default**. Set `MCP_ENABLE_TRADE=false` to disable them and run a server that cannot trade or return a collateral-moving transaction to sign. No tool both builds and signs an order: build operations return unsigned artifacts, and signing stays client-side. Server-side spend caps still apply — by default, **100 USD per order** and **1,000 USD per API key over a rolling 24 hours**. Read [Trading & execution](/guides/trading/) before acting; it records the current per-venue reality and guardrails. ## Route B — build an agent on the API Choose context at the granularity your agent needs: - Paste [`llms.txt`](/llms.txt) when the model needs a compact integration brief in-context. - Load [`llms-full.txt`](/llms-full.txt) when you need all 56 authored pages plus the complete generated API operation catalogue. - For one authored page, use `/index.md`, `/quickstart.md`, or replace the trailing slash in a `/guides/**/` or `/reference/**/` URL with `.md`. - Do not append `.md` to `/api/` or `/api/operations/**/`: the generated API reference has no per-page markdown twins. Use [`llms-full.txt`](/llms-full.txt) for its operation catalogue. ### Install the published agent skill The canonical skill is served at `https://docs.predictefy.com/.well-known/agent-skills/predictefy/skill.md`. For Claude Code, install it at `~/.claude/skills/predictefy/SKILL.md`: ```sh mkdir -p ~/.claude/skills/predictefy curl -fsSL https://docs.predictefy.com/.well-known/agent-skills/predictefy/skill.md \ -o ~/.claude/skills/predictefy/SKILL.md ``` For runtimes using the [Agent Skills](https://agentskills.io) ecosystem, install the same file in the shared user directory: ```sh mkdir -p ~/.agents/skills/predictefy curl -fsSL https://docs.predictefy.com/.well-known/agent-skills/predictefy/skill.md \ -o ~/.agents/skills/predictefy/SKILL.md ``` Other runtimes can fetch the raw `.well-known` URL directly. The [skill discovery index](/.well-known/agent-skills/index.json) publishes its URL and digest. ### Discovery cards for platform builders - [`agent-card.json`](/.well-known/agent-card.json) advertises the A2A identity and capabilities. - [`agent-skills/index.json`](/.well-known/agent-skills/index.json) lists the published skill and its integrity digest. - [`mcp/server-card.json`](/.well-known/mcp/server-card.json) describes the local stdio MCP package and install contract; it does not advertise a hosted MCP endpoint. ### Build with the API, SDKs, or CLI - Use the [REST API](/api/) when your agent owns its HTTP client. - Use [`@predictefy/sdk`](/guides/sdk/) for TypeScript or [`predictefy` on PyPI](/guides/python-sdk/) for Python. - Use [`@predictefy/cli`](/guides/cli/), **the terminal and AI-agent client**, when shell commands are the right integration boundary. :::note[Beta releases] These packages are published on beta lines. Pin exact versions rather than tracking `latest`. ::: ```sh npm install @predictefy/sdk pip install predictefy npm install --global @predictefy/cli ``` --- # Best practices > Correctness, cost, and resilience rules that follow from how the API actually behaves. Source: https://docs.predictefy.com/guides/best-practices/ Each rule below follows from something documented elsewhere. The reasoning is included so you can decide when it does not apply to you. ## Correctness ### Check capability before assuming support Read the venue's `has` map or the `capabilities` field rather than discovering a gap through a caught exception. Seventeen venues genuinely differ, and `NOT_SUPPORTED` is an answer rather than a fault. See [Capability-honest data](/guides/honest-data/). ### Never render a synthetic book as depth Venues without a real order book return a book-shaped response labelled synthetic. It is a faithful representation of price and is not orders anyone can fill against. Feeding it into a sizing calculation produces a number with no meaning. ### Do not equate similar markets across venues Matched clusters carry a similarity score, not an identity claim, and two venues can settle what looks like the same question differently. A price gap is an [indicative price discrepancy](/guides/cross-venue/) until every executable gate passes. ### Use `asOf`, not your own clock A timestamp you generate on receipt describes your request. `asOf` describes the data. ### Persist `marketId`, not `slug` Slugs derive from titles and change when a venue renames a market. See [Identifiers](/guides/market-ids/). ## Cost ### Follow cursors instead of parallelising offsets `nextCursor` freezes the catalog snapshot from page one, so a long walk never skips or double-counts rows that move while you page. Parallel offset pages give up that guarantee and spend the rate allowance faster. Cursors do not live forever: each one carries a TTL, and on live-published surfaces such as arbitrage it is also pinned to the publish it started on. Either way a stale cursor returns `400 VALIDATION_ERROR` with `Cursor has expired` — treat that as the signal to restart the walk from page one, not as an error to retry. ### Batch where a batch verb exists `fetchOrderBooks` takes many outcomes in one request. It is priced by items, so it saves rate allowance rather than credits. ### Stream rather than poll A [WebSocket subscription](/guides/streaming/) delivers book and trade updates without consuming the request window. Streaming is metered at 2 credits per connection-minute; polling also consumes the request allowance and the endpoint's credits. ### Cache what does not move Capability maps and [taxonomy](/guides/categories-tags/) change rarely. Re-fetching them per query is pure overhead. ### Request only the window you need History is priced per query regardless of range, but a narrower window returns faster and is less likely to hit a lane bound. ## Resilience ### Branch on `code` and `retryable`, not on HTTP status Several codes share a status, and `retryable` is the field that answers the question you are actually asking. See [Errors](/guides/errors/). ### Retry only when `retryable` is true, with exponential backoff and jitter That includes retryable `502` upstream and relay failures as well as `429` and `503`. Errors marked non-retryable fail identically on the second attempt. `INSUFFICIENT_CREDITS` in particular will not resolve by retrying. ### Never auto-retry a write :::caution[A write that may have reached the venue must not be replayed] A submit, cancel, or modify that may have reached the venue must not be replayed by a client library. Retry deliberately, with an `Idempotency-Key`. See [Trading & execution](/guides/trading/). ::: ### Expect empty results to be legitimate A market with no trades today returns an empty tape. `fetchSeries` on a venue without the concept returns an empty list. Neither is an error. ### Handle 503 on billing-session writes while other routes keep flowing When rate limiting is degraded, only the zero-credit checkout, subscribe, and portal routes fail closed. Reads and other writes are not in that special fail-closed set. The narrow guard prevents unlimited billing-session creation while the limiter is unavailable. ## Keys and secrets ### Send keys in the `Authorization` header, never in a query string Query strings end up in request logs, browser history, and referrer headers. ### Keep keys server-side The raw key is shown once and stored only as a hash. A key that reaches a browser bundle is disclosed permanently and must be revoked. Predictefy REST sends no CORS headers by design, so [proxy browser history calls through your server](/guides/tradingview-charts/#proxy-history-through-your-server). [WebSocket first-frame authentication](/reference/streaming/#authentication) is the one sanctioned browser use. Give that page a dedicated `read`-only key with no `trade` or `sql` scope, treat it as disclosed, and be prepared to revoke it. ### Use separate keys per environment Rate limits and credit spend are tracked per key, so one key across staging and production makes both untraceable and lets a test loop exhaust a production allowance. --- # Categories & tags > The canonical taxonomy layered over venue-native labels, and how counts are scoped. Source: https://docs.predictefy.com/guides/categories-tags/ Venues label markets in their own vocabularies. Predictefy maps those onto a single canonical taxonomy so one filter works everywhere, and exposes it through two verbs. ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchCategories" \ -H "Authorization: Bearer pk_live_YOUR_KEY" curl -s "$PREDICTEFY_API_URL/api/polymarket/fetchTags?category=politics" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` Both are catalog reads and cost 1 credit. ## How counts are scoped Both verbs return served-market counts alongside each label, and the scope of those counts depends on the exchange segment: - **A venue** (`/api/polymarket/…`) scopes counts to that venue alone. - **`router`** returns cross-venue counts across the whole catalog. `fetchCategories` returns **every** canonical category in its defined sort order, including categories that currently have no served markets on the venue you asked about. A zero count is information — it says the category exists and this venue has nothing in it right now — not an omission. `fetchTags` returns only tags that are currently active. ## Filtering with them The canonical category is accepted as a filter on the list verbs, so taxonomy and catalog reads compose: ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchMarkets?category=politics&status=active&limit=20" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` `fetchTags` takes an optional `category` to narrow the tag list to one category, which is the usual way to build a two-level filter UI without pulling the whole tag set. ## What the taxonomy is not It is a **classification of markets**, not a claim about venue capability. A category returning markets on a venue says nothing about whether that venue has an order book, a trades tape, or a history lane — those are answered by the venue's `has` map and [Venue coverage](/reference/venues/). Because the mapping is applied by Predictefy rather than published by the venues, a market's canonical category may differ from the label shown on the venue's own site. The venue-native label is preserved on the market record; the canonical one exists so a single query works across all of them. ## Caching The taxonomy changes rarely. Re-fetching it on every request is the most common avoidable cost in an integration — cache the category list for the lifetime of a process and refresh it on a schedule rather than per query. See [Rate limits](/guides/rate-limits/). --- # CLI > The predictefy terminal client — every command, the output contract, and the trading gates. Source: https://docs.predictefy.com/guides/cli/ `@predictefy/cli` installs the `predictefy` binary, the terminal and AI-agent client for the Predictefy API. It is a thin wrapper over the [TypeScript SDK](/guides/sdk/): one command shape for every venue, with `--venue ` choosing the lane. Reads and normalization stay server-side; signing stays on your machine. :::note[Beta release] Published on npm as **`1.0.0-beta.5`**. Pin an exact version while the beta line moves. ::: ## Install & authentication From this workspace: ```bash pnpm install pnpm --filter @predictefy/cli... build pnpm --filter @predictefy/cli exec predictefy --help ``` Or install the binary globally: ```bash npm install --global @predictefy/cli predictefy --help ``` Node `>=20.19 <21 || >=22.12` is required. Authenticate through the environment: ```bash export PREDICTEFY_API_KEY='pk_live_…' predictefy whoami ``` Or through a config file: ```bash predictefy config init # writes a commented ~/.predictefy/config.toml predictefy config show # prints it with the API key redacted ``` `PREDICTEFY_HOME` relocates the config directory: the CLI then reads `$PREDICTEFY_HOME/config.toml`. Environment authentication takes precedence over the file. API keys are never accepted as command-line arguments. Argv is readable by other processes and lands in shell history, so the key comes from the environment or the config file only. The same rule covers the signing key and every [venue credential](#venue-credentials). `status` is public and keyless. Every other remote command uses the configured key. ## Global flags | Flag | Default | What it does | | ---------------- | -------- | ------------------------------------------------------------- | | `--json` | off | Writes the API response bytes unchanged. | | `--venue ` | `router` | Selects the venue. There are no per-venue command namespaces. | `--json` does not rewrap, rename, or pretty-print fields. It is offered when the command's final captured result is one API envelope. Trading commands can first read the execution registry, and submit also fetches then relays the execution; only the final captured envelope reaches stdout. The machine contract is that final API envelope: ```json { "success": true, "data": [] } ``` An API error envelope is written unchanged and exits 1: ```json { "success": false, "error": { "code": "NOT_SUPPORTED", "message": "…", "retryable": false } } ``` Exit codes: - `0` — success. - `1` — API, transport, config, or doctor-check failure. - `2` — command usage error. Errors go to stderr, except a `--json` envelope, which goes to stdout. Composite and local commands reject `--json` because there is no single envelope to pass through honestly: `venues`, `doctor`, `config`, `skill`, and `watch`. Two surfaces stream instead: `webhooks listen` and `watch --raw` emit NDJSON, one JSON object per line. `predictefy --help` lists the exact options a command accepts. ## Command reference Venue-aware commands take `--venue `; the default `router` is the cross-venue aggregate. List verbs paginate with `--limit` plus either `--offset` or `--cursor`, depending on which pagination the route serves. ### Platform Service checks, identity, capability summaries, and local setup. ```bash predictefy status # public service snapshot, no key required predictefy whoami # prove the configured key works predictefy venues # every venue with its honest capability summary predictefy venues metrics --venue polymarket predictefy doctor # sequenced connectivity, key, and catalog checks predictefy config init predictefy config show predictefy skill install --agents # write the predictefy-cli agent skill ``` The API does not expose identity, scopes, or plan, so `whoami` says so and uses an authenticated capability request as its proof. Balance and consumption are available separately through `GET /v1/usage`. `skill install` also accepts `--claude`, `--read-only`, and `--dry-run`. ### Markets Search, read, and page the normalized market catalog. ```bash predictefy markets search "central bank" --venue router --limit 5 predictefy markets screen "central bank" --venue router --limit 5 predictefy screen "central bank" --venue router --limit 5 # top-level alias predictefy markets get 0xabc --venue polymarket predictefy markets list --venue kalshi --status active --sort liquidity --limit 20 predictefy markets page --venue router --limit 100 predictefy markets categories predictefy markets tags --category politics predictefy markets has --venue kalshi predictefy markets orderbooks predictefy markets execution-price --side buy --size 250 predictefy markets execution-price --side buy --size 250 --detailed predictefy markets filter '' --markets '' ``` `markets search` accepts `--search-in title|description|both` and `--mode lexical|semantic|hybrid`; semantic and hybrid stay subject to server capability flags. `markets screen` and the top-level `screen` alias run the same cross-venue market screener. They accept the search, status, sort, category/tag, pagination, and venue options shown by `--help`. `markets page` is the cursor-paginated form, which freezes the catalog snapshot from page one. `markets has` prints one venue's capability map. `markets execution-price` is a stateless VWAP calculation over the venue's order book: it estimates what a fill of that size would average. It places, routes, and prepares nothing. `--side` and `--size` are required, and `--detailed` returns the per-level breakdown. `markets filter` is a pure stateless filter over a market list: the positional argument is the criteria JSON and the required `--markets` is the JSON array to filter. That route is feature-flagged, so a deployment without it returns the server's honest error. ### Events Event-shaped listings, event metadata, and series. ```bash predictefy events --venue router --status active --sort volume --limit 20 predictefy events get --venue polymarket predictefy events page --venue router --limit 100 predictefy events metadata --venue kalshi predictefy events series --venue kalshi predictefy events filter '' --events '' ``` `events --sort` accepts `volume`, `newest`, `liquidity`, or `closeDate`. `events filter` mirrors `markets filter`: criteria as the positional argument, the required `--events` array as the input. ### Market data Live books, the public trades tape, and candles. ```bash predictefy orderbook --venue polymarket --limit 20 predictefy trades --venue polymarket --limit 20 predictefy candles --venue polymarket --timeframe 1h --limit 100 ``` These three verbs are outcome-keyed. The positional argument keeps the `` spelling, but the value must be the venue's outcome or CLOB token identifier. A venue without a public trades tape returns the server's honest `NOT_SUPPORTED` error rather than an empty list. ### Cross-venue intelligence Matched clusters, matched pairs, indicative price discrepancies, and the router's executable assessment. ```bash predictefy clusters --sort similarity --limit 20 predictefy clusters list --has-discrepancy --limit 20 predictefy clusters get predictefy clusters markets --limit 20 predictefy clusters events --limit 20 predictefy discrepancies --live --limit 10 predictefy discrepancies qualify --size 100 predictefy arbitrage --venue router --contracts 100 --executable-only --limit 20 predictefy matches list --market-id polymarket:0xabc --limit 20 predictefy matches browse --category politics --limit 20 predictefy matches markets --min-difference 0.05 --sort priceDifference --limit 20 predictefy matches prices --min-similarity 0.9 --limit 20 predictefy matches compare --market-id polymarket:0xabc --live --limit 20 predictefy matches hedges --market-id polymarket:0xabc --limit 20 predictefy matches related --slug --limit 20 predictefy matches events --event-id polymarket: --limit 20 ``` Cross-venue price gaps are **indicative price discrepancies** — observed price gaps (stored Yes prices by default, live order-book mid-prices with `--live`), not executable opportunities. `--live` re-derives every row from live order books, so it is capped at **10** rows against the stored surface's 100 — a larger `--limit` is refused rather than quietly trimmed. `discrepancies qualify` runs the server's qualification checks against one cluster at a size, and `arbitrage` applies the server's live-ask, open-market, depth, fee/gas, and resolution-equivalence gates. The CLI neither weakens nor recreates those gates; it prints what the server decided, including the refusal reasons. `matches compare`, `matches hedges`, and `matches related` are anchored on one market with `--market-id` (the canonical `{venue}:{marketId}` spelling) or `--slug`, and `matches events` takes an `--event-id` anchor instead. `--sort` accepts `priceDifference` or `similarity`. Hedge and related rows are candidates, not instructions. ### Trader Intelligence Wallet-attributed tapes, holders, leaderboards, and scored trades. ```bash predictefy traders leaderboard --window all --limit 20 predictefy traders leaderboard --venue polymarket --window month --limit 20 predictefy traders wallet
--venue polymarket predictefy traders smart-money --venue kalshi --limit 20 predictefy traders market-trades --venue polymarket --limit 50 predictefy traders holders --venue polymarket --limit 50 predictefy traders wallet-trades
--venue polymarket --limit 50 ``` Trader identity is capability-qualified per venue. Without `--venue`, `traders leaderboard` serves the cross-venue score ranking. Unsupported venue and verb combinations return the server's honest error; scores are informational signals, not advice. ### Portfolio ```bash predictefy portfolio
--venues polymarket,limitless,opinion ``` One public address across the venues you name. ### Accounts Hosted account reads. Every verb addresses one account on one venue, so all six require an explicit `--venue`: the `router` default is a catalog aggregate, never an account. ```bash predictefy accounts capabilities --venue hyperliquid predictefy accounts snapshot --venue hyperliquid predictefy accounts balances --venue hyperliquid --limit 50 predictefy accounts positions --venue hyperliquid --limit 50 predictefy accounts open-orders --venue hyperliquid --limit 50 predictefy accounts fills --venue hyperliquid --cursor ``` Start with `accounts capabilities`: it reports which of these resources the venue actually serves. See [Accounts & funding](/guides/accounts/) for the per-venue account model. ### Funding & bridge Funding prerequisites, ordered funding steps, and cross-chain bridge sessions. ```bash predictefy funding requirements --venue polymarket predictefy funding steps --venue polymarket --owner 0xabc --target-amount 250 predictefy funding transfer-plan --from-venue hyperliquid --to-venue polymarket \ --amount 250 --owner 0xabc predictefy bridge quote --from-chain 8453 --from-token USDC --from-amount 250000000 \ --from-address 0xabc --to-venue hyperliquid predictefy bridge session create --from-chain 8453 --from-token USDC \ --from-amount 250000000 --from-address 0xabc --to-venue hyperliquid \ --payment-currency predictefy bridge session get predictefy bridge session payment --tx-hash 0xdef predictefy bridge status --tx-hash 0xdef --from-chain 8453 --to-chain 42161 ``` `funding transfer-plan` currently supports one directed pair: `hyperliquid` → `polymarket`. `funding requirements` and `funding steps` address one venue and require an explicit `--venue`; `funding transfer-plan` names both ends itself with `--from-venue` and `--to-venue`. `bridge quote` also accepts `--to-chain`, `--to-token`, `--to-address`, and `--slippage`. Every artifact these commands return is **unsigned**. The CLI prints it; you sign and broadcast it yourself, then report the payment hash back with `bridge session payment`. Step bodies are venue-shaped, so `funding steps` takes repeatable `--field key=value` values that the server stays authoritative over — additive fields only, under the same rule as [trade `--field`](#trading-gates). ### Data feeds Reference price feeds, separate from the prediction-market venues. ```bash predictefy feeds list predictefy feeds markets predictefy feeds ticker predictefy feeds tickers --symbols , predictefy feeds candles --timeframe 1h --limit 100 predictefy feeds orderbook --symbol predictefy feeds oracle-round predictefy feeds oracle-history --limit 20 predictefy feeds history --from --until --order asc ``` A feed is addressed by feed id and symbol, not by `--venue`. `feeds candles` also takes `--since` as an epoch-ms lower bound, and `feeds history` takes `--max-size` plus `--order asc|desc`. `feeds orderbook` is a permanent capability gap, kept visible rather than hidden: reference feeds publish prices, not depth, so the route always returns a typed `NOT_SUPPORTED` error. Use `predictefy orderbook` for prediction-market depth. ### Billing ```bash predictefy billing checkout --pack predictefy billing subscribe --plan predictefy billing portal ``` Each command prints a hosted Stripe URL for you to open. The CLI never collects card or payment data. See [Credits & billing](/guides/credits/) for packs, plans, and metering. ### SQL & mappings ```bash predictefy sql "select venue, count(*) from markets group by venue" predictefy mappings polymarket:0xabc kalshi:KXFED-26MAR-T4.00 ``` `sql` requires an API key with the `sql` scope. A key without that scope gets an authorization error from the server — the scope is not granted by default and cannot be set from the CLI. `mappings` resolves `venue:marketId` pairs to their cross-venue identity. ### Webhooks ```bash predictefy webhooks create --url https://hooks.example.com/predictefy \ --events ingest.run.completed,execution.status.changed predictefy webhooks list predictefy webhooks delete predictefy webhooks listen --endpoint predictefy webhooks listen --endpoint --forward http://localhost:3000/hooks ``` `webhooks listen` polls deliveries until `SIGINT`. Without `--endpoint` it creates a temporary endpoint, subscribes, polls, and deletes the endpoint on exit; a failed cleanup prints the endpoint id. Forwarding accepts only `localhost`, `127.0.0.1`, or `::1`, and POSTs the payload with an `X-Predictefy-Event` header. `--interval ` changes the poll interval, which defaults to 2. With `--json`, each delivery is one NDJSON line rather than an API envelope. ### Trading ```bash predictefy trade buy --venue hyperliquid --price 0.62 predictefy trade sell --venue hyperliquid --price 0.62 --preview predictefy trade submit --venue hyperliquid --side buy --artifact-digest --yes predictefy trade cancel --venue hyperliquid predictefy trade modify --venue hyperliquid --price 0.64 predictefy trade status --venue hyperliquid predictefy trade status --venue hyperliquid --refresh ``` Every `trade` verb requires an explicit `--venue`: the `router` default is a catalog aggregate, never an execution lane. `--price` is required on `buy` and `sell` and lies in `(0, 1]`. `trade modify` needs at least one of `--price`, `--size`, or `--field`. `trade status --refresh` re-reads status from the venue, using the venue credentials described in [Venue credentials](#venue-credentials) — never command arguments. `--tif` accepts `Alo`, `Ioc`, or `Gtc` and applies to `hyperliquid` only, which is the one builder that takes the field. It has no default: omit it and each venue applies its own. Passing it for another venue is refused locally, before any network call, because most builders reject the key outright and `pascal` rejects the value — its time-in-force vocabulary is uppercase `GTC | GTT | IOC`, which this flag deliberately does not translate. Translating would submit an order you did not describe. `trade cancel` and `trade modify` sign locally, and only Hyperliquid's cancel/modify artifact is signable by the local EVM key today. Both verbs refuse another venue by name before signing; use the REST API or the SDK for those. **`polymarket_us` is a capability-qualified exception.** `trade buy` and `trade sell` refuse it by name. The CLI derives one generic order body — `outcome`, `outcomeSide`, `isBuy`, `price`, `size`, `owner` — and the `polymarket_us` builder takes a different vocabulary entirely (`marketId`, `outcomeId`, `side`, `type`, `amount`), so every field would be rejected. Use the REST API or the SDK for `polymarket_us` orders. Its other verbs are unaffected. These verbs are gated. Read [Trading gates](#trading-gates) before running any of them, and [Trading & execution](/guides/trading/) for the full non-custodial model. ### Execution lifecycle Reads over the isolated execution service: the armed lane registry and your recorded orders, trades, positions, and balance. ```bash predictefy exec venues predictefy exec orders --venue hyperliquid --status open --limit 50 predictefy exec trades --venue hyperliquid --limit 50 predictefy exec positions --venue hyperliquid predictefy exec balance --venue hyperliquid ``` `exec venues` is the registry the trading gates below consult: one row per venue the execution service arms, with a flag per verb and the gating that applies. The table prints the `build`, `submit`, `cancel`, `modify`, and `redeem` flags plus the geo-restriction gate; `--json` returns the row verbatim. Run it first when a trade is refused. `exec orders --status` accepts `open`, `closed`, or `all` and defaults to `open`. Every `exec` verb except `venues` addresses one venue and requires an explicit `--venue`. These verbs read; they never sign. They therefore take exactly one requirement — `PREDICTEFY_EXEC_BASE_URL`, because there is no other origin to read from — and neither the `[trading]` opt-in nor a signing key. `trade status` follows the same rule, so no read is gated more tightly than its neighbour. ### Streaming ```bash predictefy watch orderbook --venue polymarket predictefy watch trades --venue polymarket predictefy watch price --venue rain --market-address 0xabc predictefy watch feed-ticker predictefy watch arbitrage ``` See [Streaming behavior](#streaming-behavior) for the socket lifecycle, `--raw`, and entitlements. ## Trading gates Trading is deliberately gated by three independent controls. None of them substitutes for another, and each one covers a different set of verbs. 1. `[trading] enabled = true` in `~/.predictefy/config.toml` — the local opt-in. Required by `trade buy`, `trade sell`, `trade submit`, `trade cancel`, and `trade modify`. 2. `PREDICTEFY_EXEC_BASE_URL` — the isolated execution origin. It has no default, so the reads API can never become an execution proxy. Required by every command that talks to the execution service, reads included: all of `exec *` and `trade status`. 3. `PREDICTEFY_EVM_PRIVATE_KEY` — required by `trade buy`, `trade sell`, `trade submit`, `trade cancel`, and `trade modify`, **including an unsigned preview**. A preview is built _for_ your account, so the CLI reads that key to derive its address and puts the address in the build body. Nothing is signed and nothing is sent. `exec *` and `trade status` never read the key at all. ```toml [trading] enabled = true ``` ```bash export PREDICTEFY_EXEC_BASE_URL='https://execution.example.com' export PREDICTEFY_EVM_PRIVATE_KEY='0x…' ``` The signing key is environment-only. It is never accepted in argv or `config.toml`, never printed, and never sent to Predictefy. Every byte the CLI writes — human output, `--json` envelopes, warnings, and error text — passes through one redaction seam that removes the API key, the signing key, and any configured venue credential, matching case-insensitively and with or without a `0x` prefix. Signing happens locally, in-process. **Venue support is not a hardcoded CLI list.** `trade` queries `GET /v1/exec/venues` — the server's lane registry — at runtime, and allows any venue whose row arms the requested verb. When a venue or verb is not armed, the CLI refuses and names the venues that are armed, reading both answers out of the registry rather than out of a list baked into the client. A preview needs `build`; a confirmed persisted execution needs `submit`. Before signing, the CLI also honors the gating the same row publishes: a geo-restricted lane, or a lane armed without artifact bounds, is refused before a signature exists rather than after the venue rejects the submit. **Signing routes on your `--venue`, and the envelope is pinned.** Structural validity is not authorization: a token `Permit`, a Permit2 batch and a marketplace listing are all valid EIP-712 typed data, so an execution service that returned one instead of an order could otherwise have it signed. Two rules prevent that: - The signing path is chosen by the venue **you** typed, never by the shape or the self-declared venue of the artifact. An artifact stamped for a different venue than `--venue` is refused by name. - Before signing, the envelope must match that venue's pinned exchange domain — EIP-712 domain name, version, `chainId`, and a `verifyingContract` from the venue's known-exchange allowlist — the exact struct of its deployed exchange (field names, types, count and order, not just the struct name), and your own intent: the struct's signer field must be your signing address, and its side must be the buy or sell you asked for. Where the struct routes value or names a counterparty (`beneficiary` on XO, `taker` on Opinion, PredictStreet, and PRED), that field must be the zero address or your own account. Any mismatch is refused naming the field that failed, and no signature bytes are produced. Hyperliquid's native L1 path is pinned the same way. The signer canonicalizes the visible action, recomputes its official msgpack `connectionId` with the nonce and expiry, and requires the network source to match `Mainnet` or `Testnet`. It also requires the real `Exchange` domain (chainId 1337, zero `verifyingContract`) and refuses any action outside `order`, `cancel`, `cancelByCloid`, and `modify`. A different digest or fund-moving action is refused before signing. A venue with no pinned envelope and no native path — one authenticating with venue-issued API credentials, or signing with a non-EVM key — is refused with a message naming what _is_ supported. The CLI never signs an artifact it cannot account for. **Always preview first.** The default builds and prints the unsigned artifact plus its notional and fee bounds. It signs nothing and submits nothing: ```bash predictefy trade buy --venue hyperliquid --price 0.62 ``` The preview prints a canonical artifact digest and the exact second-step command. After a human confirms that artifact, submit the persisted execution rather than rebuilding it: ```bash predictefy trade submit --venue hyperliquid --side buy \ --artifact-digest --yes ``` `trade submit` reloads the named execution, recomputes its digest, and refuses before signing when it differs from `--artifact-digest`. Under `--json`, the preview's confirmation command and the confirmed artifact go to **stderr**, so stdout remains one clean API envelope. The older one-shot `trade buy|sell --yes` path no longer exists. **Read that artifact — it is your verification surface for the values the pins cannot cover.** The order amounts (`makerAmount`/`takerAmount`) and the outcome `tokenId` are _not_ pinned. Amounts are each venue's own tick- and decimal-scaled derivation of price × size, and the token is resolved from your market through the venue's catalog, so the CLI cannot re-derive either without duplicating every venue's rounding rules — which would refuse legitimate orders. The honest consequence: an execution origin that has been compromised can put different values in the preview, so check its amounts and token. Once its digest is confirmed, however, the origin cannot swap those values before signing: any change produces a different digest and is refused. Additive venue-shaped build fields go through repeatable `--field key=value` values, which the server — not the CLI — is authoritative over. The per-venue build schemas in [Trading & execution](/guides/trading/) name the fields each venue expects: ```bash predictefy trade buy --venue polymarket \ --field = --field = ``` A field value parses as JSON when it can, so numbers, booleans, and objects survive; anything else stays a literal string. `trade modify` takes the same escape hatch for venue-shaped modify bodies, and `funding steps` takes it for venue-shaped step bodies. `--field` is additive only. It cannot set `venue`, `owner`, `signer`, `funder`, `isBuy`, `side`, `price`, `size`, `outcome`, `outcomeSide`, `asset`, `targetAmount`, `dryRun`, or `idempotencyKey`: those come from the command's own arguments, `--venue`, its dedicated flags, and the signing key, and the safety gates already ran against them. Setting one would move the request to a different venue, account, side, price, or size than the one you typed — past the gate that checked it. Passing one is a usage error (exit 2) before any network call. ## Venue credentials Some execution lanes require credentials the venue issued to _you_ — an API key, secret, and passphrase, or an access/refresh token pair. Predictefy never holds them, so the CLI forwards them from your environment on the calls that need them: `trade submit` and `trade status --refresh`. They are accepted from the environment only, never from command arguments, where they would land in the process list, shell history, and CI logs. One variable per field: ```bash export PREDICTEFY_VENUE_CRED_XO_API_KEY='…' export PREDICTEFY_VENUE_CRED_XO_API_SECRET='…' export PREDICTEFY_VENUE_CRED_XO_API_PASSPHRASE='…' ``` The name is `PREDICTEFY_VENUE_CRED__`. `` is the venue slug upper-cased with separators removed, so `polymarket_us` becomes `POLYMARKETUS` and can never be confused with `POLYMARKET`. `` is the request field in `SCREAMING_SNAKE_CASE`, so `ACCESS_TOKEN` becomes `accessToken`. Any value set this way is redacted from CLI output like the other secrets. ## Streaming behavior `watch` opens the hosted WebSocket, prints frames until you press Ctrl-C, then unsubscribes and closes the socket cleanly. ```bash predictefy watch orderbook --venue polymarket predictefy watch orderbook --venue polymarket --raw ``` `--raw` emits each frame as one line of JSON (NDJSON) instead of the human summary, which is the form to pipe into another process. `watch` itself rejects `--json`: a stream has no single envelope. `watch orderbook`, `watch trades`, and `watch price` each address one market on one venue, so they require an explicit `--venue`; `watch price` also requires `--market-address`, the on-chain market contract address. `watch arbitrage` requires a plan entitling the `arbitrage` feature. The entitlement is re-checked while the socket is open, so a denial — or a mid-stream revocation — arrives as a `PLAN_UPGRADE_REQUIRED` error frame on a socket that stays open. The CLI prints those error frames to stderr rather than exiting silently, so a revoked stream never looks like a quiet market. A venue that does not serve a subscription answers `NOT_SUPPORTED` the same way, on every verb — order books, trades, prices, and feed tickers included. Ctrl-C is the only clean ending. If the socket closes on its own — a server restart, an auth rejection, a dropped connection — the CLI prints the close code and reason to stderr and exits non-zero, so a dead stream is never mistaken for a quiet one. The hosted stream requires an API key, so `watch` fails fast with a named error when none is configured, before opening a socket the server would immediately close. See [Streaming](/guides/streaming/) for venue coverage and the [WebSocket API](/reference/streaming/) for the frame shapes. --- # Scan for qualified cross-venue opportunities > Poll fetchArbitrage for gate-checked results, read every rejection reason, and revalidate before acting. Source: https://docs.predictefy.com/guides/cookbook/arbitrage-scanner/ The same question trades on more than one venue, and the two prices are rarely identical. Most of those gaps are not tradeable. This recipe keeps only the ones that survive every gate. ## What you will build A polling worker that calls `fetchArbitrage` with `executableOnly=true` and revalidates anything it is about to act on. When you need to diagnose an empty result, make a separate call without `executableOnly=true`; only that unfiltered response includes non-qualifying candidates and their rejection reasons. **Prerequisites:** a Builder plan or above, and an API key from [the dashboard](https://portal.predictefy.com/keys). :::caution[The Free plan excludes this feed] Calling `fetchArbitrage` on a Free key returns `PLAN_REQUIRED`. See [Credits & billing](/guides/credits/) for what each plan includes. ::: ## The gates `fetchArbitrage` is the only verb that applies the word _arbitrage_ to anything, and it does so only when every gate passes: live non-synthetic asks on both legs, both markets open, real depth at the size you asked for, verified per-venue fees, compatible resolution rules, and a net edge that survives all of it. Anything short of that stays labelled an indicative price discrepancy. `label` carries exactly two values — `arbitrage` and `indicative price discrepancy` — and `executable` is the boolean form of the same judgement. There is no partial credit. Two fields describe resolution, and they are not the same claim: - `resolution.compatible` is a **cheap fingerprint veto**. Its `reason` is one of `threshold_conflict`, `stage_conflict`, `source_conflict`, or empty. Compatible does **not** mean verified equivalent. - `resolutionEquivalence` is the stronger statement — `verified` only when a high-confidence persisted verdict matches both legs' current resolution-rule content hashes. `similarity` is a match score, never a confidence. Two markets can read alike and settle differently, which is exactly why the resolution gates exist. ## Request ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchArbitrage?executableOnly=true&contracts=1000&limit=500" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` - `contracts` is the size the assessment is made **at** — depth and fees are judged for that fill, not for one contract. Change it and the answer legitimately changes. - `limit` supports up to **500** rows per page, paginated via `cursor` (`page.nextCursor`). - `executableOnly=true` returns only rows passing every executable gate. Drop it to also receive non-qualifying candidates with rejection reasons. - `venues` (optional) keeps a row when **either leg** touches the listed venues (for example, `venues=polymarket,kalshi`). It does not require both legs to stay inside the list. - `minEdge` (optional) filters rows by minimum net edge. ```js const BASE = 'https://data.predictefy.com'; async function scan({ contracts = 1000, minNetEdge = 0.01, venues } = {}) { let cursor; const results = []; do { const url = new URL('/api/router/fetchArbitrage', BASE); url.searchParams.set('executableOnly', 'true'); url.searchParams.set('contracts', String(contracts)); url.searchParams.set('limit', '500'); if (venues) url.searchParams.set('venues', venues.join(',')); if (cursor) url.searchParams.set('cursor', cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PREDICTEFY_API_KEY}` }, }); const body = await res.json(); if (!body.success) { // PLAN_REQUIRED on Free keys; RATE_LIMITED and INSUFFICIENT_CREDITS are also expected. if (body.error.retryable) return results; throw new Error(`${body.error.code}: ${body.error.message}`); } // netEdge is nullable — an unpriced pair is not a zero-edge pair. results.push(...body.data.filter((row) => row.netEdge !== null && row.netEdge >= minNetEdge)); cursor = body.page?.hasMore ? body.page.nextCursor : undefined; } while (cursor); return results; } ``` ## Response ```json { "success": true, "data": [ { "clusterId": "clr_9f2a7c41:kalshi:polymarket", "question": "Will the Fed cut rates at the January 2026 meeting?", "similarity": 0.94, "contracts": 1000, "legs": { "buyYes": { "venue": "kalshi", "canonicalMarketId": "kalshi:FED-26JAN-CUT", "side": "yes", "executable": true, "reasons": [], "vwap": 0.412, "cost": 412.0, "fee": 3.7, "feeBasis": "general", "filled": 1000, "fullyFilled": true }, "buyNo": { "venue": "polymarket", "canonicalMarketId": "polymarket:0x7d3f...c19a", "side": "no", "executable": true, "reasons": [], "vwap": 0.559, "cost": 559.0, "fee": 0.0, "feeBasis": "general", "filled": 1000, "fullyFilled": true } }, "resolutionEquivalence": "verified", "resolution": { "compatible": true, "reason": "", "auditReasons": [] }, "settlementFee": 0.0, "totalCost": 974.7, "payout": 1000.0, "netEdge": 25.3, "roi": 0.026, "executable": true, "reasons": [], "label": "arbitrage", "asOf": "2026-08-31T14:22:08.412Z" } ], "meta": { "live": true, "contracts": 1000, "provenance": { "source": "predictefy-live" }, "asOf": "2026-08-31T14:22:08.412Z", "seq": 1042, "source": "published" }, "page": { "limit": 500, "offset": 0, "total": 1, "hasMore": false, "nextCursor": null } } ``` Every leg carries its own `executable` and `reasons`, so a pair can fail on one side only. Read the leg-level reasons when the pair-level `reasons` array does not explain enough. `fullyFilled` is the field to branch on for sizing: `filled` is what the walked asks could actually absorb, and it is less than or equal to what you requested. ## Reading the numbers honestly - **`meta.source` discloses the data origin.** `'published'` means the response was served from the live published surface (fresh within the publisher's self-declared cadence (about 30s; 3s floor)); `'computed-fallback'` indicates bounded on-demand computation over at most 10 candidate clusters when the published surface is unavailable. One candidate cluster can emit multiple ordered pair rows. `meta.asOf` and `meta.seq` record the timestamp and sequence number. - **`netEdge`, `roi`, `totalCost`, `vwap`, `cost`, `fee` and `settlementFee` are all nullable.** A null is "not priced", not "zero". Filtering with `row.netEdge >= x` silently drops nulls in some languages and admits them in others — test for null explicitly. - **`fee` is a verified taker fee or nothing.** Where a venue's schedule is not verified, the leg carries a `note` explaining why no model was applied, and the edge is unknown rather than optimistic. - **`page.total` may be `null`**, which means _not counted_, not zero. Paginate on `hasMore` and `nextCursor`. ## Before you act on a result :::danger[Revalidate immediately before execution] Every field is a snapshot carrying `asOf`. A pair that qualified forty seconds ago may be gone, partly filled, or closed. Re-read the books and re-check market status at the moment of execution, every time. ::: Good market data on a venue does not mean you can trade there. `GET /v1/exec/venues` is the authoritative list of execution lanes — see [Trading & execution](/guides/trading/). Several venues serve real books with no hosted trading lane at all. Venues with reconstructed books cannot qualify by design: a synthetic book is a faithful representation of price and is **not** executable depth. [Venue coverage](/reference/venues/) records which venues have a real book. ## Cost An arbitrage query is the most expensive read on the platform at **15 credits**; a cross-venue price comparison is 10 and an order-book snapshot is 5. Continuous polling adds up: | Interval | Queries/day | Credits/day | Credits/30 days | | -------- | ----------- | ----------- | --------------- | | 5 min | 288 | 4,320 | 129,600 | | 60 s | 1,440 | 21,600 | 648,000 | | 10 s | 8,640 | 129,600 | 3,888,000 | Budget before you poll, and add revalidation on top — 5 credits per leg per check. Current weights and plan allowances are in [Credits & billing](/guides/credits/). ## Related - [Cross-venue data](/guides/cross-venue/) — how matching works, and why a gap is not an edge - [Market relationships](/guides/market-relationships/) — related markets and hedge candidates - [Prediction markets](/guides/prediction-markets/) — why venues disagree in the first place --- # Backtest a strategy on historical candles > Pull OHLCV for an outcome, read the quality vocabulary, and refuse to backtest on data that cannot support it. Source: https://docs.predictefy.com/guides/cookbook/backtest-on-candles/ Historical candles are the input to any backtest. Predictefy labels every candle with where it came from and how good it is, and a backtest that ignores those labels will produce confident numbers from data that cannot support them. ## Request Candles key on an **outcome**, not a market — a binary market has one series per side. [Identifiers](/guides/market-ids/) covers why. ```sh curl -s "$PREDICTEFY_API_URL/api/polymarket/fetchOHLCV?outcomeId=OUTCOME_ID&resolution=1h&start=2026-01-01T00:00:00Z&end=2026-06-30T00:00:00Z&limit=5000" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` | Parameter | Detail | | --------------- | ----------------------------------------------------------------------------------------------------------------------- | | `outcomeId` | The required series selector. `id` is a compatibility alias; `marketId` may narrow the lookup but is not accepted alone | | `resolution` | `1s` `5s` `10s` `30s` `1m` `5m` `15m` `30m` `1h` `4h` `6h` `1d` | | `start` / `end` | ISO timestamp **or** epoch milliseconds | | `limit` | 1–5000 | `5m`, `15m`, `30m`, `4h` and `6h` are **aggregated at query time** from stored history rather than being stored natively. That is not a defect, but it does mean those buckets inherit the quality of whatever they were built from — which the response tells you. ## The candle record ```json { "timestamp": 1767225600000, "open": 0.41, "high": 0.44, "low": 0.4, "close": 0.43, "volume": 128400, "source": "official", "sourceType": "true-candle", "quality": "ok", "isTrueCandle": true } ``` `timestamp`, `open`, `high`, `low`, `close`, `volume`, `source`, `sourceType`, `quality`, and `isTrueCandle` are always present. The `volume` value may be `null`; the key itself is required. **`source`** — where the series came from: `official`, `onchain`, `write-forward`, `derived`. **`sourceType`** — how the bucket was built: | Value | Meaning | | --------------- | ------------------------------- | | `true-candle` | The venue published this candle | | `point-derived` | Built from point-in-time prices | | `trade-derived` | Built from the trades tape | | `rest-derived` | A coarse REST trade-tape candle | | `book-derived` | Built from order-book state | | `rollup` | Aggregated from finer buckets | **`quality`** — `ok`, `partial`, `suspect`, or `mixed`. `mixed` means the bucket aggregates inputs of differing quality, which is the expected value for a query-time aggregation. **`isTrueCandle`** — the single boolean that separates published candles from reconstructed ones. :::caution[Candles use a different vocabulary from other reads] Most records carry `asOf` and `provenance`. Candles do not — they carry `source`, `sourceType`, `quality` and `isTrueCandle` instead. When present, the response's `meta.provenance` describes the series as a whole: `venue-native`, `predictefy-store`, or `merged`. Store-only responses can omit `meta` entirely. A checker that demands `asOf` on a candle or `meta.provenance` on every response is asserting the wrong contract. ::: ## Filter before you backtest ```js function usable(candle) { // A backtest that mixes published candles with book-derived reconstructions is // measuring two different things and reporting one number. if (candle.quality === 'suspect') return false; if (candle.sourceType === 'book-derived') return false; return true; } const candles = body.data.filter(usable); const coverage = candles.length / body.data.length; if (coverage < 0.95) { throw new Error( `only ${(coverage * 100).toFixed(1)}% of buckets are usable — widen the window or drop the venue`, ); } ``` Decide the rule up front and record it with the result. "Backtested on 1h candles, excluding `suspect` and `book-derived` buckets, 98.2% coverage" is a claim someone can check. A bare Sharpe ratio is not. `volume` is nullable. A null is "not reported", not zero — a volume filter that treats null as zero silently discards every venue that does not publish it. ## What history you can actually read Two limits apply, and they are different: - **Venue coverage.** Not every venue has history for every resolution. [Historical data](/guides/history/) records what exists, including which venues have sub-minute data and in what id format. - **Plan window.** Your plan caps how far back you may read. Requesting beyond it returns `PLAN_REQUIRED` rather than a silently truncated series — see [Credits & billing](/guides/credits/). When `meta.provenance` is present, `venue-native` means every returned bucket came from the venue, `predictefy-store` means our own store served it, and `merged` means both. Store-only responses may omit `meta`, so use each candle's required source fields as the universal contract. A backtest that spans a provenance change is comparing two datasets. ## Cost History reads are priced above catalog reads, and a backtest is many of them — one per outcome per window. Pull once and cache locally; re-running a strategy should not re-read the API. Current weights are in [Credits & billing](/guides/credits/). ## Related - [Historical data](/guides/history/) — coverage per venue and resolution - [Identifiers](/guides/market-ids/) — why candles key on `outcomeId` - [Capability-honest data](/guides/honest-data/) — reading the honesty fields generally --- # Compare one market across every venue > Resolve a market to its cluster, line up every venue's price, and label each one honestly. Source: https://docs.predictefy.com/guides/cookbook/compare-across-venues/ "Will the Fed cut in January?" trades on several venues at once, under different titles, different tickers, and occasionally different resolution rules. This recipe lines them up. ## The one-call version `compareMarketPrices` exists for exactly this. Call it on the `router` exchange with a market from any venue and it returns that anchor's cross-venue comparison directly — no cluster lookup needed. ```sh curl -s "$PREDICTEFY_API_URL/api/router/compareMarketPrices?marketId=polymarket:MARKET_ID&live=true" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` It takes a canonical `venue:marketId` value **or** `slug`, plus `live`, `limit`, `offset` and `page`. The anchor market is excluded; each row is one other cluster member: ```json { "success": true, "data": [ { "market": { "...": "the normalized market record for this venue" }, "venue": "polymarket", "clusterId": "clr_9f2a7c41", "similarity": 0.94, "reasoning": null, "bestBid": 0.412, "bestBidMeta": { "asOf": "2026-08-12T14:31:04.882Z", "source": "live-book" }, "bestAsk": 0.418, "bestAskMeta": { "asOf": "2026-08-12T14:31:04.882Z", "source": "live-book" }, "indicativeYesPrice": 0.415, "indicativeNoPrice": 0.585, "label": "indicative price comparison", "asOf": "2026-08-12T14:31:04.882Z" } ], "meta": { "live": true, "liveUnavailable": false } } ``` Two pairs of price fields, and the difference matters: - **`bestBid` / `bestAsk`** come from the live book overlay. They are **never** a stored snapshot — if the overlay could not run, they are null rather than stale. - **`indicativeYesPrice` / `indicativeNoPrice`** are the catalog's view, which may be a stored value. Check `meta.liveUnavailable` before you present anything as live. When it is true, the live overlay was requested and did not succeed, and the row is telling you so instead of quietly serving you older numbers. ## The cluster route Use this when you want the grouping itself rather than one market's neighbours. ```sh # Browse clusters. Note: filters are category / hasDiscrepancy / sort — there is no # text search on this route. Find the market first with fetchMarkets, then compare. curl -s "$PREDICTEFY_API_URL/v1/clusters?hasDiscrepancy=true&limit=20" \ -H "Authorization: Bearer pk_live_YOUR_KEY" # Expand one cluster into its members. curl -s "$PREDICTEFY_API_URL/v1/clusters/CLUSTER_ID" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` A cluster carries `question`, `normalizedQuestion`, `category`, `venueCount`, `venues`, `hasDiscrepancy`, `aggAvgYes`, `aggSpread`, `maxSpread`, `similarity`, `closeDate` and `asOf`. Expanding it adds `members`, each with `marketPk`, `venue`, `yesPrice`, `noPrice`, `volume24h`, `liquidity` and its own `similarity`. ## Standing gaps `/v1/discrepancies` lists clusters that currently disagree on price. It filters on `category` and `live` — **not** by cluster id — and each row is a spread with its two ends: ```sh curl -s "$PREDICTEFY_API_URL/v1/discrepancies?expand=markets&limit=100" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` ```json { "clusterId": "clr_9f2a7c41", "question": "Will the Fed cut rates at the January 2026 meeting?", "venues": ["kalshi", "polymarket"], "spread": 0.023, "low": { "venue": "kalshi", "canonicalMarketId": "kalshi:FED-26JAN-CUT", "yesPrice": 0.415, "market": { "...": "catalog metadata for the kalshi leg" } }, "high": { "venue": "polymarket", "canonicalMarketId": "polymarket:0x7d3f", "yesPrice": 0.438, "market": { "...": "catalog metadata for the polymarket leg" } }, "similarity": 0.94, "matchProbability": 0.97, "label": "indicative price discrepancy", "asOf": "2026-08-12T14:31:04.901Z" } ``` `expand=markets` adds that `market` object to every leg: title, venue, status, close time, image URL when stored, liquidity, and volume. The CSV `expand` query parameter supports only `markets` today; any other value returns `400`. It adds no extra metering weight. Stored requests keep the existing `limit` default of **20** and maximum of **100**; this documents the pre-existing range rather than adding headroom. `live=true` now has an explicit maximum of **10** because every live cluster recomputes from real order books. The tighter cap bounds that cost; the old shared maximum of 100 was an accidental abuse vector on the live path. `label` has exactly one permitted value here — `indicative price discrepancy`. That is not hedging: this route makes no claim about depth, fees, or resolution equivalence, so it cannot call anything executable. To ask whether one of these survives those checks, qualify it: ```sh curl -s "$PREDICTEFY_API_URL/v1/discrepancies/CLUSTER_ID/qualification?size=1000" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` That route takes `size` and an optional `venues` filter. See [the scanner recipe](/guides/cookbook/arbitrage-scanner/) for what qualification actually tests. - **`checks.netPositive.netEdge` and `grossEdge` are rates, not total-dollar profit.** Each divides the corresponding profit by the walk's `executableSize`. For example, `netEdge: 0.031` means an estimated net return equal to 3.1% of executable spend; multiply it by `executableSize` to recover the total-dollar estimate. :::note[Performance characteristics] `live=true` recomputes against real order books on every call. Budget for seconds, not milliseconds; that is by design, not a bug. Live responses from both the list and qualification routes expose `meta.timings` with `booksMs`, `recomputeMs`, and `totalMs`, so inspect those stages before diagnosing a slow call. For latency-sensitive work, use the [streaming channel](/guides/streaming/) as the hot path instead of polling `live=true`. ::: ## Reading the comparison honestly - **`similarity` is a match score, never a confidence.** A 0.94 pair can still settle differently. `matchProbability` is a separate field and a separate claim. - **Render `asOf` next to every price.** Presenting a snapshot as live is the most common way a comparison UI misleads the person reading it. - **Label the book type per row.** Some venues serve reconstructed books — faithful as price, not executable as depth. [Venue coverage](/reference/venues/) records which venues have a real book; read it from there rather than hard-coding a list that will go stale. - **Handle `NOT_SUPPORTED` per row, not per request.** One venue that cannot serve a book should not blank the table. See [Capability-honest data](/guides/honest-data/). - **`page.total` may be `null`** — that means not counted, not zero. Paginate on `hasMore` and `nextCursor`. - **Treat a non-null `page.total` as a cluster-grain estimate, never an exact count.** Clusters can merge, split, appear, or disappear between snapshot ticks, so the total can drift between page reads. This is the same cluster-grain caveat as the match verbs: use the value for paging UX only, never to reconcile an exact count. ## Cost Cluster lookups and cross-venue comparisons are priced above catalog reads because each one walks stored relationship data. Market matching itself is a background pipeline and is not billed per request, so cache the cluster id, not the prices. Current weights are in [Credits & billing](/guides/credits/). ## Related - [Scan for qualified cross-venue opportunities](/guides/cookbook/arbitrage-scanner/) — whether a gap is tradeable - [Cross-venue data](/guides/cross-venue/) — how matching works - [Prediction markets](/guides/prediction-markets/) — why venues legitimately disagree --- # Monitor a multi-venue portfolio > Check capability first, read balances and positions per venue, and know which surface each number came from. Source: https://docs.predictefy.com/guides/cookbook/monitor-portfolio/ Positions live on venues, not on Predictefy. Reading them across venues means asking each one what it will actually serve before asking it for anything. ## Ask what is available first `getAccountCapabilities` is the call that comes before the others. It reports, per resource, what this venue can do: ```sh curl -s "$PREDICTEFY_API_URL/v1/accounts/polymarket/capabilities" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` It returns four resources — `balances`, `positions`, `openOrders`, `fills` — each with a `state`, a `served` flag and `notes`. `state` is one of: | State | Meaning | | --------------------- | ----------------------------------------------------------- | | `public` | Readable without the account holder's credentials | | `owner_auth_required` | Needs the account owner's own authentication | | `derived` | Reconstructed by Predictefy rather than served by the venue | | `not_supported` | The venue does not expose it at all | Branch on this. `not_supported` is a correct answer about the world — do not retry it, and do not substitute another venue's number. `derived` is real but is our reconstruction, not the venue's statement, and should be labelled as such wherever a user sees it. ## Read positions and balances ```sh curl -s "$PREDICTEFY_API_URL/v1/accounts/polymarket/ACCOUNT_ID/positions?limit=100" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` The path carries **both** the venue and the account id — `/v1/accounts/{venue}/{accountId}/…`. The same shape serves `/balances`, `/positions`, `/open-orders` and `/fills`. All four take `limit`, but only positions, open orders, and fills take `cursor`. Balances are not paginated; sending a cursor returns `400 VALIDATION_ERROR`. A position row: ```json { "venue": "polymarket", "accountId": "0xabc…", "positionId": "pos_18f2", "marketId": "0x7d3f…", "canonicalMarketId": "polymarket:0x7d3f…", "outcomeId": "0x91aa…", "side": "yes", "size": 250, "avgEntryPrice": 0.41, "markPrice": 0.44, "unrealizedPnl": 7.5, "realizedPnl": 0, "status": "open", "asOf": "2026-08-12T14:31:04.882Z" } ``` Everything above `raw` is required and normalized identically across venues, so one renderer works everywhere. `raw` carries the venue's own payload when you need something the normalized shape does not cover. Note both id forms: `marketId` is the venue's, `canonicalMarketId` is `{venue}:{marketId}` and is what you use as a cross-venue key. See [Identifiers](/guides/market-ids/). ## The aggregate view `getPortfolio` rolls this up for one address: ```sh curl -s "$PREDICTEFY_API_URL/v1/portfolio?address=0xabc…&venues=polymarket,limitless" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` It takes `address` and an optional `venues` filter, and returns `venues` keyed by venue — each with its own `balances` and `positions` — plus `totals` carrying `markValueUsd` and a `byVenue` breakdown, and an `asOf` for the whole snapshot. The filter is limited to the venues that serve public balances or positions: `hyperliquid`, `limitless`, `myriad`, `opinion`, `polymarket`, `predictfun`, `rain`, and `sxbet`. Omit `venues` for all eight. Naming any other venue — `kalshi`, for one — returns `400 VALIDATION_ERROR` rather than a silently short answer. Use `totals.markValueUsd` for a headline number and `totals.byVenue` when you need to show where the value sits. The top-level `asOf` covers the aggregate; individual rows carry their own. ## Two surfaces, and they are not the same This is the mistake worth avoiding. - **`/v1/accounts/{venue}/…`** is the **hosted account** surface: what a venue reports about an account, read-only. - **`/v1/exec/{venue}/positions`** is the **execution service** surface: positions derived from orders you placed through Predictefy. Its rows are `ExecDerivedPosition` — `venue`, `marketId`, `outcomeId`, `netSize` — and nothing else. It is deliberately narrow. They answer different questions and will legitimately disagree: the account surface sees everything on the venue, the execution surface sees only what came through us. Comparing them and calling the difference a bug is a misreading. Pick the one that matches the question you are answering, and say which one your UI is showing. `/v1/exec/{venue}/balance` is likewise the execution service's view, not the venue's account balance. ## Building the monitor ```js async function venueSnapshot(venue, accountId) { const caps = await api(`/v1/accounts/${venue}/capabilities`); if (caps.data.positions.state === 'not_supported') { // Honest gap. Render it as unavailable, not as zero positions. return { venue, positions: null, reason: 'not_supported' }; } const positions = await api(`/v1/accounts/${venue}/${accountId}/positions`, { limit: 100 }); return { venue, positions: positions.data, derived: caps.data.positions.state === 'derived', }; } ``` An empty array means "ran and found nothing". `not_supported` means "cannot run here". Rendering the second as the first tells the user they hold no positions on a venue where we simply cannot look — see [Capability-honest data](/guides/honest-data/). ## Cost Account reads are per venue per resource, so a portfolio across five venues is at least five calls before pagination. `getPortfolio` is one call for the aggregate and is cheaper than assembling it yourself when you only need totals. Current weights are in [Credits & billing](/guides/credits/). ## Related - [Accounts & funding](/guides/accounts/) — the account surface in full - [Trading & execution](/guides/trading/) — the execution service and its own position view - [Capability-honest data](/guides/honest-data/) — why `not_supported` is an answer --- # Build, sign, and submit an order > The five-step execution loop, what each step guarantees, and the three failures worth handling before you write any of it. Source: https://docs.predictefy.com/guides/cookbook/place-an-order/ Predictefy builds the order and relays it. **You** sign it. There is no generic signing route, and no user signing key is held here — so the loop has a shape that is not optional. [Trading & execution](/guides/trading/) is the reference, including the build schema for every venue. This page is the loop and the failures. ## The loop 1. **`GET /v1/exec/venues`** — the authoritative list of armed lanes. Good market data on a venue does not mean it has an execution lane. 2. **`POST /v1/exec/{venue}/orders/build`** — with a `trade`-scoped key and an `Idempotency-Key`. Returns an unsigned artifact and an `executionId`. 3. **Inspect and sign in your own process.** What you sign is venue-specific — an EIP-712 payload for some lanes, a raw digest for others. Gemini, Kalshi, and Polymarket US use transient request authentication instead. Novig relays its server-built authless artifact under the caller's transient `callerAccessToken`. Those four lanes need no signer callback; submit the `executionId` with that venue's per-request authorization fields. 4. **`POST /v1/exec/{venue}/orders/submit`** — the `executionId` plus that venue's signed fields. 5. **`GET /v1/exec/{venue}/orders/{executionId}`** — poll for status. For a user-authenticated venue read, call `refresh` first. `client.exec.createOrder(params, signer)` composes steps 2–4 for client-signing lanes. The signer callback receives only the unsigned artifact and returns venue-shaped signed fields; private keys never leave your process. :::caution[The venue segment is part of the path] Every execution route is `/v1/exec/{venue}/…`, not `/v1/exec/…`. There is no venue-less build or submit route. ::: ## `executionId` binds everything `executionId` is required on submit and binds the submission to one stored, already cap-checked execution. - Omitting it → `400 VALIDATION_ERROR`. - Malformed, or simply not yours → `404 EXECUTION_NOT_FOUND`. **Never retry this** — no retry can make a non-existent execution exist, which is why it is not classed as a retryable server error. ## Idempotency is enforced, not advisory The `Idempotency-Key` header is **required**, and one caller key binds at most one execution per account, venue and action. - Reusing a key against a _different_ execution → `409 IDEMPOTENCY_CONFLICT`. - Replaying against the _same_ execution once it has left `built` → the current state comes back with `Idempotency-Replay: true`. No second relay, no second charge. Generate one key per intended order and keep it with the order. Reusing a key as a retry token across different orders is the failure this design exists to catch. ## Spend caps fail closed Defaults are **100 USD per order** and **1,000 USD per API key across a rolling 24 hours**. The service re-checks the stored notional against the per-order cap and re-sums the key's actual submitted spend for the day _before_ relaying, then reserves the submission. A cap violation is **rejected** — the service never silently reduces an order to fit. The reservation behaviour is the part worth understanding: - Rejected before the venue was contacted → the reservation is freed. - `502 VENUE_RELAY_FAILED` (ambiguous — the venue may or may not have received it) → the reservation is **kept**, so a resubmit cannot bypass the cap. So an ambiguous failure consumes budget by design. Treat `502` as "state unknown, go and read the order", not as "it failed, try again". ## What transits, and what does not Wallet signatures are produced entirely in your process. Some lanes need a transient venue credential at submit — an API key and secret, a session token — and those transit only when that venue needs them. They are **not persisted and not logged**. The per-venue table in [Trading & execution](/guides/trading/) lists exactly what each lane expects back. ## Before you build - **Check the lane, not the venue.** `GET /v1/exec/venues` returns per-venue `build`, `submit`, `cancel`, `modify`, `redeem` and `gating`. A venue can support build and not modify. - **Scope the key.** Execution needs the `trade` scope; a read key returns `403 SCOPE_MISSING`. - **Missing bounds keep a venue unavailable.** Each lane enforces venue-specific contract, currency, chain, owner and artifact bounds. Unknown bounds fail closed rather than guessing. ## Related - [Trading & execution](/guides/trading/) — the full reference, per-venue build schemas, scopes - [Accounts & funding](/guides/accounts/) — funding a venue before you can trade on it - [Errors](/guides/errors/) — the envelope, and which codes are worth retrying - [Monitor a multi-venue portfolio](/guides/cookbook/monitor-portfolio/) — reading the result --- # Build a market research agent > Chain the MCP read tools into a research loop, and design around the caps rather than into them. Source: https://docs.predictefy.com/guides/cookbook/research-agent/ The MCP server gives an agent thirty-three read, intelligence, and platform tools over the same data the REST API serves, alongside ten execution and collateral tools. This recipe is the chain that answers a research question, and the caps that shape how you write it. [MCP server](/guides/mcp/) covers configuration. This page is the workflow. ## The chain A research question — "what is the market saying about the January rate decision, and who is trading it?" — resolves through four steps: 1. **`search_markets`** — text search on one venue, or **all venues** when `venue` is omitted. Omitting it is usually right for research: you want the question wherever it trades. 2. **`get_market`** — the full record for a candidate, including its outcomes. You need an outcome before you can ask for depth or history. 3. **`get_orderbook`** and **`get_ohlcv`** — current depth and price history for that outcome. 4. **`get_market_traders`**, **`get_market_holders`**, **`get_smart_money`** — who is on the other side. `list_venues` is free in the sense that matters: it makes no upstream call. Use it to ground the agent in what exists rather than letting it guess venue names. ## Design around the caps Every cap is enforced **server-side at the wire**, whatever the model asks for. An agent written as if they do not exist will silently work with partial data: | Cap | Applies to | | ----------------- | -------------------- | | **100 rows** | every list tool | | **5,000 candles** | `get_ohlcv` | | **~50 KB** | response byte budget | | **15 s** | per-request timeout | Truncation is never silent: an over-budget payload comes back with `"truncated": true` and a note. **Have the agent check that flag** and narrow its query rather than reasoning over a truncated set as though it were complete. The practical consequence: prefer several narrow calls to one broad one. A `get_ohlcv` over six months at `1m` will hit the candle cap; the same request at `1h` will not. ## What the tools will not do - **Unknown venues are rejected before any upstream call**, and the error carries the full valid venue list — so a wrong venue name costs nothing and self-corrects. - **Unsupported venue/verb combinations fail honestly.** The five trader tools use the capability-qualified Trader Intelligence routes; where a venue does not expose a verb, they say so rather than returning invented empty data. Design the prompt so the agent reports the gap instead of substituting another venue. - **The API key is redacted from every error path**, so an agent that surfaces raw errors to a user cannot leak it. ## Reading results honestly Two properties belong in the agent's system prompt, because a model will otherwise smooth over both: - **Scores are informational, not advice.** Trader Intelligence output ranks what already happened. It is not a recommendation, and an agent should not present it as one. - **Some venues serve reconstructed books.** Those are faithful as price and are **not** executable depth. An agent comparing "liquidity" across venues without distinguishing them is comparing two different things — see [Capability-honest data](/guides/honest-data/). Have the agent carry `asOf` through to its answer. "62¢ as of 14:22Z" is a claim a user can check; "62¢" is not. ## Execution is a separate, removable surface `exec_quote`, `exec_prepare` and `exec_submit` are not among the thirty-three read tools — they belong to the ten execution and collateral tools, which **are registered by default** against the canonical isolated execution origin. Set `MCP_ENABLE_TRADE=false` to remove them entirely. Even armed, the split holds: `exec_quote` is read-only, `exec_prepare` builds an unsigned artifact and never signs, and `exec_submit` relays a client-signed artifact and defaults to dry-run unless `confirm` is exactly `true`. **No tool both builds and submits**, and no signing endpoint exists at all. The default set is not wholly read-only: `exec_prepare` and `exec_submit` carry `readOnlyHint: false`, and `exec_submit` is also marked destructive. A research-only agent should set `MCP_ENABLE_TRADE=false` so the ten execution and collateral tools are not registered. ## Related - [MCP server](/guides/mcp/) — configuration, the full tool list, guardrails - [Alert on scored trades](/guides/cookbook/smart-money-alert/) — the same data over REST - [Capability-honest data](/guides/honest-data/) — what the honesty fields mean --- # Screen markets across every venue > Sweep the catalog with cursor pagination, filter on normalized fields, and stop correctly. Source: https://docs.predictefy.com/guides/cookbook/screen-markets/ One request returns at most 100 markets. Screening the catalog means paginating, and doing it with the cursor rather than with a page count. ## Sweep `router` searches every venue at once; a venue id scopes it to one. ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchMarkets?query=election&status=active&limit=100&sort=volume" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` The parameters that matter for screening: | Parameter | Values | | ------------ | --------------------------------------------------- | | `status` | `active`, `inactive`, `closed`, `resolved`, `all` | | `sort` | `volume`, `liquidity`, `newest` | | `limit` | 1–100 | | `searchIn` | `title`, `description`, `both` | | `searchMode` | `lexical` (default), `semantic`, `hybrid` | `searchMode` is worth knowing: the default is a literal match. `semantic` ranks the venue's markets by title meaning (`voyage-3.5` vectors). Results are limited to markets whose titles have been embedded — the catalog embedding pass runs with the matcher (hourly), so brand-new markets can lag; `total` is the number of matches within the scan window. `hybrid` combines that ranking with lexical search. ```js async function screen({ query, status = 'active', limit = 100, maxPages = 20, venue = 'router' }) { const out = []; let cursor = null; let pages = 0; do { const url = new URL(`/api/${venue}/fetchMarkets`, BASE); url.searchParams.set('status', status); url.searchParams.set('limit', String(limit)); if (query) url.searchParams.set('query', query); if (cursor) url.searchParams.set('cursor', cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PREDICTEFY_API_KEY}` }, }); const body = await res.json(); if (!body.success) { // A cursor older than its TTL comes back as VALIDATION_ERROR. Restart the sweep. throw new Error(`${body.error.code}: ${body.error.message}`); } out.push(...body.data); cursor = body.page?.hasMore ? body.page.nextCursor : null; pages += 1; } while (cursor && pages < maxPages); return out; } ``` :::caution[Never compute a page count from `page.total`] `page` carries `limit`, `offset`, `total`, `hasMore` and `nextCursor`. `total` is nullable, and a null means **not counted** — not zero. A loop that stops when it has fetched `total` rows will stop immediately on a null and silently return one page. Drive the loop on `hasMore`, and stop when it is false. ::: ## Filtering on the normalized record Every market comes back in the same shape whichever venue served it. The fields worth screening on: | Field | Note | | --------------------------------- | ----------------------------------------------------- | | `sourceExchange` | The venue that served this row — **not** `venue` | | `volume` / `volume24h` | All-time and rolling; `volume24h` is nullable | | `liquidity` | Nullable | | `status` | Matches the filter vocabulary above | | `category` / `tags` | The venue's own vocabulary | | `canonicalCategory` / `canonicalTags` | Predictefy's cross-venue vocabulary, both nullable | | `outcomes` | The sides you can hold; books key on these | | `outcomes[].priceChange*` | Eight windows: `1m`, `5m`, `15m`, `1h`, `6h`, `24h`, `7d`, `30d` | | `outcomes[].coverage` | Tape coverage mechanism, interval, and 24h gap metrics | | `asOf` / `provenance` / `capabilities` | Required on every record — see below | ### Eight-window price changes and tape coverage For catalog markets served from the snapshot, Predictefy computes **eight price-change windows** on every outcome: - **Order-book tape** (`tob_ticks`): `priceChange1m`, `priceChange5m`, and `priceChange15m` are computed from the top-of-book tick tape with zero freshness tolerance. - **Candle history**: `priceChange1h`, `priceChange6h`, and `priceChange24h` are computed from 1-minute stored candles (falling back to 1-hour candles if needed). - **Official candles preferred**: `priceChange7d` and `priceChange30d` prefer 1-hour and 1-day official venue candles before falling back to 1-minute aggregations. Every window key is **always present** on snapshot-enriched market outcomes. A `null` value means **insufficient reference history** at or before that window boundary — **never zero**. A zero value is reserved for an honest, observed zero change. Each outcome also includes a **`coverage`** object (or `null` when uncomputed) describing tape provenance: - `mechanism` — `ws-lossless`, `ws-top20`, `rest-adaptive`, `rest-top-of-book`, or `synthetic-spot` - `tapeSince` — ISO timestamp when continuous tape recording began - `effectiveIntervalMs` — effective sampling interval in milliseconds - `gaps24h` — number of detected tape gaps over the trailing 24 hours - `lastGapAt` — ISO timestamp of the most recent gap, or `null` All eight windows are available as router-only stateless `filterMarkets` criteria keys (`priceChange1m` through `priceChange30d`) accepting `{ outcome, min?, max? }`. `filterEvents` is the same helper at event grain, with the same contract: it is a **pure stateless filter**, not a catalog query. You send `{ args: [events, criteria] }` with an array you already hold, and it returns the matching objects unchanged — it never fetches anything. Unknown criteria fields, and the non-serializable function form, answer `400`. Both are router-only; a non-router exchange answers `400`. Prefer `canonicalCategory` and `canonicalTags` when screening across venues: `category` is whatever the venue calls it, so filtering on it gives different results per venue. See [Categories & tags](/guides/categories-tags/). ```js const shortlist = rows .filter((m) => (m.volume ?? 0) > 50_000) .sort((a, b) => (b.volume ?? 0) - (a.volume ?? 0)) .slice(0, 25); ``` Note the `?? 0` on every nullable numeric. `volume24h` and `liquidity` are declared nullable, and a null sorts unpredictably if you do not handle it. ## The honesty fields `asOf`, `provenance` and `capabilities` are **required** on every market record — the spec marks them so. They are the difference between a screener that is right and one that looks right: - **`asOf`** — when the data was true. Show it. - **`provenance`** — where it came from. - **`capabilities`** — `read`, `trade`, `depth`, `history` for that record. Check `depth` before assuming you can size against a book, and read [Capability-honest data](/guides/honest-data/) for what each one does and does not promise. Do not filter venues with a hard-coded list of which ones have real books. That list changes; `capabilities` and [Venue coverage](/reference/venues/) do not go stale. ## Cost A catalog read is the cheapest call on the platform, but a sweep is many of them: 20 pages is 20 reads. Cap `maxPages`, and prefer a narrower `query` or a `category` filter over paginating the whole catalog. Current weights are in [Credits & billing](/guides/credits/). `fetchMarketsPaginated` exists as an offset-paginated alternative when you genuinely need to jump to a position rather than walk forward. ## Related - [Categories & tags](/guides/categories-tags/) — canonical vs venue-native vocabulary - [Identifiers](/guides/market-ids/) — which id each verb expects - [Compare one market across every venue](/guides/cookbook/compare-across-venues/) — from a shortlist to a comparison --- # Alert on scored trades > Poll the smart-money feed with keyset pagination, read the score honestly, and handle the venues that have no rows. Source: https://docs.predictefy.com/guides/cookbook/smart-money-alert/ The smart-money feed is a scored-trade feed. It ranks trades that have already happened; it is informational and is not a recommendation, and treating a score as a signal to copy is a misreading of what it measures. [Trader Intelligence](/reference/trader-intelligence/) is the reference. This page builds a watcher. ## Poll ```sh curl -s "$PREDICTEFY_API_URL/v1/traders/smart-money?minScore=80&window=day&limit=100" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` Every parameter is optional: | Parameter | Values | | ---------- | --------------------------------------------- | | `venue` | One Trader Intelligence venue | | `minScore` | 0–100 | | `market` | Exact market id | | `wallet` | Exact venue-scoped wallet | | `category` | `bot`, `whale`, `smart`, `fresh`, `fish` | | `window` | `day`, `week`, `month`, `all` (default `all`) | | `limit` | 1–100 | | `cursor` | Opaque `(ts, tradeId)` keyset cursor | Rows add `tradeScore`, `tradeFactors`, `scoreVersion`, `walletScoreAtTrade` and `categoryAtTrade` to the normal trader-trade fields. Under score version `t1` the factors are `walletScore`, `size`, `entry` and `timing`. ## Watch without re-alerting The feed is newest-first. Its cursor walks **backward** to rows older than the last row on the current page; it is not a forward watcher checkpoint. Start every poll without a cursor, then use the top-level `nextCursor` only to walk older pages until you reach the newest trade saved from the previous poll. ```js let initialized = false; let newestSeen = null; function tradeKey(trade) { return `${trade.venue}:${trade.ts}:${trade.tradeId}`; } async function poll() { let cursor = null; let newestInPoll = null; let reachedPrevious = false; const fresh = []; do { const url = new URL('/v1/traders/smart-money', BASE); url.searchParams.set('minScore', '80'); url.searchParams.set('window', 'day'); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PREDICTEFY_API_KEY}` }, }); const body = await res.json(); if (!body.success) { // TRADERS_UNSUPPORTED is a 400 and is not retryable — it is an answer. if (!body.error.retryable) throw new Error(`${body.error.code}: ${body.error.message}`); return; } if (newestInPoll === null && body.data[0]) newestInPoll = tradeKey(body.data[0]); for (const trade of body.data) { if (tradeKey(trade) === newestSeen) { reachedPrevious = true; break; } fresh.push(trade); } cursor = reachedPrevious ? null : (body.nextCursor ?? null); } while (initialized && cursor); // The first poll establishes a baseline instead of alerting the existing feed. if (!initialized) { initialized = true; newestSeen = newestInPoll; return; } for (const trade of fresh.reverse()) alert(trade); if (newestInPoll !== null) newestSeen = newestInPoll; } ``` Persist `newestSeen` across process restarts and keep the filter set unchanged. Reusing a backward cursor on the next poll skips trades that arrived after that cursor was minted; restarting from the newest page and stopping at the saved trade avoids both misses and duplicate alerts. ## Reading the score honestly - **`window=all` means all collected feed data.** No historical backfill is included — the feed starts when collection started, so an empty early window is a collection boundary, not a quiet market. - **`walletScoreAtTrade` and `categoryAtTrade` are the values as they were at the time of the trade**, not the wallet's current standing. Rendering a current score next to a historical trade attributes information to the trader that they did not have. - **`scoreVersion` matters.** Factors differ between versions, so scores are not comparable across them. Store the version with anything you persist. - **A listed capability is not a claim that rows exist.** Scored-trade coverage requires activity from that venue; a supported venue can legitimately return nothing. ## The errors are answers | Response | Meaning | | ---------------------------- | ----------------------------------------------------- | | `400 TRADERS_UNSUPPORTED` | That venue does not support that verb. Not retryable. | | `404 TRADER_NOT_FOUND` | Unknown wallet. | | `404` on any `/v1/traders/*` | Trader Intelligence is unavailable. | `TRADERS_UNSUPPORTED` names the verb and the venue — for example, market holders being unsupported on a given venue. Do not fall back to another venue's data to fill the gap; the honest render is that this venue does not expose it. See [Capability-honest data](/guides/honest-data/). ## Through an agent instead The same surface is available as MCP tools — `get_smart_money`, `get_market_traders`, `get_market_holders`, `get_leaderboard`, `get_wallet_profile` — each capped at 100 rows, with `get_wallet_profile` under a 50 KB response budget. See [MCP server](/guides/mcp/). ## Related - [Trader Intelligence](/reference/trader-intelligence/) — every route, parameter and venue capability - [MCP server](/guides/mcp/) — the same data as agent tools - [Capability-honest data](/guides/honest-data/) — why an unsupported verb is an answer --- # Stream a live order book > Subscribe over WebSocket, handle full-state frames, and survive the non-fatal errors. Source: https://docs.predictefy.com/guides/cookbook/stream-order-book/ REST gives you a book at a moment. The WebSocket gives you the book as it changes, and it does so with one upstream venue subscription shared across every client watching that market — so scaling consumers does not burn a venue's rate limits. The protocol reference is [Streaming](/guides/streaming/). This page is the working loop. ## Connect and subscribe Connect to `/v1/stream` on the WebSocket origin shown in your dashboard, authenticating with the same `pk_live_…` key as REST. ```js const ws = new WebSocket(`${WS_ORIGIN}/v1/stream`, { headers: { Authorization: `Bearer ${process.env.PREDICTEFY_API_KEY}` }, }); ws.on('open', () => { ws.send( JSON.stringify({ op: 'subscribe', channel: 'orderbook', venue: 'polymarket', marketId: OUTCOME_ID, // venue-native — see below }), ); }); ``` :::caution[`marketId` is the venue-native id, not the unified one] It is passed straight through to the venue. For Polymarket it is the CLOB asset/token id, which the unified API returns as the outcome's **`outcomeId`** — the same id `fetchOrderBook` takes. Sending a unified `marketId` here produces a subscription the venue has nothing for: an empty stream rather than an error. [Identifiers](/guides/market-ids/) covers the general rule. ::: ## Handle the frames ```js ws.on('message', (raw) => { const frame = JSON.parse(raw); switch (frame.type) { case 'subscribed': case 'unsubscribed': return; // Both carry the FULL book, never deltas. Replace state; do not merge. case 'snapshot': case 'update': book.set(frame.marketId, frame.data); return; case 'trade': onTrade(frame.venue, frame.marketId, frame.data); return; case 'error': // Non-fatal. The socket stays open — do NOT reconnect on these. console.warn(`${frame.code}: ${frame.message}`, frame.venue, frame.marketId); return; } }); ``` Two properties decide the shape of this loop: **`snapshot` and `update` both carry the full book.** `snapshot` is the first frame after subscribing and after backpressure coalescing; `update` marks live ticks. Neither is a delta, so replace your local state rather than merging into it. Merging works until the first coalesced snapshot, then drifts. **Protocol errors are non-fatal.** `BAD_MESSAGE`, `NOT_SUPPORTED`, `NOT_SUBSCRIBED` and `SUBSCRIPTION_LIMIT` arrive as `type: "error"` frames and the socket **stays open**. Treating them as disconnects produces a reconnect loop against a working connection. Book frames carry `bids` and `asks` as `{ price, size }`, with prices as probabilities in `[0, 1]`. ## What you cannot subscribe to `NOT_SUPPORTED` is a real answer about the world, not a failure: - Venues without a public stream answer it rather than serving a synthetic one. - **Trade streaming is capability-qualified, not native-only.** Native channels stream directly; Rain, XO, and PRED use an emulated chain-scan tape with disclosed provenance. A venue with neither lane answers `NOT_SUPPORTED`. Order-book support still does not imply trade support. - `subscribeAll` needs a venue-wide firehose upstream; venues without one answer `NOT_SUPPORTED`. Do not fall back to another venue when you get this. The honest answer is that this venue does not offer that stream — see [Capability-honest data](/guides/honest-data/). ## Subscription limits Active logical subscriptions are capped by plan: **2** on Free, **20** on Builder, **100** on Pro, **500** on Scale. A lower service safety cap can also apply. Exceeding either returns a non-fatal `SUBSCRIPTION_LIMIT` — the socket survives and the subscription simply does not exist. Track your own count; a subscribe that "succeeded" because the socket stayed open is not the same as a subscribe that took effect. Wait for the `subscribed` ack. ## Falling back to REST For a point-in-time book, or for any venue without a stream, `fetchOrderBook` takes an `outcomeId` and a `limit` of up to 1000 depth levels. It also reads the archive: `at` for the nearest stored snapshot to a time, `since`/`until` for a range, and `side` to return only bids or only asks. ## Related - [Streaming](/guides/streaming/) — the full protocol: auth, close codes, feed tickers - [Identifiers](/guides/market-ids/) — venue-native versus unified ids - [Venue coverage](/reference/venues/) — which venues have a real book at all --- # Errors > The error envelope, the codes you will actually see, and which ones are worth retrying. Source: https://docs.predictefy.com/guides/errors/ Every error — 4xx and 5xx alike — uses one shape: ```json { "success": false, "error": { "code": "INSUFFICIENT_CREDITS", "message": "…", "retryable": false, "requestId": "req_01J..." } } ``` `code`, `message`, `retryable`, and `requestId` are always present. Treat everything else as optional. **Branch on `code`, not on the HTTP status.** Several codes share a status — `NOT_SUPPORTED` arrives as both 400 and 501 depending on where the gap is — and the code is the specific one. Use `retryable` to decide whether to try again at all. ## The codes you will see | HTTP | Code | Meaning | Retry | | ------- | ------------------------------------------------------------ | --------------------------------------------------------------------- | -------------------- | | 400 | `VALIDATION_ERROR` | Bad or missing parameters | No | | 400 | `CATALOG_QUERY_TOO_BROAD` | The filter matches too much to serve; narrow it | No | | 400 | `BRIDGE_UNSUPPORTED` | The venue has no bridge-in route: custody-only or fiat | No | | 400 | `TRADERS_UNSUPPORTED` | The venue or requested trader verb is not served | No | | 400 | `SQL_QUERY_FAILED` | The analytical query failed to execute | No | | 401 | `UNAUTHORIZED` / `AUTHENTICATION_ERROR` | Missing, unknown, or revoked key | No | | 402 | `INSUFFICIENT_CREDITS` | Balance below the endpoint weight | After top-up | | 403 | `PLAN_REQUIRED` / `PERMISSION_DENIED` | The plan does not include this route or window | After upgrade | | 403 | `SCOPE_MISSING` | The key lacks the required scope, e.g. `trade` | No | | 404 | `MARKET_NOT_FOUND` / `EVENT_NOT_FOUND` / `OUTCOME_NOT_FOUND` | Unknown record | No | | 404 | `EXCHANGE_NOT_AVAILABLE` / `VENUE_NOT_AVAILABLE` | Unknown or unserved venue | No | | 404 | `CLUSTER_NOT_FOUND` | Cluster detail lookup missed | No | | 404 | `SNAPSHOT_NOT_FOUND` | No stored order-book snapshot in that archive window | No | | 404 | `ROUTE_NOT_FOUND` | The route is not mounted | No | | 409 | `API_KEY_LIMIT` | The account's active-key cap is reached | After revoking a key | | 422 | `BRIDGE_NO_ROUTE` | No route for that source/amount; change source chain, token or amount | No | | 429 | `RATE_LIMITED` / `RATE_LIMIT_EXCEEDED` | Request window exceeded | Yes, with backoff | | 400/501 | `NOT_SUPPORTED` / `ACCOUNTS_UNSUPPORTED` | An honest capability gap | No | | 502 | `VENUE_RELAY_FAILED` | The venue relay failed before returning a usable result | Yes, with backoff | | 502/503 | `BRIDGE_UPSTREAM` | Provider rejected or failed | Yes, with backoff | | 503 | `BRIDGE_PROVIDER_UNAVAILABLE` | Provider not configured or down | Yes, with backoff | | 503 | `CATALOG_UNAVAILABLE` / `HISTORY_UNAVAILABLE` | The lane is temporarily unavailable | Yes, with backoff | | 503 | `MATCHES_UNAVAILABLE` | The cross-match lane is not enabled on this deployment | Yes, once enabled | | 503 | `ARBITRAGE_UNAVAILABLE` | Executable-arbitrage lane disabled on this deployment | Yes, once enabled | | 503 | `PLATFORM_UNAVAILABLE` / `BILLING_UNAVAILABLE` | Temporary outage | Yes, with backoff | | 503 | `SQL_TIMEOUT` | The analytical query exceeded its time limit | Yes, with backoff | | 5xx | `INTERNAL` / `NETWORK_ERROR` | Unexpected failure or transport error | Yes, with backoff | `MATCHES_UNAVAILABLE` and `ARBITRAGE_UNAVAILABLE` are the two codes above that are about a deployment rather than a request. The six cross-match verbs — `fetchMarketMatches`, `fetchMatchedMarkets`, `compareMarketPrices`, `fetchHedges`, and the deprecated `fetchMatches` / `fetchMatchedPrices` aliases — are always mounted, so an unenabled lane answers an honest `503` instead of a `404`. It is marked retryable because enabling the lane is a deployment change, not something a caller can fix by retrying now, and it is deliberately raised **before** any credit is debited: a dark lane never charges you and never answers `402`. The `/api/{exchange}/fetchArbitrage` route is always mounted the same way and stays dark with `ARBITRAGE_UNAVAILABLE` until `READS_ENABLE_ARBITRAGE` is set and the live order-book fetcher is wired through `READS_ENABLE_ORDERBOOK`. It is likewise retryable and raised before any credit is debited for the same deployment-change reason. The table includes runtime codes that the generated `ErrorDetail` enum in the [API reference](/api/) does not yet list. Branch on the response's `code` and `retryable` fields. ## NOT_SUPPORTED is not a failure `NOT_SUPPORTED` means the venue does not expose that capability at all — no public trades tape, no per-address order list. It is a correct answer about the world. Do not retry it, do not fall back to a different venue silently, and do not render it as an empty result. An empty array means "ran and found nothing"; `NOT_SUPPORTED` means "cannot run here". See [Capability-honest data](/guides/honest-data/). ## Retrying Retry only when `retryable` is `true`, regardless of HTTP status. Current retryable errors include `429`, upstream and relay failures returned as `502`, and temporary `503` failures. Errors marked non-retryable will fail identically on the second attempt. `INSUFFICIENT_CREDITS` is the one that looks retryable and is not — the balance will not change because you asked again. Surface it and stop. For backoff shape, jitter, and why writes are never auto-retried, see [Rate limits & retries](/guides/rate-limits/). ## Typed errors in the SDK The [TypeScript SDK](/guides/sdk/) throws typed subclasses of `PredictefyError`, so you can branch on the class instead of parsing strings. The server's `code` and `message` are preserved on the thrown error either way. ```ts import { InsufficientCreditsError, NotSupportedError } from '@predictefy/sdk'; try { await client.gemini.fetchTrades(marketId); } catch (err) { if (err instanceof NotSupportedError) return renderUnavailable(); if (err instanceof InsufficientCreditsError) return renderTopUp(); throw err; } ``` --- # Events & series > How markets group into events and series, and which venues have the concept at all. Source: https://docs.predictefy.com/guides/events-series/ Three levels of grouping exist in the catalog, and confusing them is a common source of integration bugs. [Prediction markets, briefly](/guides/prediction-markets/) defines the terms; this page covers the verbs. | Level | What it is | Verbs | | ------ | ---------------------------------- | ------------------------------------------------- | | Market | One question, holding its outcomes | `fetchMarkets`, `fetchMarket` | | Event | A group of related markets | `fetchEvents`, `fetchEvent`, `fetchEventMetadata` | | Series | A recurring group of events | `fetchSeries` | Order books and candles key on the **outcome**, one level below all of these. Selecting an outcome is always the last step before asking for depth or history. ## Events An event ties related markets together — an election with a market per candidate, or a fixture with a market per result. Reading at the event level is how you get the whole question rather than one slice of it. ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchEvents?query=election&status=active&limit=10" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` `fetchEvents` accepts the same list parameters as `fetchMarkets` — `limit`, `cursor`, `status`, `query`, `category` — and paginates the same way. ## Series A series is a **recurring** grouping: the same question asked on a schedule, such as a monthly rate decision. `fetchSeries` is catalog-derived rather than venue-published. Not every venue has the concept. On a venue without it, `fetchSeries` returns an **empty list rather than an error** — the request succeeded and the answer is that this venue does not organise markets that way. Do not treat an empty series list as a failure. ## Venue-native event metadata `fetchEventMetadata` returns the venue's own metadata for a single event. It is **Kalshi-only and not available on `router`** — it exposes a venue-native structure that has no cross-venue equivalent, so there is nothing sensible for the router to union. Calling it on another venue returns an honest `NOT_SUPPORTED` rather than an empty object. ## Choosing the right level - Use **markets** when you want one question and its prices. - Use **events** when you want a whole contest, and when you want to avoid showing a user one candidate's market without its siblings. - Use **series** to find recurring questions over time, then read the events within them. - Use [matched clusters](/guides/cross-venue/) when you want the same real-world question across different venues — that is a different relationship from an event, and is Predictefy's own matching rather than a venue's grouping. --- # Capability-honest data > How market records expose asOf, provenance, and capabilities — and why NOT_SUPPORTED is an answer. Source: https://docs.predictefy.com/guides/honest-data/ Every `UnifiedMarket` record carries three fields that describe the data rather than the market. They exist because a normalized interface over 16 served venues can either hide the differences between them or state them, and hiding them produces integrations that are quietly wrong. ```json { "marketId": "…", "title": "…", "asOf": "2026-07-03T09:15:00.000Z", "provenance": { "source": "overlay" }, "capabilities": { "read": true, "trade": false, "depth": true, "history": false } } ``` The guarantee is specific to market records. `UnifiedSeries` omits these fields. `OrderBook` makes `asOf` and `provenance` optional and has no `capabilities`. `PriceCandle` instead exposes `source`, `sourceType`, `quality`, and `isTrueCandle`. ## asOf — when this was true On a market record, `asOf` is the moment the data was captured, not the moment you asked. Other response families can expose it optionally. When present, it distinguishes a snapshot taken seconds ago from one taken minutes ago. Use it rather than your own clock when displaying freshness. A timestamp you generate on receipt describes your request, not the data. ## provenance — where it came from On market records, `provenance.source` records how the value was obtained. The closed API vocabulary is `venue-rest`, `predictefy-live`, `fixture`, `overlay`, `archive`, `predictefy-store`, `glide`, and `lifi`; individual response schemas narrow the values they can emit. Live catalog reads currently report `overlay` on both the market record and response `meta`. This matters when values disagree. Two reads of the same market with different `asOf` and different `provenance` are not a contradiction; they are two different observations, and provenance tells you which pipeline produced each. ## capabilities — what this venue actually supports On market records, `capabilities` is a per-venue map: `read`, `trade`, `depth`, `history`. It is the field to branch on before assuming a verb will work. The flags are **conservative by design**. `history: false` means a history lane has not been proven for that venue, not that no data could ever exist. `trade: false` on a catalog record does not mean trading is impossible — public execution availability is documented separately per venue in [Trading & execution](/guides/trading/). For the full per-verb picture, read the venue's `has` map or the [coverage matrix](/reference/venues/). `has.buildOrder` and `has.submitOrder` report implemented isolated-server lanes; use `/v1/exec/venues` for the lanes armed in the current execution deployment. ## NOT_SUPPORTED is an answer When a venue genuinely does not expose something — no public trades tape, no per-address order list — the API returns `NOT_SUPPORTED` rather than an empty array. This distinction is the point: - An **empty array** means the query ran and found nothing. A market with no trades today returns an empty tape. - **`NOT_SUPPORTED`** means the query cannot run here. The venue has no tape at all. Collapsing the two loses real information. An integration that treats `NOT_SUPPORTED` as "no results" will report "no recent trades" for a venue that has never published a single one, which is a different and more misleading statement. Handle it explicitly: ```ts try { const trades = await client.gemini.fetchTrades(marketId); render(trades); // may legitimately be empty } catch (err) { if (err instanceof NotSupportedError) { renderUnavailable('This venue does not publish a public trades tape.'); } else { throw err; } } ``` ## Synthetic books A venue without an order book — an automated market maker, or a pari-mutuel pool — still returns a book-shaped response so that one integration works everywhere. That response is labelled **synthetic**. A synthetic book is a faithful representation of price. It is **not executable depth**, and must never be presented as orders a user could fill against, or fed into a sizing calculation as though it were. [Venue coverage](/reference/venues/) records which venues have a real book. ## The same discipline applies to cross-venue output Price differences between venues are reported as **indicative price discrepancies** — observed gaps, not opportunities. Only `fetchArbitrage` performs a live executable assessment, against live asks, open status, real depth, the fee model, and resolution-equivalence. See [Cross-venue data](/guides/cross-venue/). The rule underneath all of this is the same: the API states what it knows and how well it knows it, and expects the integration to preserve that rather than flatten it. --- # Identifiers > Which id each verb expects, where venue-native ids surface, and why the wrong one fails quietly. Source: https://docs.predictefy.com/guides/market-ids/ Passing the right identifier to the right verb is the most common early integration mistake, because several of them look alike and one of them is not ours. | Id | Identifies | Used by | | ----------- | --------------------------- | --------------------------------------------------- | | `marketId` | One question | `fetchMarket`, `fetchRelatedMarkets`, `fetchHedges` | | `outcomeId` | One side of one question | `fetchOrderBook`, `fetchOHLCV` | | `eventId` | A group of markets | `fetchEvent`, event filters | | `slug` | Human-readable market alias | Selected market lookup verbs; see below | Router endpoints that resolve an anchor require canonical `venue:marketId`, for example `polymarket:2252244`; a bare venue-native id does not resolve. ## Books and candles key on the outcome `fetchOrderBook` and `fetchOHLCV` take an **`outcomeId`**, not a `marketId`. A binary market has one book per side, so a market id alone is ambiguous — there is no single book to return. The normal sequence is therefore two calls, not one: ```sh # 1. Find the market, and read its outcomes. curl -s "$PREDICTEFY_API_URL/api/polymarket/fetchMarkets?query=fed&limit=1" \ -H "Authorization: Bearer pk_live_YOUR_KEY" # 2. Use an outcomeId from that response. curl -s "$PREDICTEFY_API_URL/api/polymarket/fetchOrderBook?outcomeId=OUTCOME_ID" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` Every market record carries its outcomes inline, so the second call needs no extra lookup. ## Venue-native ids Some surfaces require the venue's **own** identifier rather than a Predictefy one. The clearest case is [streaming](/guides/streaming/): WebSocket subscriptions are made with native ids, because the subscription is proxied to the venue's own feed. Sub-minute history (`1s`, `5s`, `10s`, and `30s`) has the same property: it requires a venue-native `outcomeId`. Ordinary stored-candle queries accept either canonical or venue-native outcome identity. [Historical data](/guides/history/) documents coverage per venue. The rule: **follow each verb's identifier contract.** REST catalog, trader, and history surfaces accept different combinations; live-feed subscriptions use venue-native ids. Catalog-indexed subscription misses return a non-fatal `MARKET_NOT_FOUND`. Silent acknowledgement remains possible only on native-id lanes the validator cannot index, such as Polymarket. ## Slugs A `slug` is a readable alias accepted by `fetchMarket`, `fetchRelatedMarkets`, and `fetchHedges`. It is convenient in URLs and hand-written queries, but it is not a universal replacement for a `marketId`; trader surfaces, for example, do not resolve `metadata.slug`. Prefer `marketId` for anything stored. Slugs can come from venue-provided fields or venue-specific derivation, so do not assume they are title-derived or stable. A persisted slug may stop resolving, while the `marketId` will. ## Ids do not cross venues There is no global identifier for a real-world question. The same event on two venues has two unrelated `marketId`s, and neither is convertible into the other. Relating them is what [matched clusters](/guides/cross-venue/) do, and the result is a judgement carrying a similarity score rather than an identity claim. Do not build a lookup that assumes an id from one venue means anything on another. --- # Market relationships > Verified subset/superset edges and indicative hedge candidates — including what each one deliberately does not claim. Source: https://docs.predictefy.com/guides/market-relationships/ Two router-only verbs describe how markets relate to one another. Both are deliberately narrow, and the limits are the important part of this page. | Verb | Returns | Claim strength | | --------------------- | --------------------------------------------- | --------------------------------------------- | | `fetchRelatedMarkets` | Verified subset/superset outcome implications | Strong — each edge was verified | | `fetchHedges` | Hedge **candidates** for one market | Indicative — a starting point, not a position | Both resolve a single anchor market by `marketId` or `slug`, and both are available on `router` only. Calling either on a venue segment returns an error rather than a partial answer, because the relationship is cross-venue by definition. ## Related markets ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchRelatedMarkets?marketId=polymarket:MARKET_ID&limit=20" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` An edge means one market's outcome **implies** another's: a subset or superset relationship, verified before it was stored. "Will inflation exceed 4%?" is a superset of "Will inflation exceed 5%?" — the second implies the first. What it deliberately excludes: - **Same-event siblings are not relations.** Two candidates in one election are not a subset or superset of each other, and are not upgraded into a relation claim. Use [`fetchEvents`](/guides/events-series/) for siblings. - **Similarity is not implication.** Markets that merely read alike are matched into [clusters](/guides/cross-venue/), which is a different and weaker statement. Coverage is bounded by what has been verified rather than by what exists. A market with no edges returns an empty list — that means no verified relationship was found, not that none could exist. ## Hedges ```sh curl -s "$PREDICTEFY_API_URL/api/router/fetchHedges?marketId=polymarket:MARKET_ID&limit=10" \ -H "Authorization: Bearer pk_live_YOUR_KEY" ``` :::caution[Support is limited, and the response says so] `fetchHedges` currently returns **same-proposition cross-venue co-members** — markets matched as the same underlying question on a different venue, where holding the opposite side is a plausible offset. It does not yet apply the subset/superset filtering a full hedge surface would need. Those typed relations do exist — the matcher produces verified directional `subset`/`superset` edges, and `fetchRelatedMarkets` above serves them — but `fetchHedges` reads cluster co-membership only and never consults them. This is an honest subset, not the finished feature. ::: A hedge candidate is **not** a hedge. Predictefy has not checked that the two markets resolve on equivalent criteria, that there is depth at the size you want, or that fees and gas leave the offset intact. Those are the same gates that separate an indicative price discrepancy from arbitrage — see [Cross-venue data](/guides/cross-venue/). Treat the response as a shortlist to evaluate, and do the resolution-equivalence check yourself before putting capital behind it. ## Costs Both are cross-venue reads. See [Credits & billing](/guides/credits/) for the current weights; they are priced above catalog reads because each one walks stored relationship data rather than a single venue's catalog. --- # Prediction markets, briefly > The domain vocabulary the rest of these docs assumes — contracts, outcomes, resolution, and why venues disagree. Source: https://docs.predictefy.com/guides/prediction-markets/ This page defines the vocabulary the rest of the documentation uses. If the terms are already familiar, go to the [Quickstart](/quickstart/). ## The contract A prediction market is a market in **contracts that settle on a real-world outcome**. A contract pays out a fixed amount if the outcome happens and nothing if it does not. Because the payout is fixed, the price is the interesting part. A contract that pays \$1 if an event occurs trades between \$0 and \$1, and its price is commonly read as the market's implied probability — 62¢ implies roughly a 62% chance. That reading is a useful shorthand rather than a precise claim: the price also carries the cost of tying up capital until settlement, the fees involved, and whatever risk appetite the participants have. Most contracts are **binary**: the outcome either happened or it did not. Where a question has several possible answers, venues generally express it as a set of contracts, one per answer, rather than a single multi-way instrument. ## Market, event, outcome These three words appear constantly in this API and are not interchangeable. - An **outcome** is the thing you actually hold a position in — one side of one question. "Yes" and "No" are two outcomes. - A **market** is the question, holding its outcomes together. "Will the Fed cut rates in March?" is a market with two outcomes. - An **event** groups related markets. A single election has many markets — one per candidate, or per state, or per margin — and the event is what ties them together. Order books and price history key on the **outcome**, not the market, because a market has one book per side. Selecting an outcome before asking for depth or candles is the most common early integration step. See [Events & series](/guides/events-series/) for how the grouping behaves across venues. ## Resolution **Resolution** is the point at which a market's answer becomes final and contracts pay out. It is the least standardised part of prediction markets. Venues differ on who decides, on what evidence, and how disputes are handled — some resolve from a named official source, some from an oracle, some from a committee. Two venues can carry what looks like the same question and settle it differently, because their resolution criteria differ in ways the title does not reveal. This is why a price difference between two venues is not automatically a mispricing: if the two contracts can settle differently, they are not the same contract, and the gap may be entirely rational. Predictefy treats resolution-equivalence as something to be **checked**, not assumed — see [Cross-venue data](/guides/cross-venue/). ## Why venues disagree Prices for the same real-world question routinely differ across venues: - **Different participants.** A venue's users are not a random sample, and their collective view is not either. - **Different liquidity.** A thin book moves further on the same order, so its price can drift from a deeper venue's without anyone being wrong. - **Different costs.** Fees, gas, and the capital cost of holding to settlement are not identical across venues, and they are priced in. - **Different resolution criteria.** As above — sometimes the questions only look alike. Predictefy reports these gaps as **indicative price discrepancies**: observed differences, not opportunities. Calling a gap _arbitrage_ requires proving it survives live asks on both legs, open market status, real depth at the size you want, the actual fee model, and a resolution-equivalence check. That is a separate and much stronger claim, and only [`fetchArbitrage`](/guides/cross-venue/) makes it. ## Order books and liquidity Most venues here run an **order book**: resting bids and asks, and a trade when they meet. The best bid and best ask bracket the current price, and the gap between them is the spread. Some venues do not run a book at all — they use an automated market maker or a pari-mutuel pool, where price is a function of what has been staked rather than of resting orders. The API still returns a book-shaped response for those venues so that one integration works everywhere, but it labels the book **synthetic**. A synthetic book is a faithful representation of price; it is **not** executable depth, and should never be treated as orders you could fill against. [Venue coverage](/reference/venues/) records which venues have a real book. ## What this means for an integration Three rules follow, and the rest of the documentation assumes them: 1. **Check capability before assuming support.** Venues genuinely differ. A venue with no public trades tape answers `NOT_SUPPORTED` — an honest answer about the world, not an error to route around. 2. **Read the honesty fields defined for that record family.** `UnifiedMarket` carries `asOf`, `provenance`, and `capabilities`. Other response families use type-specific contracts: `PriceCandle`, for example, carries `source`, `sourceType`, `quality`, and `isTrueCandle` instead. 3. **Do not equate similar-looking markets.** Matching across venues is a judgement, and Predictefy exposes it as one — with a similarity score, not a promise. Next: [Quickstart](/quickstart/) for your first request, or [Venue coverage](/reference/venues/) for what each venue supports. --- # Rate limits & retries > Per-plan request windows, what a 429 actually means, and how to back off correctly. Source: https://docs.predictefy.com/guides/rate-limits/ Every API key is rate limited per plan. Exceeding the window returns `429 RATE_LIMITED` with `retryable: true` in the standard [error envelope](/quickstart/#4-the-error-envelope). Rate limiting and [credits](/guides/credits/) are separate controls. The limiter caps how _fast_ you may call; credits cap how _much_ you may call in total. A request can pass the limiter and still fail with `402 INSUFFICIENT_CREDITS`, or fail the limiter without ever being charged. ## Limits by plan | Plan | Requests | API keys | Concurrent WebSocket streams | | ---------- | ------------ | ---------- | ---------------------------- | | Free | 60 / min | 1 | 2 | | Builder | 300 / min | 3 | 20 | | Pro | 3,000 / min | 10 | 100 | | Scale | 10,000 / min | 25 | 500 | | Enterprise | negotiated | negotiated | negotiated | The limiter uses a **fixed 60-second window per API key**, not per account and not per endpoint. Requests on both sides of a bucket boundary can arrive back-to-back. Two keys on one account each get the full allowance; one key spread across ten processes shares a single allowance. Enterprise plans and individual keys can carry a bespoke override. An override is applied as a **per-second** window rather than per-minute — so a key provisioned at 50/s is allowed 50 in any given second, not 3,000 spread freely across a minute. ## What the response tells you ```json { "success": false, "error": { "code": "RATE_LIMITED", "message": "rate limit exceeded — retry shortly", "retryable": true } } ``` :::note[429 responses include `Retry-After`] Every handled `429` includes `Retry-After` as a whole number of seconds, rounded up. The TypeScript SDK consumes it. The API does not currently return `X-RateLimit-Remaining` or other rate-limit quota headers. ::: ## Backing off Honor the server-supplied `Retry-After` delay. If that delay is unavailable to your retry wrapper, use exponential backoff with jitter. A client retrying every second spends its next allowance on failures. ```ts async function withRetry(call: () => Promise, attempts = 5): Promise { for (let attempt = 0; ; attempt++) { try { return await call(); } catch (err) { const code = (err as { code?: string }).code; // Only these are worth retrying. A 400 or 401 will fail identically forever. if (code !== 'RATE_LIMITED' && code !== 'PLATFORM_UNAVAILABLE') throw err; if (attempt >= attempts - 1) throw err; // 1s, 2s, 4s, 8s … plus jitter so parallel workers do not resynchronise. const backoff = 2 ** attempt * 1000 + Math.random() * 1000; await new Promise((r) => setTimeout(r, backoff)); } } } ``` The [TypeScript SDK](/guides/sdk/) retries `GET` requests once on 429 by default (`retryOn429`). **Writes are never auto-retried** — a submit or cancel that may have reached the venue must not be replayed by a client library. For those, retry deliberately and send an `Idempotency-Key`; see [Trading & execution](/guides/trading/). ## Which errors to retry | Code | HTTP | Retry? | | --------------------------------------------- | --------- | ------------------------------------------------------ | | `RATE_LIMITED` | 429 | Yes — back off, then retry | | `PLATFORM_UNAVAILABLE` | 503 | Yes — back off, then retry | | `CATALOG_UNAVAILABLE` / `HISTORY_UNAVAILABLE` | 503 | Yes — back off, then retry | | `INSUFFICIENT_CREDITS` | 402 | No — retrying cannot succeed until the balance changes | | `VALIDATION_ERROR` | 400 | No — fix the request | | `UNAUTHORIZED` | 401 | No — fix the key | | `NOT_SUPPORTED` | 400 / 501 | No — an honest capability gap, not a failure | `retryable` is present on every error and is the field to branch on. Treat it as authoritative over the HTTP status. ## Staying under the limit - **Prefer cursors over parallel offset pages.** Following `nextCursor` keeps one request in flight; twenty parallel offset pages spend twenty of the allowance in one second. - **Batch where a batch verb exists.** `fetchOrderBooks` takes many outcomes in one request. Note it is [priced by items](/guides/credits/), so it saves allowance rather than credits. - **Stream instead of polling.** A [WebSocket subscription](/guides/streaming/) delivers book and trade updates without consuming the request window at all. Polling a book every second on Free spends the entire minute allowance on one market. - **Cache what does not move.** Venue capability maps (`has`) and taxonomy (`fetchCategories`, `fetchTags`) change rarely; re-fetching them per request is pure overhead. - **Spread scheduled work.** Offset cron jobs by a random delay so batch runs do not collide with each other or with interactive traffic. ## When rate limiting itself is degraded If the limiter's backing store is unreachable, most routes fail open at this layer and credits remain the spend backstop. Only the zero-credit billing-session routes — checkout, subscribe, and portal — fail closed with `503 PLATFORM_UNAVAILABLE`. This prevents unlimited Stripe sessions while the limiter is unavailable; other writes, including webhook management, are not in that guarded set. No special handling is required. A burst of `503`s on those billing-session routes while other traffic continues is recognisable as designed behaviour rather than a partial outage. --- # Support > Where to get help, what to include in a report, and which surfaces answer for themselves. Source: https://docs.predictefy.com/guides/support/ ## Contact Email [support@predictefy.com](mailto:support@predictefy.com). Account, billing, and credit-balance questions all go to the same address. Do not include an API key in a support message. If a key may have been exposed, revoke it at [portal.predictefy.com/keys](https://portal.predictefy.com/keys) first — the raw value is shown once and stored only as a hash, so revoking and re-creating is the only remedy. ## Before reporting a bug Most reports resolve faster with four things, and three of them come back in the response you already have: 1. **The request** — verb, exchange segment, and parameters, with the key redacted. 2. **The `code` from the error envelope**, not just the HTTP status. `code` is more specific and is what identifies the path taken. 3. **`asOf` and `provenance`** from the response. These say when the data was captured and which pipeline produced it — the two things that distinguish stale data from wrong data. 4. **Whether it reproduces.** One occurrence and a consistent one need different investigations. ## Things that are answers, not faults Three responses are commonly reported as bugs and are working as designed: - **`NOT_SUPPORTED`** — the venue does not expose that capability. It is a statement about the world, not a failure. See [Capability-honest data](/guides/honest-data/). - **A synthetic order book** — venues without a real book return a book-shaped response labelled synthetic. It is price, not executable depth. - **A price gap between venues** — reported as an indicative price discrepancy. Venues disagreeing is normal market behaviour; see [Cross-venue data](/guides/cross-venue/). For `429` and `503`, check [Rate limits & retries](/guides/rate-limits/) first — both are retryable and both have a documented backoff. ## Checking what the API says about itself Several answers are available without asking anyone: - The venue's `has` map reports its own capabilities per verb. - [Venue coverage](/reference/venues/) records book type, trades tape, and history per venue. - The [API reference](/api/) is generated from the OpenAPI contract that gates the implementation, so it cannot drift from the served routes. ## For agents `/llms.txt` is a self-contained brief written to be pasted into an AI agent, and `/llms-full.txt` is the full corpus. Every guide page is also available as raw Markdown at its own URL plus `.md`. --- # Echoed build results > The byte-compatible legacy echoed-build-result variant, and which lanes accept it — Polymarket V2, Opinion, and Predict.fun. Source: https://docs.predictefy.com/guides/trading/echoed-build-results/ Polymarket, Opinion, and Predict.fun may echo their venue SDK build result at the top level or nested under `buildResult`. The server validates the venue-specific shape, re-derives the EIP-712 `structHash`, and stores only the clean subset needed for submission. | Field | Venue scope | Type | | -------------------------------------- | ---------------------------- | ----------------------------------------------- | | `order.salt` | all three | `uint256` decimal string | | `order.maker` / `signer` | all three | EVM address | | `order.tokenId` | all three | `uint256` decimal string | | `order.makerAmount` / `takerAmount` | all three | `uint256` decimal string (atomic) | | `order.expiration` | all three | `uint256` decimal string | | `order.side` | all three | venue BUY/SELL form | | `order.signatureType` | all three | see per-venue rules below | | `order.timestamp` | Polymarket V2 only | `uint256` Unix milliseconds | | `order.metadata` / `builder` | Polymarket V2 only | bytes32 hex | | `order.taker` / `nonce` / `feeRateBps` | Opinion and Predict.fun only | venue V1 fields; rejected for Polymarket V2 | | `domain` | all three | `{ name, version, chainId, verifyingContract }` | | `structHash` | all three | 32-byte hex digest | | `exchangeAddress` / `currencyAddress` | all three | EVM addresses | `currencyDecimal` is shape-validated (integer 0–36) but is **never** the notional divisor. The divisor is server-derived — from the signed V2 `verifyingContract` on Polymarket, from the pinned collateral on Opinion and Predict.fun — because a client-declared decimals value is not part of the signed order and could otherwise be inflated to evade spend caps. **Polymarket V2** (Polygon, chain 137) additionally requires: - `order.signatureType` must be `'0'` (EOA). A Proxy or Safe order is rejected: `polymarket signatureType must be EOA (0) — only client-signed EOA orders are accepted (no Proxy/Safe)`. `maker` must equal `signer`; V2 has no signed `taker` field. - The domain version must be `2`, and the complete 11-field V2 signed order is required. Supplying V1-only `taker`, `nonce`, or `feeRateBps` is rejected. - `orderType`: one of `GTC`, `GTD`, `FOK`, `FAK` — `orderType must be one of GTC|GTD|FOK|FAK`. - `currencyAddress` must be pUSD-Polygon; other collateral is rejected. - `order.salt` must stay within the safe-integer range (the relay serializes it as a number, so a larger salt would no longer be byte-exact to what you signed). - Supply a `conditionId` (or catalog market context) so the lane can bind live market fee and token-level NegRisk truth. Optional compatibility metadata includes `category` and `marketId`. - Collateral is **6 decimals**, not 18 — notional is `makerAmount / 1e6`. **Opinion** (BSC) differs on three points: - `order.signatureType` may be `'0'` (EOA) or `'2'` (Safe). A Safe order where `maker` differs from `signer` must use `'2'`; that is enforced at submit. - `order.taker` defaults to the zero address when omitted. - `currencyDecimal` is **required** here, and `marketId` (integer), `orderType` (integer), and `price` (non-empty string) are required. `postOnly` is an optional boolean — `postOnly must be a boolean when provided`. A collateral whose decimals are not armed in this deployment is rejected with a message beginning `unsupported opinion collateral currency — its decimals are not armed`. **Predict.fun** (BNB Chain, chain 56) differs again: - EOA only, same invariant as Polymarket with the message `…(no Safe/proxy)`. - `strategy` is required: `strategy must be 'MARKET' or 'LIMIT'`. - `pricePerShare` is required as a `uint256` decimal string. - Optional controls, each type-checked exactly: `slippageBps` (a uint decimal **string**, not a number), `isFillOrKill`, `isPostOnly`, `isMinAmountOut` (booleans), `reservedBalancePolicy` (`REJECT_MARKET_ORDER` \| `SKIP_RESERVED_BALANCE_CHECKS`), and `selfTradePrevention` (`CANCEL_MAKER` \| `CANCEL_TAKER` \| `CANCEL_BOTH`). - Collateral is pinned to USDT-BSC at **18 decimals**, not 6. --- # Gemini > The build request schema, signing scheme, and bounds for Gemini. Source: https://docs.predictefy.com/guides/trading/gemini/ ## Step zero — from nothing to your first trade Gemini uses a venue-custodied cash account. You do not need a crypto wallet or gas asset for this lane. 1. **Create the account.** Sign up at [gemini.com](https://gemini.com/), complete the venue's regulated identity checks, and accept its current prediction-market terms. Expect government-ID KYC. 2. **Create credentials.** In your venue account's API settings, create a Trader API key and secret. Enable a time-based nonce, disable heartbeat, and use Unrestricted trusted-IP mode while Predictefy egress is not pinned. Predictefy uses the credential in-process and never retains it. 3. **Fund it.** Deposit USD through Gemini's own supported bank or card rail; no chain or crypto collateral applies. Start with enough to meet the selected instrument's published `quantityMinimum` plus fees. The repo sources establish no universal deposit minimum. 4. **Allow time.** Setup is about 10 minutes after approval; KYC may take a day. 5. **Check access.** Gemini controls product and geographic eligibility, so confirm both before funding. ## What you need first - **Production status:** Armed for build, submit, and cancel as verified on 2026-08-15. - **Wallet and chain:** No wallet or chain transaction signs the order body. Gemini request auth is the only signature. - **Venue account:** Yes. Use a funded Gemini account and accept the current prediction-market terms. - **Credentials:** A caller-owned Trader API key and secret. Keep them in your process and send them only for submit, cancel, or refresh; Predictefy uses them in-process and never retains them. - **Funding:** Fund the venue-custodied Gemini account through Gemini's own rails. The funding registry pins no chain or collateral token for this lane. Gemini is a strict server-built, per-request HMAC transit lane. It is **source-ready but default off** behind `GEMINI_EXECUTION_ENABLED=true`; check `GET /v1/exec/venues` before integrating. :::note[Production update — 2026-08-15] The default-off wording above records Gemini's 2026-08-11 source-ready phase. Production now advertises build, submit, and cancel for Gemini. Continue to check the live capability endpoint before every integration because arming can change independently of this page. ::: Build accepts no credential and performs no venue request. Send `asset` (the Core outcome id), or `outcome` + `outcomeSide`, with these plain fields: | Field | Type | Required | Meaning | | --------------- | ----------------------------------------------------------- | -------- | ------------------------------------------------ | | `isBuy` | boolean | yes | `true` → Gemini `buy`; `false` → `sell` | | `price` | number in `(0,1)` | yes | Limit price, persisted as a decimal string | | `size` | number greater than zero | yes | Contract quantity, persisted as a decimal string | | `timeInForce` | `good-til-cancel`, `immediate-or-cancel`, or `fill-or-kill` | no | Defaults to `good-til-cancel` | | `makerOrCancel` | boolean | no | `true` requires maker-only behavior | The catalog supplies the exact Gemini instrument `symbol` and `yes`/`no` outcome. The stored version-1 artifact contains only `{ venue, buildVersion, request, body }`, where `request` is pinned to `/v1/prediction-markets/order` and `body` is the documented venue shape: `symbol`, `orderType: "limit"`, `side`, `quantity`, `price`, `outcome`, `timeInForce`, and optional `makerOrCancel: true`. Stop-limit is not supported. Notional is `quantity × price` and passes the same build and submit caps as every other order lane. Gemini also publishes instrument-specific `quantityIncrement`, `quantityMinimum`, `priceIncrement`, and `priceMinimum` values in contract metadata; choose aligned inputs or Gemini will reject the order without placing it. Submit `{ executionId, apiKey, apiSecret }`. Master API keys must also supply `account` with the Gemini account name (for example, `primary`); account-scoped keys omit it. Only after version, shape, request-path, notional, and artifact-bounds checks pass does Predictefy read the credentials. It constructs `{ request, nonce, account, ...body }` when `account` is present (otherwise `{ request, nonce, ...body }`), base64-encodes the JSON, computes the hex HMAC-SHA384 with `apiSecret`, and sends one empty-body POST with Gemini's three auth headers. Credentials and generated headers are excluded from both persisted artifacts and logs. :::caution[Gemini account and key prerequisites] Complete these yourself; Predictefy never changes account settings or accepts terms for you. - Accept the current prediction-market terms once via Gemini's `POST /v1/prediction-markets/terms/accept` flow. - Use a third-party key with the **Trader** role. - Enable **Uses a time based nonce**. Counter-nonce keys are unsafe for a stateless relay because a relay request can race and regress the caller's own bot. Gemini requires epoch seconds within plus or minus 30 seconds. A rejection returns `409 GEMINI_TIME_NONCE_REQUIRED` with the fix. - Disable **Requires Heartbeat**. Thirty seconds of authenticated silence cancels that session's open orders. If Gemini exposes this setting in an error, Predictefy returns `409 GEMINI_HEARTBEAT_KEY_UNSUPPORTED`. - Gemini trading keys require trusted-IP configuration. Predictefy does not provide pinned egress, so this lane requires choosing **Unrestricted** and accepting that it removes the IP allowlist layer of protection. ::: Gemini holds the account balance, just as a venue holds other venue-account balances; this is fund custody by Gemini, not custody by Predictefy. Predictefy never holds those funds and never retains the API key or secret. The same private REST family supports cancel and status: cancel builds a new zero-notional artifact containing the stored integer `orderId`, while status refresh signs `/orders/active` and then `/orders/history` with the caller's transient credentials. No automatic background poll is possible because no credential is retained. --- # Choose your first venue > Choose a first trading venue by account, wallet, funding, and execution requirements. Source: https://docs.predictefy.com/guides/trading/getting-started/ Predictefy's data and trading onboarding are different. You can read unified live and historical data with one Predictefy signup; you do not need an account at every venue. Trading requires the chosen venue's own account, credentials, wallet, and funding where applicable. Predictefy does not pool those balances. ## Pick a starting lane | Starting point | Venues | What this means | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Card/bank, no crypto** | [Kalshi](/guides/trading/kalshi/), [Gemini](/guides/trading/gemini/), [Polymarket US](/guides/trading/polymarket-us/), [Novig](/guides/trading/novig/) (confirm personal API credential access first) | Open the venue account, complete its identity checks, create any required API credential, and fund a venue-custodied cash balance. | | **Crypto wallet required** | [Polymarket](/guides/trading/polymarket/) (Polygon), [Opinion](/guides/trading/opinion/), [Predict.fun](/guides/trading/predict-fun/), and [Myriad](/guides/trading/myriad/) (BNB Chain); [XO](/guides/trading/xo/) (chain 3223); [Rain](/guides/trading/rain/) (Arbitrum); [Limitless](/guides/trading/limitless/) (Base); [Hyperliquid](/guides/trading/hyperliquid/) (Hypercore); [Pascal](/guides/trading/pascal/) (Solana) | Create and fund a wallet on the exact chain with the exact collateral named in that guide. Some lanes also require venue credentials or account provisioning. | | **Data-only today (hosted API)** | [PRED](/guides/trading/pred/) is darked by the venue's caller-key model; [SX Bet](/guides/trading/sxbet/) is client-signed through the venue-direct SDK only, with no hosted lane by custody design. | Do not expect these venues in `GET /v1/exec/venues`. The SX Bet guide documents its separate client-side integration; PRED must not be funded for hosted submit while darked. | | **Dark venues (not served):** | [Smarkets](/guides/trading/smarkets/) | Smarkets is excluded from hosted routes, router fan-outs, and the served venue count until a commercial API agreement is in place; the venue-direct SDK client remains only for customers with their own Smarkets API approval. | [PredictStreet](/guides/trading/predictstreet/) needs both a venue account and an ADI Chain wallet and vault, so it does not fit the simple card-only or wallet-only split. Once you have picked a venue, [Venue credentials](/guides/trading/venue-credentials/) shows what that venue issues, whether it needs an API credential at all, and whether the credential reaches Predictefy or stays in your process. If this is your first trade anywhere, start with a card/bank venue. The account and KYC step can take longer than the trade itself, but you avoid choosing a chain, bridging collateral, and holding gas. Check the venue guide before depositing because account eligibility and execution availability can differ. If you choose a wallet venue, first create a wallet for the named chain and secure its seed phrase offline. Never paste the seed phrase or private key into Predictefy: Predictefy never sees that key. Fund only a small first trade and any gas asset that the venue guide explicitly requires. When funds must cross chains, use `GET /v1/bridge/quote` as the assisted route where the venue guide says a bridge is available. ## Every venue guide [Hyperliquid](/guides/trading/hyperliquid/) · [Limitless](/guides/trading/limitless/) · [Kalshi](/guides/trading/kalshi/) · [Polymarket](/guides/trading/polymarket/) · [Polymarket US](/guides/trading/polymarket-us/) · [Opinion](/guides/trading/opinion/) · [Predict.fun](/guides/trading/predict-fun/) · [Pascal](/guides/trading/pascal/) · [XO](/guides/trading/xo/) · [PredictStreet](/guides/trading/predictstreet/) · [Novig](/guides/trading/novig/) · [Gemini](/guides/trading/gemini/) · [Myriad](/guides/trading/myriad/) · [PRED](/guides/trading/pred/) · [Rain](/guides/trading/rain/) · [Smarkets (dark)](/guides/trading/smarkets/) · [SX Bet](/guides/trading/sxbet/) --- # Hyperliquid > The build request schema, signing scheme, and bounds for Hyperliquid. Source: https://docs.predictefy.com/guides/trading/hyperliquid/ ## Step zero — from nothing to your first trade 1. **Create the account.** Go to [hyperliquid.xyz](https://hyperliquid.xyz) and connect a Hyperliquid EVM master wallet. The wallet is the account identity; the repository sources do not document a separate KYC step. 2. **Set up signing.** If this is your first on-chain venue, create an EVM wallet first and secure its seed phrase offline. Use the master key directly or approve an agent key. Predictefy never sees either private key; signing stays in your process. 3. **Fund it.** Deposit USDC into Hypercore spot, Hyperliquid's own L1 clearinghouse. Fund at least the `price × size` of your first order; the historical Bridge2 fallback rejects deposits below 5 USDC. Use `GET /v1/bridge/quote` to price cross-chain funding, then create a bridge session for the assisted deposit. HIP-4 orders spend spot USDC directly. 4. **Allow time.** Budget about 1–2 hours for a first wallet and bridge; an already-funded wallet can be ready much sooner. Confirm venue eligibility for your location before depositing. ## What you need first - **Production status:** Armed for build, submit, cancel, single-order modify, and `approveAgent` as verified on 2026-08-15. - **Wallet and chain:** A Hyperliquid EVM master wallet, or an EVM agent approved by that master, signs the Hyperliquid L1 action in the caller's process. - **Venue account:** The master wallet is the account identity. Approve an agent first when a browser or mobile wallet will not sign each order directly. - **Credentials:** No venue API credential. The master or agent private key remains client-side and never transits Predictefy. - **Funding:** Spendable Hypercore spot USDC (`total - hold`). `POST /v1/bridge/session` can deliver USDC to that spot balance; first-party HIP-4 orders need no internal transfer. - **Getting funds out:** Only through Hyperliquid's own `withdraw3` action, which reaches **Arbitrum and nowhere else** — that action is the venue's Arbitrum bridge, so the destination chain is not a caller choice. `client.funding.buildWithdrawRequest` builds the EIP-712 data against the Arbitrum domain and stops there; you sign and POST it to Hyperliquid yourself. Any other chain is a second leg: withdraw to Arbitrum, then bridge onward with `GET /v1/bridge/quote`. | Field | Type | Required | Rejection | | ------------------- | ----------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `asset` | non-negative integer | this **or** `outcome` + `outcomeSide` | `asset must be a non-negative integer` | | `outcome` | non-negative integer | with `outcomeSide` | `provide asset, or outcome + outcomeSide (YES\|NO)` | | `outcomeSide` | `"YES"` or `"NO"` | with `outcome` | same message | | `isBuy` | boolean | yes | `isBuy (boolean) is required` | | `price` | number in **(0, 1] dollars** | yes | `price must be a number in (0, 1] dollars` | | `size` | number > 0 | yes | `size must be a number > 0` | | `owner` | `0x…` EVM address | yes | `owner (the wallet that will sign this action) is required` | | `tif` | `Alo` \| `Ioc` \| `Gtc` (default `Gtc`) | no | `tif must be Alo, Ioc, or Gtc` | | `trigger` | object, mutually exclusive with `tif` | no | `provide either tif (limit) or trigger (tp/sl), not both` | | `trigger.triggerPx` | number in (0, 1] dollars | with `trigger` | `trigger.triggerPx must be a number in (0, 1] dollars` | | `trigger.tpsl` | `"tp"` or `"sl"` | with `trigger` | `trigger.tpsl must be "tp" or "sl"` | | `trigger.isMarket` | boolean (default `true`) | no | `trigger.isMarket must be a boolean when provided` | | `nonce` | safe positive integer (default: ms clock) | no | `nonce must be a safe positive integer` | | `expiresAfter` | safe positive integer | no | `expiresAfter must be a safe positive integer` | | `cloid` | `0x` + 32 hex chars (128-bit) | no | `Hyperliquid cloid must be a 128-bit hex string (0x followed by 32 hex characters).` — auto-derived when omitted, so every order carries one | | `reduceOnly` | boolean (default `false`) | no | — | A HIP-4 outcome resolves to `asset = 100000000 + 10 × outcome + (YES → 0, NO → 1)`. These are spot-class assets with no perp-DEX component. On a unified account, their collateral is spendable spot USDC: `total - hold` from `spotClearinghouseState`. The spend-cap and funding target are `price × size`; current Hyperliquid documentation says outcome fees are zero. Every HIP-4 order build makes the collateral location explicit: ```json { "collateral": { "ledger": "spot", "destinationDex": null } } ``` The SDK convenience path validates that descriptor before signing. Default-on `autoFund` is a typed no-op because the source and destination ledgers already match: ```ts const signer = makeHyperliquidSigner({ privateKey: process.env.HL_AGENT_PRIVATE_KEY!, agentFor: process.env.HL_MASTER_ADDRESS!, }); const result = await client.exec.createOrder( { venue: 'hyperliquid', outcome: 1081, outcomeSide: 'YES', isBuy: true, price: 0.5, size: 25, owner: signer.owner, }, signer, ); result.funding; // { // status: 'not_required', // reason: 'collateral_destination_matches_source', // collateral: { ledger: 'spot', destinationDex: null } // } ``` It does not call the funding route, read a default-perp balance, sign `sendAsset`, or move funds. Pass `{ autoFund: false }` as argument three only if you also want to omit the SDK-only `funding` result. `autoFundMax` is reserved for a future named builder DEX whose destination genuinely differs from spot; it has no effect on first-party HIP-4 orders. REST callers can inspect readiness with `POST /v1/funding/hyperliquid/steps`. The route reads only spot state and always returns zero steps for first-party HIP-4. For the funded tester case — 11 USDC spot and a 28 at 0.40 order — it returns: ```json { "readiness": { "spotUsdcBalance": "11", "targetAmount": "11.2", "shortfall": "0.2" }, "reason": "insufficient_spot_usdc", "steps": [] } ``` Without a target, `reason` is `readiness_only`; with a sufficient target it is `collateral_already_in_place`. Funding signing and submission refuse every `sendAsset` today. A future named `destinationDex` requires an explicit venue-verified destination, collateral token, and mainnet deployer allowlist entry. Predictefy exposes no hosted send-asset endpoint and never changes account abstraction automatically. --- # Kalshi > The official hosted REST lane, transient RSA credentials, and unchanged client-side SDK path. Source: https://docs.predictefy.com/guides/trading/kalshi/ ## Step zero — from nothing to your first trade Kalshi has two separate execution paths. The hosted execution service and the client-side SDK use the same venue account, but they have different credential boundaries. Choose one deliberately. 1. **Create the account.** Sign up at [kalshi.com](https://kalshi.com/) and complete the venue's regulated identity and eligibility checks. 2. **Create credentials.** From the Kalshi API Keys page, generate an API key ID and its matching RSA private key. Save both immediately outside source control; the private key is not shown again. If your account uses subaccounts, record the non-negative integer subaccount you trade. 3. **Fund it.** Deposit USD into your regulated Kalshi account through the venue's supported rails. Kalshi holds that balance; Predictefy never holds or intermediates it. 4. **Choose the path.** Use `/v1/exec/kalshi/...` for the hosted lane described below, or `client.accounts.kalshi` when credentials must stay entirely inside your own process. 5. **Check access.** Eligibility remains venue-controlled and location-dependent. Confirm access before depositing or submitting an order. ## What you need first ### Hosted official REST lane — per-request credential transit - **Production status:** Armed for build, submit, and cancel as verified on 2026-09-02. Check `GET /v1/exec/venues` for the deployment's current arming state. - **Venue account:** A funded, identity-verified Kalshi account. Funds remain venue-custodied USD. - **Credentials:** The caller supplies `apiKeyId` and `privateKeyPem` on each submit or refresh. `subaccount` is optional on submit and must be a non-negative integer. - **Lifecycle:** Catalog-bound build, official V2 submit, cancel, and caller-initiated status refresh are supported. The server never retains credentials for background polling. - **Funding:** Use Kalshi's regulated deposit and withdrawal rails. No Solana wallet, token mint, gas balance, or Predictefy escrow is involved. :::caution[Hosted credential boundary] The RSA private key crosses to the isolated execution service for one authenticated request. The service validates stored order truth and strict field allowlists before reading it, signs only the fixed Kalshi method and path in memory, and then discards the key and generated headers. Neither the key ID, PEM, nor auth headers are stored or logged. ::: ### Client-side SDK lane — credentials remain local `client.accounts.kalshi` is unchanged. Configure `venueCredentials.kalshi` with your `apiKeyId` and `privateKeyPem`; the SDK signs and sends requests directly to Kalshi from your process, so those credentials never reach Predictefy. It supports account reads, limit orders, market-as-IOC orders at your price cap, amendments, cancellations, and batched create/cancel. Use this path when the local credential boundary matters more than hosted orchestration. ## Build an official hosted order `POST /v1/exec/kalshi/orders/build` accepts normalized catalog intent. It accepts no venue credential and performs no venue write. | Field | Type | Required | Rule | | ------------- | ---------------------------- | -------- | -------------------------------------------------------------------- | | `outcome` | non-empty catalog id | yes | Market id with `outcomeSide`, or a direct catalog outcome id | | `outcomeSide` | `YES` or `NO` | no | Required only when `outcome` names the market rather than the outcome | | `isBuy` | boolean | yes | Maps the selected outcome to Kalshi's YES-book `bid` or `ask` | | `price` | number strictly between 0–1 | yes | Must land exactly on a whole-cent probability | | `size` | positive integer | yes | Whole-contract count | | `timeInForce` | `good-til-cancel` | no | Defaults to `good-til-cancel` | The server resolves the native ticker and binary side from catalog truth. The returned authless order artifact has exactly these fields: | Field | Wire shape | | ----------------------------- | ----------------------------------------------- | | `ticker` | non-empty native Kalshi ticker | | `client_order_id` | server-generated UUIDv4 | | `side` | `bid` or `ask` | | `count` | fixed-point whole contracts, for example `5.00` | | `price` | fixed-point cents, for example `0.4200` | | `time_in_force` | `good_till_canceled` | | `self_trade_prevention_type` | `taker_at_cross` | ## Submit, cancel, and refresh - **Submit:** Send `{executionId, apiKeyId, privateKeyPem, subaccount?}`. The service signs and POSTs only the stored artifact. Caller-supplied ticker, side, count, price, or order fields are rejected before signing. - **Cancel:** Build a cancel from the owned execution, then submit the new cancel execution with fresh `apiKeyId` and `privateKeyPem`. The stored cancel artifact is `{order_id}` and the venue call is the official V2 DELETE order endpoint. - **Refresh:** Send `{apiKeyId, privateKeyPem}` to `POST /v1/exec/kalshi/orders/{executionId}/refresh`. No `executionId` or `subaccount` belongs in the body because the execution is already named by the path. Status mapping is conservative: `resting` and `pending_review` become `acked`; `executed` becomes `filled`; `canceled`, `expired`, and `rejected` become `canceled`, `expired`, and `failed`. Unrecognized venue status leaves the non-terminal execution at the safe `acked` fallback. :::caution[Orders built before the official-API migration] Pre-migration Solana-shaped stored artifacts are incompatible with the official REST contract. Submit or cancel refuses them with `ARTIFACT_VERSION_CONFLICT` (409), implemented by `ArtifactVersionConflictError`. Build a fresh order with a new `Idempotency-Key`. ::: --- # Limitless > The build request schema, signing scheme, and bounds for Limitless. Source: https://docs.predictefy.com/guides/trading/limitless/ ## Step zero — from nothing to your first trade 1. **Create the account.** Go to [limitless.exchange](https://limitless.exchange) and connect an eligible EOA on Base. The hosted lane accepts no venue account credential or `profileId`; the repository sources do not establish a separate registration or KYC requirement, so confirm that prerequisite with Limitless. 2. **Set up signing.** A fresh user must first create an EVM wallet and secure the seed phrase offline. The wallet signs each order locally. Predictefy never sees the private key. 3. **Fund it.** Put Base-native USDC in that EOA, keep a little ETH for Base gas, and grant only a finite approval to the market's authoritative exchange. The sources publish no numeric deposit minimum, so fund one small intended order. If funds start elsewhere, request the assisted LI.FI route with `GET /v1/bridge/quote`. 4. **Allow time.** Budget about 1–2 hours if this is your first wallet; an existing funded Base wallet is faster. Predictefy's hosted lane refuses US-region callers through its own geofence by design. ## What you need first - **Production status:** Armed for build, submit, and cancel as verified on 2026-08-15. - **Wallet and chain:** An EOA on Base (`8453`) signs the server-built EIP-712 order client-side. - **Venue account:** The hosted lane accepts no caller account credential or `profileId`; verify any separate venue registration and eligibility requirement with Limitless. - **Credentials:** No caller venue credential. Predictefy holds the partner HMAC credential, while the EOA private key remains only in the caller's process. - **Funding:** Base-native USDC in the EOA, with a finite approval to the market's authoritative exchange. The LI.FI helper can fund the wallet. :::caution[Production geo status — 2026-08-16] Restricted production egress receives `403 GEO_BLOCKED` from Limitless, including on order-status reads. The lane is armed, but that does not make the hosted lifecycle usable from a blocked egress. Use only an eligible route that supplies the required trusted geo signal and still satisfies the venue's policy. ::: | Field | Type | Required | Rejection | | ----------------------- | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | `owner` | EVM address | yes | `owner (the EOA that will sign) is required` | | `tokenId` | CTF position id, digits only | yes | `tokenId (CTF position id) is required` | | `orderSide` (or `side`) | `BUY`/`SELL` or `0`/`1` | yes | `orderSide must be BUY or SELL` | | `orderType` | `FOK` or `GTC` | yes | `orderType must be FOK or GTC` | | `marketSlug` | string | yes | `marketSlug is required` | | `price` | number in **(0, 1) exclusive** | yes | `price in (0,1) is required` | | `shares` | number > 0 | yes | `shares (> 0) is required` | | `amountUsd` | number > 0 | FOK buy | `amountUsd (> 0) is required` | | `expiration` | unix seconds (default now + 1 hour) | no | `expiration must be a non-negative safe integer (unix seconds)` | | `nonce` | number (default `0`) | no | — | | `profileId` | **rejected outright** | — | `profileId is not accepted — the bound owner address is the relay identity` | Note the exclusive bound: Hyperliquid accepts `price = 1`, Limitless does not. The server fixes every remaining field — `feeRateBps` 300, `signatureType` 0, `taker` the zero address, the salt, and `maker` = `signer` = `owner` — so you sign exactly what was cap-checked. Notional is `makerAmount / 1e6`: exact USDC for a buy, a conservative dollar ceiling for a sell. --- # Myriad > The build request schema, signing scheme, and bounds for Myriad. Source: https://docs.predictefy.com/guides/trading/myriad/ ## Step zero — from nothing to your first trade 1. **Create the account.** Open [myriad.markets](https://myriad.markets), connect the EOA you will trade from, and follow the current prompts; the repo sources do not document the identity-check sequence. 2. **Set up credentials.** Create a wallet and secure its seed phrase; never share the private key, which Predictefy never sees. Get an API key and secret in your venue account's API settings, bound to that EOA. For hosted submit or cancel, the HMAC pair transits one bounded request and is never retained. 3. **Fund and approve.** A practical first test is $10–$25—not a venue minimum—of the market-selected 18-decimal USD1 or USDT on BNB Smart Chain (`56`), plus a little BNB for approval gas. Myriad has no venue-pinned bridge helper. For cross-chain funds, use `GET /v1/bridge/quote` only as an explicit chain/token quote; set chain `56` and the exact market collateral, then verify both before signing. 4. **Allow time.** Budget 1–2 hours for a first wallet and funding; less if the asset is ready. The repo sources document no Myriad geographic gate. ## What you need first - **Production status:** Armed for Order Book build, submit, and cancel as verified on 2026-08-15. - **Wallet and chain:** An EOA on BNB Smart Chain (`56`) signs the EIP-712 order client-side. - **Venue account:** Yes. Use a current Myriad API key and secret bound to the same connected EOA; bare, non-wallet-bound credentials are unsupported. - **Credentials:** Keep the HMAC key and secret in your process and send them only with hosted submit or cancel. Predictefy reads them after signature recovery and never retains them. - **Funding:** The manager-selected, 18-decimal BSC collateral: code-pinned USD1 or USDT. Fund the EOA and approve the exchange's required collateral or outcome shares; there is no hosted helper. Myriad is a strict server-built Order Book lane. It is **source-ready but default off** behind exact literal `MYRIAD_TRADE_ENABLED=true`; check `GET /v1/exec/venues` before integrating. Send a canonical model-qualified outcome id as `asset` (or `outcome`) plus these fields: :::note[Production update — 2026-08-15] The default-off wording above records the source-ready phase. Production now advertises build, submit, and cancel for Myriad's Order Book lane; AMM ids remain unsupported by this order path. ::: | Field | Type | Required | Meaning | | ------- | -------------------- | -------- | ------------------------------------------------ | | `isBuy` | boolean | yes | `true` → native side 0; `false` → native side 1 | | `price` | decimal `0.01..1.00` | yes | Limit price on Myriad's mandatory 0.01 tick grid | | `size` | positive decimal | yes | Share amount, scaled by live collateral decimals | | `owner` | EVM address | yes | EOA trader that signs and owns the venue API key | Only `myriad:ob:56:{marketId}:0|1` reaches order construction. The catalog lookup uses the exact qualified outcome and must provide matching native market/outcome provenance. Every build then reads the exact Order Book market, proves BSC chain 56 plus deployed exchange/manager/token code, obtains the market collateral from `getMarketCollateral`, reads decimals, and requires agreement with the API. Supported collateral is code-pinned to 18-decimal BSC USD1 or USDT. Any missing or conflicting identity, model, generation, code, collateral, or decimal fact fails closed. The returned `buildVersion: 1` artifact includes the complete EIP-712 domain, canonical ordered types, message, market and collateral provenance, and `structHash`. The signed `nonce` is generated from 256 bits of server CSPRNG entropy. Orders are SDK-compatible GTC limits with expiration and minimum fill set to zero. Sign the typed data in your wallet, then submit `{ executionId, signature, owner, apiKey, apiSecret }`. Predictefy recovers the EOA over the stored digest before reading either credential, then HMAC-SHA256 signs the exact `POST /orders` bytes. Cancel builds a zero-notional artifact from the original stored order and wallet signature. Submit the cancel execution with `{ executionId, owner, apiKey, apiSecret }`; the service HMAC-signs one `DELETE /orders/:orderHash` request. The key, secret, and generated headers are never persisted or logged. The configured venue endpoint must be a clean HTTPS origin and redirects are rejected. Use a current Myriad key/secret bound to the same connected wallet; bare-key auth is not supported. There is no hosted status refresh, modify, account, or funding-helper lane. Canonical `myriad:amm:...` outcomes return typed `NOT_SUPPORTED`. AMM trading uses Myriad's separate quote/calldata wallet-transaction lifecycle and is never sent through Order Book signing logic. The existing resolved-AMM claim lane is an independent deployment capability. --- # Novig > Market data model, reads authentication, tick table, and trading integration for Novig. Source: https://docs.predictefy.com/guides/trading/novig/ ## Step zero — from nothing to your first trade Novig uses a venue-custodied USD account, not a crypto wallet. Its production API is also restricted to US egress. 1. **Create the account.** Sign up at [novig.us](https://novig.us/). Novig is a CFTC-regulated US exchange, so expect identity checks and government-ID KYC. 2. **Confirm credentials.** The integration requires a Novig client ID and client secret for OAuth 2.0 token minting. Personal-account API credential issuance is still being confirmed with the venue; do not assume these credentials appear in self-service API settings. Confirm access with Novig before planning an API trade. 3. **Fund it.** Deposit USD through Novig's regulated banking rails; no chain or crypto asset applies. In CASH denomination, 100 Minimum Currency Units make one contract. Start with enough for at least one contract plus fees; the repo sources establish no deposit minimum. 4. **Allow time.** Setup is about 10 minutes after approval; KYC may take a day, and API credential issuance may add time. 5. **Check access.** Production endpoints are geo-fenced to US egress, so verify that requirement before funding. ## What you need first - **Production status:** Market data reads, order books, and hosted build, submit, and cancel are live. - **Venue account:** Yes, for authenticated reads and trading on Novig. - **Credentials:** `NOVIG_CLIENT_ID` and `NOVIG_CLIENT_SECRET` for OAuth 2.0 Client Credentials token minting (`POST /nbx/v1/auth/emm-token`). - **Funding:** On-venue USD (`fiat_custodied`). Novig custodies funds; deposits and withdrawals happen on the venue via regulated banking rails. ## Reads authentication & access Novig is a CFTC-regulated US sports prediction exchange operating the NBX v2 EMM API. All read operations against `api.novig.us` require a Bearer access token obtained via OAuth 2.0 Client Credentials: ```http POST /nbx/v1/auth/emm-token Content-Type: application/json { "grant_type": "client_credentials", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET" } ``` Tokens are minted lazily and cached in-process. Production endpoints (`https://api.novig.us`) are geo-fenced to US egress IP addresses, while the QA environment (`https://api-qa.novig.us`) is open. :::note[DCM cutover UUID stability] All NBX UUIDs were regenerated at Novig's 2026-08-04 DCM cutover. Pre-cutover cached identifiers are invalid. ::: ## Data model & sidedness Novig's sports markets follow a 3-tier hierarchy: - **Event:** A sports game or match (e.g. NFL, NBA, EPL) carrying a `scheduledStart` timestamp and teams/competitors. Cross-venue joins utilize `opticOddsId` when present. - **Market:** A betting market within an event (e.g., moneyline, spread, total). - **Outcomes:** Binary pairs of outcomes per market. Sidedness is defined strictly by the outcome `index`: - `index = 0`: Home / Over / Yes - `index = 1`: Away / Under / No _Never rely on array position for sidedness._ Outcome IDs carry no derivable mathematical relation to market UUIDs, so Predictefy uses the composite format `${marketId}:${outcomeId}` to route outcome-level verbs. ## Prices, tick table, and order books - **Odds as probabilities:** Prices represent decimal probabilities in $[0.001, 0.999]$. - **Non-uniform tick table:** Novig enforces a non-uniform tick grid (finer resolution near 0.01 and 0.99, standard 0.005/0.01 in the middle). The valid ticks are queried from `GET /nbx/v2/emm/ticks` and never hardcoded. - **Bids-only ladder & complement:** Novig order books (`GET /nbx/v2/emm/book/{marketId}`) publish bids only. The offer (ask) side is derived as the exact binary complement ($1 - p$) with quantities mirrored across the paired outcome. - **Quantity units:** Quantities are denominated in Minimum Currency Units. For CASH denomination, 1 unit = $0.01 (1 cent), and **100 units = 1 contract**. ## Execution status - **Server execution lane:** Armed for hosted build, submit, and cancel since 2026-08-22. Build stores an authless REST artifact; no order-body signature is required. Hosted build accepts only the fields below. Unknown fields are rejected before an execution is persisted, and every order requires an idempotency key. | Field | Required | Contract | | ----------- | ------------------- | -------------------------------------------------------------------- | | `intent` | no | If present, must be `"order"`. | | `marketId` | yes | Must match the catalog market's native Novig market UUID. | | `outcomeId` | yes | Catalog composite `{marketId}:{outcomeId}` for the selected outcome. | | `type` | yes | Must be `"limit"`; cash-sized market orders are not supported. | | `amount` | yes | Positive number of contracts. | | `price` | yes | Number in `(0, 1)` that matches the live Novig tick table exactly. | | `currency` | no | If present, must be `"CASH"`; the `COIN` balance is not executable. | | `tif` | no; default `"GTC"` | One of `"GTC"`, `"GTT"`, `"IOC"`, or `"FOK"`. | | `ttl` | for `"GTT"` | Positive whole milliseconds; rejected for every other time in force. | Predictefy's platform `NOVIG_CLIENT_ID` and `NOVIG_CLIENT_SECRET` are used only for build-time reads such as the live tick table. They cannot move caller funds. Submit accepts only `executionId` and the caller's own `callerAccessToken`; refresh accepts only `callerAccessToken`. The token is used for that one venue request and is never persisted. Cancel build takes no caller fields and derives a bodyless `DELETE` from the stored venue order id. Submit that cancel with `callerAccessToken`. Novig acknowledges cancellation asynchronously, so refresh with the same kind of per-request token until venue status confirms the order is canceled. --- # Opinion > The build request schema, signing scheme, and bounds for Opinion. Source: https://docs.predictefy.com/guides/trading/opinion/ ## Step zero — from nothing to your first trade 1. **Create the account.** Open [opinion.trade](https://opinion.trade), connect the BNB Smart Chain EOA you will trade from, and follow the current prompts; the repo sources do not document the identity-check sequence. 2. **Set up credentials.** First create a wallet and secure its seed phrase; never share the private key, which Predictefy never sees. Get the user API key in your venue account's API settings. The direct path sends it only to Opinion. 3. **Fund and approve.** A practical first test is about $10–$25 of USDT on BNB Smart Chain (`56`), plus a little BNB for gas—not a venue minimum. Approve the exact market-derived exchange; do not hardcode the spender. If funds are on another chain, start with `GET /v1/bridge/quote`. 4. **Allow time.** Budget 1–2 hours for a first wallet, bridge, and approval; less if already funded. Opinion's account and jurisdiction rules still apply, and the direct path uses your real connection. ## What you need first - **Production status:** Armed for build, submit, and cancel. Hosted requests use eligible regional egress and reach Opinion; a live credentialed submit has not yet been verified on that path. - **Wallet and chain:** A BSC (`56`) EOA signs. Signature type `2` additionally uses an Opinion Safe as maker while the EOA remains its controlling signer. - **Venue account:** For the working venue-direct SDK path, use an Opinion account with a user API key. Hosted build needs no caller venue credential. - **Credentials:** On `client.accounts.opinion`, the user API key stays in the caller's process and goes only to Opinion. Hosted cancel/status accepts it transiently and never retains it. - **Funding:** Hold the market-authoritative BSC quote token in the maker EOA or Safe and approve the exact exchange. The funding registry's current route is BSC USDT through LI.FI. Opinion is server-built by default. Send `asset`, or `outcome` plus `outcomeSide`, together with `isBuy`, probability-dollar `price`, share `size`, and `owner`. Optional fields are `signatureType`, `safeAddress`, `expiresAt`, and `taker`. With no `order` object, the lane resolves the token through Predictefy's catalog, fetches that exact Opinion market, then follows its quote token to the authoritative BSC exchange and collateral decimals. A missing or ambiguous mapping, or any catalog/API disagreement, fails closed before an execution is persisted. Identity is explicit: - Signature type `0` is plain EOA: `maker == signer == owner`; `safeAddress` is rejected. - Signature type `2` requires `safeAddress`: `maker == safeAddress` is the funds-holding Safe, while `signer == owner` is its controlling EOA. They are deliberately not forced equal. Opinion's pinned 0.6.1 builder fixes LIMIT prices to six decimals and order fees to `0`. A price must round inside `0.000001..0.999999`. Omitted `expiresAt` becomes `0`; a positive Unix timestamp is carried verbatim for GTD. The server generates a full-width 256-bit cryptographic salt. Opinion's relay serializes that uint256 as a decimal string, so the Polymarket safe-integer salt limit does not apply. The response includes the venue-shaped unsigned order, `buildVersion: 1`, and canonical `types`, `primaryType`, `domain`, and `message` for wallet signing. The legacy echoed `buildResult` remains accepted unchanged. ## Client-side submit route Opinion rejects order traffic from restricted regions. Hosted requests now use Predictefy's eligible regional egress relay; venue reads and order requests are verified to reach Opinion through it, but a live credentialed submit remains unverified. The caller-direct route below remains supported: it builds at Predictefy, signs in the caller's wallet, and submits from the caller's own eligible connection with their user API key. ```ts const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, execBaseUrl: PREDICTEFY_EXEC_BASE_URL, venueCredentials: { opinion: { apiKey: process.env.OPINION_API_KEY! } }, }); const placed = await client.accounts.opinion.createOrder( { idempotencyKey: 'your-stable-build-key', asset: '123456789', isBuy: true, price: 0.4, size: 10, owner: account.address, signatureType: 0, }, account, ); const accountAuth = { apiKey: process.env.OPINION_API_KEY! }; const status = await client.accounts.opinion.fetchOrder(placed.orderId, accountAuth); await client.accounts.opinion.cancelOrder(placed.orderId, accountAuth); ``` The build request has an explicit field allowlist. Unknown fields are rejected before any request, and their values are never included in errors. Before asking the wallet to sign, the SDK rebuilds the exact-key Opinion order and canonical EIP-712 types, pins chain `56`, domain name `OPINION CTF Exchange`, version `1`, and verifies the market-derived exchange against the execution lane's known-exchange allowlist. It independently compares the digest and `structHash`, then binds the requested token, side, venue-rounded amounts, expiration, maker, signer, and signature type. The SDK also reads the market's authoritative outcome-token mapping from the fixed venue origin and refuses to sign unless the signed token is the requested side's token for that market — in asset mode the signed token must belong to the routed market. A failed or malformed venue read refuses before signing; the mapping is fetched once per market per client and reused. After signing, it recovers the EOA from the signature and requires it to match the requested owner. Safe mode remains a plain EOA signature over the complete order: signature type `2` uses the Safe as maker and its controlling EOA as signer. It never uses EIP-1271. The SDK sends the opinion-clob-sdk 0.6.1 wire body under the caller's `apikey` header to the fixed `https://openapi.opinion.trade/openapi` origin. Create is `POST /order`, status is `GET /order/{orderId}`, and cancel is `POST /order/cancel`. Hosted build traffic and credentialed venue traffic use separate fetch seams. Venue calls refuse redirects, and failures expose typed, generic errors without upstream text or credentials. The route does not bypass Opinion's account or jurisdiction rules. This implementation is source-ready. One eligible submit → status → cancel pass remains pending. --- # Pascal > The build request schema, signing scheme, and bounds for Pascal. Source: https://docs.predictefy.com/guides/trading/pascal/ ## Step zero — from nothing to your first trade 1. **Create the account.** Start at the venue's site, create an eligible Pascal account, and connect its Solana-format custody wallet. The allowed sources do not document Pascal's signup or identity checks, so confirm those details with the venue. 2. **Set up signing.** A fresh user must first create a Solana wallet and secure the seed phrase offline. Sign with that wallet or register a revocable Pascal trading key. There is no relay API credential, and Predictefy never sees either private key. 3. **Fund it.** Keep SOL in the custody wallet for Solana network fees, plus the balance Pascal requires for the first order. The funding registry verifies no collateral asset, deposit route, or numeric minimum and exposes no hosted helper. `GET /v1/bridge/quote` may price a cross-chain route; it is not a verified Pascal deposit path. 4. **Allow time.** Budget about 1–2 hours for a first wallet and venue setup; any account approval can take longer. Pascal itself restricts US users, so do not fund or trade from the US. ## What you need first - **Production status:** Armed for build, submit, and cancel — armed 2026-08-12; fleet-verified 2026-08-15. Status refresh is served as well; `GET /v1/exec/venues` reports only the three write verbs, so it will not show up there. - **Wallet and chain:** A Solana-format Ed25519 custody wallet. The wallet key may sign directly, or a registered, revocable delegated Pascal trading key may sign the permit. - **Venue account:** Yes. Use an eligible Pascal account and its existing custody wallet. - **Credentials:** No relay API credential. The private wallet or trading key stays client-side; only the base58 permit signature and public owner cross the API. - **Funding:** Use the existing Pascal custody wallet. The funding registry does not verify a collateral asset or deposit route and there is no hosted funding helper. Pascal is a strict server-built binary-permit lane, and it is **armed in production**: `PASCAL_TRADE_ENABLED=true` was set on 2026-08-12 and the row was fleet-verified on 2026-08-15, so `GET /v1/exec/venues` lists Pascal with build, submit, and cancel. That single flag remains the registration gate, and flipping it back off is an owner/compliance operation — so keep reading the live `/v1/exec/venues` row rather than trusting this page for runtime state. What arming did **not** change: there is still no hosted funding helper and no verified collateral deposit route. | Field | Type | Required | Meaning | | ----------- | ------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- | | `market` | Predictefy catalog market id | yes | Resolves Pascal's symbol from the catalog; tick parameters and taker fee come from a live venue read | | `side` | `YES` or `NO` | yes | Outcome being bought or sold | | `isBuy` | boolean | yes | Maps the outcome intent to Pascal's native bid or ask | | `price` | probability in `(0, 1)`, at most six places | yes | Must align with the market's tick at that price; see the tick rule below | | `size` | positive u64 integer | yes | Contract quantity | | `owner` | base58 Ed25519 wallet public key | yes | Custody wallet | | `signer` | base58 Ed25519 public key | yes | Trading key that signs the permit; never defaulted from `owner` | | `tif` | `GTC`, `GTT`, or `IOC` | no | Defaults to `GTC`, or `GTT` when `expiresAt` is present | | `postOnly` | boolean | no | Incompatible with `IOC` | | `expiresAt` | future Unix milliseconds | GTT only | Required for `GTT`; forbidden for other time-in-force values | `signer` is never defaulted. Pascal signs place and cancel permits with a registered trading key, so an omitted `signer` is refused with a 400 instead of silently signing as the custody wallet. An account whose wallet is its own signing key simply names that wallet as `signer`. The server resolves the Pascal symbol from the catalog, converts price to exact millionths, derives `client_order_id` from the build idempotency key, and fixes the millisecond timestamp and 5,000 ms receive window. The returned artifact pins permit version `1`, production deployment `3`, the 232-byte layout, and the published off-chain-fields digest. Its `message` is hex encoding of the permit bytes; decode the hex and sign those bytes, not the text characters, with Ed25519. Submit only `{ executionId, signature, owner }`, with the signature in base58. Predictefy re-encodes and byte-compares the stored artifact, checks freshness, and verifies the signature against the stored `signer` before relaying the stored request. It persists only the permit, signature, and owner; the private trading key never transits the service. Cancellation is also client-signed. Call the standard cancel route with `{ signer }`, sign the new 96-byte cancel permit, then submit the cancel execution's ID with `{ signature, owner }`. The cancel inherits the original owner and client order id, while allowing the owner to select a currently active trading key. Pascal supports at most ten delegated trading keys; they are revocable, expire within 365 days, and cannot withdraw. That split from the custody wallet is a meaningful custody positive. Confirming an order afterwards needs no credential. Pascal signs writes only — its read API is unauthenticated — so `POST /v1/exec/pascal/orders/{executionId}/refresh` takes an empty body, and the server reads Pascal's own account snapshot and fill history for the wallet named on the stored permit. The background reconciler uses that same keyless read, so a Pascal order can also be resolved server-side without you asking. That read is deliberately one-sided, and this is a property of the venue rather than a gap in the lane. Pascal's order objects carry no status field: a closed order simply leaves the open-order list, whether it filled, was canceled, expired, or was resolved by the matching engine. Disappearance is therefore not a verdict and is never turned into one. Refresh reports `filled` only when the account's own fill rows for that exchange order id cover the size you built, and `acked` while the order is still listed on the book; every other shape leaves your stored status untouched. An order canceled or expired at Pascal keeps its last known Predictefy status, because the venue publishes no reason and inventing one would be worse than saying nothing. "Every other shape" includes a venue response that does not parse the way the contract says it should. Order ids and sizes are matched as exact `u64` decimal strings — never coerced through a number, which would round a 19-digit id onto its neighbour and credit you another order's fills — and repeated `trade_id` values are collapsed to one fill before anything is counted or stored. A row that fails any of those checks, or that reports a market you did not ask about, or two rows that disagree under one `trade_id`, discard the whole read rather than half of it. You get your stored status back, which is the same answer as a venue timeout. Collateral is catalog-bound: a native bid locks `quantity × price`, while a native ask locks `quantity × (1 − price)`. The fee estimate uses that market's live `taker_fee_rate`. Pascal's tick is price-dependent, so `tick_size_min` alone does not describe it. With `p' = min(price, 1 − price)`, the tick is `max(10^(floor(log10(p')) + 1 − tick_sig_figs), tick_size_min)`, evaluated on the native price actually being signed. A market with `tick_size_min = 0.001` and `tick_sig_figs = 2` therefore accepts `0.55` and `0.055` but refuses `0.555`; the `min(p, 1 − p)` mirror keeps `0.995` as fine-grained as `0.005`. Off-tick prices are refused at build with a 400 naming the tick, rather than by Pascal's `INVALID_PRICE` after you have already signed the permit. :::caution[Pascal eligibility — current, not superseded] Arming changed nothing here. Pascal's Terms PDF restricts Australia, Belgium, France, Germany, Italy, the Netherlands, Ontario, Poland, Quebec, Russia, Singapore, Taiwan, Thailand, the UK, the US, and comprehensively sanctioned jurisdictions including Iran, Syria, Cuba, North Korea, Crimea, Donetsk, and Luhansk. Its automated-access and data/API clauses remain under owner review. Eligibility and enforcement are the operator/compliance responsibility; an armed lane is not permission to trade from a restricted location. ::: :::note[Historical arming context — superseded 2026-08-15] **Superseded 2026-08-15** (the arming status only — the restrictions above are current). Before arming, this page recorded measured venue-wide flow of only about **$3.2k notional/day on 2026-08-11** despite real displayed depth, and stated that source-ready code was not permission to arm the lane. Enablement was taken as an explicit owner decision on 2026-08-12. ::: --- # Polymarket US > The build request schema, signing scheme, and bounds for Polymarket US. Source: https://docs.predictefy.com/guides/trading/polymarket-us/ ## Step zero — from nothing to your first trade Polymarket US uses a venue-custodied account and is separate from wallet-based Polymarket on Polygon. No wallet or chain transaction is part of this lane. 1. **Create the account.** Sign up at [polymarket.us](https://polymarket.us/). Identity verification is mandatory before API trading; expect government-ID KYC. 2. **Create credentials.** In your venue account's API settings, create the UUID key ID and base64 Ed25519 API secret. This secret authenticates requests; it is not a Solana wallet key. Send it only for an authenticated call. Predictefy uses it transiently and never persists it. 3. **Fund it.** Deposit venue-custodied USD through Polymarket US's regulated in-venue bank or card rails. No crypto collateral or bridge route applies. Start with enough to meet the selected market's published `minimumTradeQty` plus fees; there is no repo-grounded global minimum. 4. **Allow time.** Setup is about 10 minutes after approval; KYC may take a day. 5. **Check access.** Eligibility, geography, KYC, and other compliance duties remain yours; confirm access before funding. ## What you need first - **Production status:** Armed for build, submit, and cancel as verified on 2026-08-15. - **Wallet and chain:** No order-body wallet signature or chain transaction. The Ed25519 secret is request-auth material and is not a Solana wallet key. - **Venue account:** Yes. Use an identity-verified, funded Polymarket US account. - **Credentials:** A caller UUID key ID and base64 Ed25519 API secret. Keep both in your process and send them only for the authenticated call; Predictefy never persists them or generated headers. - **Funding:** Fund the venue-custodied account through regulated in-venue rails. The funding registry pins no crypto collateral or bridge route. Polymarket US is source-ready but default-off. `GET /v1/exec/venues` will omit it unless an owner explicitly sets `POLYMARKET_US_EXECUTION_ENABLED=true`; this implementation did not enable any environment. Build resolves the native slug and `:long|short` outcome from Predictefy's catalog and returns a `buildVersion: 1` authless request. :::note[Production update — 2026-08-15] The default-off statement above is the 2026-08-11 source-ready record. Production now advertises build, submit, and cancel for Polymarket US. ::: | Field | Type | Required | Meaning | | ----------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------ | | `marketId` | string | yes | Exact catalog-native market slug | | `outcomeId` | `:long` or `:short` | yes | Exact catalog-native outcome id | | `side` | `"buy"` or `"sell"` | yes | Combined with the outcome to select the venue intent | | `type` | `"limit"` | yes | Market/cash-sized orders are not supported | | `amount` | number > 0 | yes | Contract quantity aligned to catalog `minimumTradeQty`; `cashOrderQty` is never sent | | `price` | number from 0.01 through 0.99 | yes | User/outcome price rounded to the catalog market's `orderPriceMinTickSize` | The venue publishes both increments per market. Predictefy persists them during catalog ingest, includes the exact values in the authless artifact, and fails build closed if either rule is absent or unsupported. It does not assume every market uses the fixture's 0.001 price tick or quantity 1. Submit accepts only `executionId`, canonical UUID `keyId`, and `secretKey`. The lane checks version, bounds, complete body shape, stored intent, and notional before credential access, then creates `X-PM-Access-Key`, `X-PM-Timestamp`, and `X-PM-Signature` over `${timestamp}${METHOD}${pathname}`. The venue does **not** include the body in this signature, so a captured header set is replayable with a different body during the venue timestamp window. The relay is TLS-only, rejects redirects, and never logs headers or bodies; operators must preserve that rule in proxy and APM configuration. Credentials and auth headers are never persisted. Identity verification is required before API trading. Polymarket US documents a 20 requests/second limit per API key, so callers retain their own quota instead of sharing a service key. Eligibility, geography, and legal/compliance duties remain with the caller and operator. The separate ISV/IB institutional-partner path is out of scope. The repo guide `docs/polymarket-us-execution.md` carries the official source list and full security boundary. --- # Polymarket > The build request schema, signing scheme, and bounds for Polymarket. Source: https://docs.predictefy.com/guides/trading/polymarket/ ## Step zero — from nothing to your first trade 1. **Create the account.** Open [polymarket.com](https://polymarket.com), connect a Polygon-capable EOA, and follow the venue's current account and eligibility prompts. The repo sources do not establish a separate identity-check flow. 2. **Set up credentials.** If this is your first wallet, create one, store its seed phrase offline, and never share the private key. Predictefy never sees that key. You do not mint CLOB credentials manually: on the first direct SDK call, the wallet signs Polymarket's `ClobAuth` message and the SDK derives existing CLOB credentials or creates them locally. 3. **Fund it.** A practical first test is about $10–$25 of pUSD on Polygon (`137`), not a venue minimum. If funds are elsewhere, use `GET /v1/bridge/quote`; existing USDC.e can also be wrapped through the documented funding steps. 4. **Allow time.** Budget 1–2 hours for a first wallet and cross-chain funding; much less if both are ready. Polymarket applies regional eligibility rules and evaluates the submitting connection. Do not mask your location. ## What you need first - **Production status:** Armed for build, submit, and cancel. Hosted requests use eligible regional egress and reach Polymarket; a live credentialed submit has not yet been verified on that path. The venue-direct SDK remains supported and awaits one eligible-wallet live lifecycle pass. - **Wallet and chain:** A Polygon (`137`) EOA signs. The funds-holding maker may instead be a Polymarket Proxy or Safe for signature type `1` or `2`. - **Venue account:** Use a Polymarket-eligible wallet/account. The SDK derives or creates its CLOB credentials for the verified signer and account. - **Credentials:** On `client.accounts.polymarket`, the wallet key and CLOB key/secret/passphrase stay in the caller's process and go only to Polymarket. The hosted path accepts the triple transiently through eligible regional egress. - **Funding:** Polygon pUSD in the EOA, Proxy, or Safe, with approval to the applicable V2 CTF or NegRisk exchange. LI.FI can land pUSD; existing USDC.e can be wrapped through the documented funding steps. Polymarket is server-built by default. Send plain intent parameters; Predictefy resolves the CLOB token through its own catalog, fails closed unless live `GET /version` returns exactly `2`, and reads the token-bound tick, fee details, and NegRisk value from `GET /clob-markets/{conditionId}`. The CLOB NegRisk value must agree with Gamma for a new server build. Predictefy then returns the complete unsigned V2 EIP-712 order. The server never receives a private key or signs the order. | Field | Type | Required | Meaning | | --------------- | --------------------------------------- | ------------------------------------- | ------------------------------------------------------------- | | `asset` | uint256 decimal string or safe integer | this **or** `outcome` + `outcomeSide` | Catalog outcome id / CLOB token id | | `outcome` | string or safe integer | with `outcomeSide` | Catalog market id | | `outcomeSide` | `"YES"` or `"NO"` | with `outcome` | Selects the market's catalog outcome token | | `isBuy` | boolean | yes | BUY when true, SELL when false | | `price` | number or numeric string in **(0, 1)** | yes | Rounded with the live CLOB tick rules | | `size` | number or numeric string > 0 | yes | Outcome shares; rounded down to two decimals | | `owner` | `0x…` EVM address | yes | Controlling EOA signer and CLOB `POLY_ADDRESS` identity | | `funder` | `0x…` EVM address | signature type `1` or `2` | Inventory-holding Proxy/Safe maker; must differ from `owner` | | `signatureType` | `0`, `1`, or `2` (default `0`) | no | EOA, Polymarket Proxy, or Polymarket Gnosis Safe verification | | `expiresAt` | non-negative Unix seconds (default `0`) | no | Wire-only expiration: `0` builds GTC; positive builds GTD | | `metadata` | bytes32 hex (default zero bytes32) | no | Signed V2 metadata | | `builder` | bytes32 hex (default zero bytes32) | no | Signed V2 builder code | For signature type `0`, omit `funder` or set it equal to `owner`; the order has `maker = signer = owner`. For types `1` and `2`, `funder` is required and must differ from `owner`; the signed order has `maker = funder` and `signer = owner`, and the EOA owner signs and remains the CLOB L2-auth identity. The response keeps the established unsigned envelope (`order`, `domain`, `structHash`, exchange, collateral, NegRisk disclosures) and carries `buildVersion: 2` plus canonical `types`, `primaryType: "Order"`, and `message`. Pass those typed-data fields directly to `eth_signTypedData_v4`. The signed V2 order fields, in order, are `salt`, `maker`, `signer`, `tokenId`, `makerAmount`, `takerAmount`, `side`, `signatureType`, `timestamp`, `metadata`, and `builder`. `timestamp` is generated in Unix milliseconds. `expiration` remains in the relayed wire order for GTC/GTD but is not signed. V1-only `taker`, `nonce`, and `feeRateBps` fields are neither accepted nor relayed. Fees are operator-set and estimated from the live market `fd` curve, plus the live builder taker fee when a nonzero builder is used. The domain is `Polymarket CTF Exchange`, version `2`, on chain 137. The standard exchange is `0xE111180000d2663C0091e4f400237545B87B996B`; NegRisk uses `0xe2222d279d744050d28e00520010520000310F59`. Both settle pUSD (`0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`) with six decimals. The echoed `buildResult` path also requires V2; a V1 echo is rejected as a new-order artifact. ### Client-side submit: the working Polymarket route Polymarket geo-evaluates the network connection that submits the CLOB order. Earlier hosted attempts from Predictefy's Railway US egress were venue-rejected. Hosted requests now use eligible regional egress, and venue reads and order requests reach Polymarket through it; a live credentialed submit remains unverified. The caller-direct SDK route remains supported: it uses Predictefy only to build the complete unsigned order, reconstructs and verifies that server artifact, signs it with the caller's wallet, and sends the signed order directly from the caller's connection to `https://clob.polymarket.com`. ```ts import { Predictefy, PREDICTEFY_EXEC_BASE_URL } from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, execBaseUrl: PREDICTEFY_EXEC_BASE_URL, venueCredentials: { polymarket: { address: account.address } }, }); const placed = await client.accounts.polymarket.createOrder( { idempotencyKey: 'your-stable-build-key', asset: '987654321', isBuy: true, price: 0.4, size: 10, owner: account.address, signatureType: 0, }, account, ); const accountAuth = { address: account.address, signer: account }; const status = await client.accounts.polymarket.fetchOrder(placed.orderId, accountAuth); const canceled = await client.accounts.polymarket.cancelOrder(placed.orderId, accountAuth); ``` Before signing, the SDK binds the returned token, side, rounded maker/taker amounts, identities, signature type, metadata, builder, and expiration to the original caller intent. Only an explicit allowlist of plain order fields crosses the hosted build boundary; an unknown field is rejected by name while its value remains redacted. Both token-selection modes are independently bound. With `asset`, the signed CLOB token id must equal the caller's value and the SDK performs no Gamma lookup. With `outcome` + `outcomeSide`, the SDK sends the caller-pinned catalog id — never a builder-selected condition or market id — to `https://gamma-api.polymarket.com/markets/{id}`, requires exactly one case-insensitive Yes and one No outcome, and checks the signed token against the requested side by array position. Gamma's authoritative `negRisk` boolean also pins the EIP-712 domain to the fixed standard or NegRisk exchange. The Gamma request carries no headers, refuses redirects, and its immutable mapping is cached per SDK client. A venue-read failure, malformed mapping, foreign-market token, opposite-side token, or exchange mismatch is a typed refusal before `signTypedData`. On the first call, the SDK asks the same wallet to sign Polymarket's `ClobAuth` EIP-712 message, tries to derive the wallet's existing CLOB API credentials, and creates them if no credential exists for nonce `0`. It caches the triple by explicit account and verified signer address. Every cache lookup re-verifies the signer, and authenticated status, cancel, and open-orders calls require an explicit account context. You may instead provide an existing triple under `venueCredentials.polymarket` for that address. The private key remains inside the caller's signer. The order signature, `ClobAuth` signature, CLOB API credentials, and HMAC headers never transit a Predictefy host. The optional hosted `fetch` override is never reused for venue traffic; a separate credential-trusted `venueFetch` override is available when required. Credentialed requests are pinned to `clob.polymarket.com`, every redirect is refused, and transport/HTTP failures return only typed generic errors without upstream text. This path exposes the caller's real connection to the venue; callers must still satisfy Polymarket's own account and jurisdiction rules and must not mask their location. The endpoint shapes are checked against current official Polymarket documentation, and fixture tests byte-pin the L1 typed data, L2 HMAC headers, and order body against the existing execution implementation. No live venue call is made by the test suite. One eligible-wallet derive/create → submit → status → cancel pass is still required before calling this route production-live. --- # PRED > The build request schema, signing scheme, and bounds for PRED. Source: https://docs.predictefy.com/guides/trading/pred/ ## What you need first - **Production status:** **Darked on 2026-08-13.** PRED is absent from the 13 armed hosted lanes; `PRED_EXCHANGE_ADDRESSES` is empty. The build/sign contract below remains documentation for the source-ready lane and a possible caller-owned future arrangement. - **Wallet and chain:** A caller-owned PRED Safe on Base (`8453`) is the maker, and a distinct EVM EOA controlling that Safe signs the type-2 order client-side. - **Venue account:** A caller-owned PRED credential arrangement is required. Predictefy's platform key is not accepted for orders made by caller-owned Safes. - **Credentials:** The source-ready submit shape uses the caller's API key plus access and refresh JWTs for that one request; none is retained. There is no supported hosted credential path today. - **Funding:** Do not fund for hosted submit while the lane is dark. The registry verifies neither the collateral asset nor the Safe funding/enablement route and exposes no hosted helper. :::danger[Hosted execution darked — 2026-08-13] The venue confirmed in writing that platform-key submissions for caller-owned Safe makers are refused. Predictefy therefore emptied `PRED_EXCHANGE_ADDRESSES` and darked the lane. That allowlist is the registration gate for the **whole** lane, not just submit, so `POST /v1/exec/pred/orders/build` also answers `404 VENUE_NOT_SUPPORTED` today — no PRED verb is reachable over the hosted API. Source-ready build and signing documentation is retained so callers with their own venue arrangement—and a future evidence-backed re-arm—can use the exact artifact contract without implying hosted access. ::: PRED is a strict server-built Safe-maker lane and has no client-echo compatibility path. It ships **default off and unregistered**. `PRED_EXCHANGE_ADDRESSES` is empty by default and accepts only the three live-verified Base exchanges; a catalog market whose promoted parent contract is not armed fails closed. Check `GET /v1/exec/venues` rather than assuming a deployment enabled it. Send `asset` (`{bytes32 child market id}#0` Long or `#1` Short), or the child `outcome` plus `outcomeSide`, with `isBuy`, selected-outcome probability `price`, `size`, Safe `owner`, and its distinct EOA `signer`. The server reads the parent id and verifying contract from catalog metadata, maps normalized buy/sell semantics to PRED Long/Short intent, fixes fee to 0 and signature type to 2, generates the `02` + 10-digit salt, and returns `buildVersion: 1` with the complete typed data. The domain is `Pred CTF Exchange`, version `1`, chain `8453`; it is never a hardcoded exchange address. Sign the stored digest with the EOA, then submit `{ executionId, signature, owner, apiKey, accessToken, refreshToken }`. Artifact version/integrity and EOA recovery run before the lane reads any caller auth. Only `{ order, signature, owner }` is persisted. The API key gates PRED login, the access JWT authenticates order placement, and all three values remain caller-supplied per request; the original rollout left whether that partner key could serve caller-owned Safes awaiting written founder confirmation. **Superseded 2026-08-13:** the venue answered that platform-key submissions are refused, so no Predictefy-hosted submit path is currently supported. ## What PRED never had, dark or armed The darking above is only about build and submit. Everything else on this venue was never built at all, so its absence is not a consequence of the dark lane and would not return if the lane re-armed: - **Cancel** — no PRED cancel lane exists. The registry row is `cancelOrder: false`, so `POST /v1/exec/pred/orders/{id}/cancel` is an honest `NOT_SUPPORTED`, not a flagged-off verb. - **Modify** — no PRED modify lane. Hyperliquid is the only venue with one. - **Redeem / settlement** — the settlement registry records PRED as `not_applicable` and unserved; Base CTF-style redemption is unresolved, so `intent: "redeem"` answers `501 NOT_IMPLEMENTED`. - **Status refresh** — the lane implements no status read, so `POST /v1/exec/pred/orders/{id}/refresh` is not supported and no background poll exists. - **Account reads** — balances, positions, open orders, and fills are all registry `not_supported`; there is no hosted PRED account lane and no `client.accounts.pred`. Read this list as capability truth rather than as silence about verbs waiting behind the dark flag. PRED Safe enablement is a separate high-risk flow: the venue asks for a raw secp256k1 signature over its returned `transactionHash`, without an EIP-191 prefix. Use `signPredSafeTransaction` from the TypeScript SDK. It rebuilds the canonical Safe EIP-712 hash from every returned transaction field, checks the token/spender/amount against the encoded ERC-20 approval, and refuses to sign any mismatch. :::caution[PRED eligibility] PRED restricts the US, UK, France, Ontario, Singapore, Poland, Thailand, and Taiwan, and prohibits VPN/proxy or other location masking. Eligibility and enforcement are the operator/compliance responsibility; source-ready code is not permission to arm or trade from a restricted location. ::: --- # Predict.fun > The build request schema, signing scheme, and bounds for Predict.fun. Source: https://docs.predictefy.com/guides/trading/predict-fun/ ## Step zero — from nothing to your first trade 1. **Create the account.** Open [predict.fun](https://predict.fun), connect the EOA you will trade from, and follow the current prompts; the repo sources do not document the identity-check sequence. 2. **Set up credentials.** Create a wallet and secure its seed phrase; never share the private key, which Predictefy never sees. Get the API key in your venue account's API settings. There is no session token to copy: the SDK has your wallet sign Predict.fun's dynamic auth message locally and exchanges it directly for the JWT. 3. **Fund and approve.** A practical first test is about $10–$25 of USDT on BNB Smart Chain (`56`), plus a little BNB for approval or on-chain cancellation gas—not a venue minimum. If funds start elsewhere, use `GET /v1/bridge/quote` before signing and broadcasting its transaction request. 4. **Allow time.** Budget 1–2 hours for a first wallet, bridge, and approval; less if already funded. Hosted requests use an eligible regional egress relay and reach Predict.fun; a live credentialed submit remains unverified. The direct path still requires your connection to be venue-eligible. ## What you need first - **Production status:** Armed for build, submit, and cancel. Hosted requests use eligible regional egress and reach the venue; a live credentialed submit has not yet been verified on that path. The venue-direct SDK path is source-ready and still awaits one eligible submit-to-status live pass. - **Wallet and chain:** An EOA on BNB Smart Chain (`56`); Safe and proxy identities are rejected. - **Venue account:** Yes. The working direct path needs a Predict.fun API key and a wallet-authenticated venue session. - **Credentials:** `client.accounts.predictfun` keeps the API key and wallet private key in-process, creates the JWT directly with Predict.fun, and sends neither to Predictefy. The hosted path instead accepts the caller's session bearer transiently for submit, cancel, or refresh. - **Funding:** BSC USDT in the EOA, with approval to the exchange selected by the market's exact NegRisk/yield-bearing type. The LI.FI helper can fund the wallet. Predict.fun is also server-built by default, using the same plain selector and order fields. It is strictly EOA-only: signature type `0`, with `maker == signer == owner`; any Safe/proxy request is a typed `400 VALIDATION_ERROR`. The lane fetches the exact catalog market from Predict.fun, requires the selected on-chain outcome token to occur exactly once, reads its current `feeRateBps`, and resolves the exchange from the exact `(isNegRisk, isYieldBearing)` pair. Missing type truth or any catalog/API/exchange disagreement fails closed with nothing persisted. LIMIT amounts follow the pinned 1.3.8 SDK: 18-decimal price/size inputs are truncated to three and five significant digits respectively, and size must be at least `0.01` shares. Omitted `expiresAt` uses the SDK's `2100-01-01` no-expiry sentinel; an explicit Unix timestamp must be in the future and is carried verbatim. The server uses a full-width 256-bit cryptographic salt because Predict.fun's relay also preserves salt as a decimal string. The response includes the venue-shaped unsigned payload, `buildVersion: 1`, and complete EIP-712 fields. An active session bearer is needed only when submitting, cancelling, or refreshing; it is never accepted in this build body. The legacy echoed `buildResult` remains accepted unchanged. ## Client-side submit route Predict.fun rejects order traffic from restricted regions, and hosted submits now route through Predictefy's eligible regional egress relay (venue reads and order requests verified to reach the venue through it, 2026-08-22 — a live credentialed submit has not yet been verified on that path). The client-side route below stays fully supported for callers who prefer their own eligible connection: it builds at Predictefy, signs in the caller's wallet, and submits under the caller's own API key and in-process JWT: ```ts const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, execBaseUrl: PREDICTEFY_EXEC_BASE_URL, venueCredentials: { predictfun: { apiKey: process.env.PREDICTFUN_API_KEY!, privateKey: process.env.PREDICTFUN_WALLET_KEY!, }, }, }); const placed = await client.accounts.predictfun.createOrder( { idempotencyKey: 'your-stable-build-key', outcome: 12345, outcomeSide: 'YES', isBuy: true, price: 0.4, size: 10, owner: wallet.address, }, wallet, ); const status = await client.accounts.predictfun.fetchOrder(placed.orderHash); ``` Before asking the wallet to sign, the SDK independently reconstructs the exact Predict.fun EIP-712 order, then reads the market's authoritative record from the fixed `api.predict.fun` origin and refuses to sign unless everything agrees: the signed token must be the named outcome the caller requested (outcomes are matched by the venue's own outcome names — non-binary shapes refuse rather than guess), the EIP-712 `domain.verifyingContract` must be the exact exchange contract selected by the market's authoritative `isNegRisk`/`isYieldBearing` flags among the four known contracts, and the signed `feeRateBps` must equal the market's authoritative fee. A failed or malformed venue read refuses before signing, and the immutable mapping is cached per client per market. Intent binding is mandatory: the exported signing helper requires the original order intent and a trusted market mapping at both the type and runtime level — there is no structural-only signing path for this venue. String prices and sizes are parsed losslessly (plain decimals up to 18 fractional digits; anything else refuses), so precision drift can never smuggle different economics past the amount check. Submission sends the exact server-lane wire body under the caller's `x-api-key` and an in-process JWT (the dynamic auth message is signed by the caller's wallet and exchanged directly with the venue). Every credentialed call goes only to the fixed venue origin and refuses redirects; failures are typed and generic. A successful create requires only the documented `orderId` and `orderHash`; a missing status field is treated as acknowledged, never as a failure — a placed order is never reported as failed. Cancellation is the one verb where this SDK path is narrower than the hosted lane. **On this venue-direct path**, `client.accounts.predictfun.cancelOrder` is honestly refused as unsupported: the venue's `/v1/orders/remove` only removes the book entry while the signed order remains valid, and full invalidation requires an on-chain `CTFExchange.cancelOrders` transaction this SDK client does not send. **The hosted lane does both** — its registry row is `cancelOrder: true`, and `POST /v1/exec/predictfun/orders/{executionId}/cancel` takes a `mode`: - `mode: "book"` (the default) relays the venue's own `POST /orders/remove` under your transient session bearer — the book removal, with the same residual the venue itself warns about. - `mode: "onchain"` returns an unsigned BSC transaction encoding `CTFExchange.cancelOrders([order])`, built from the stored order struct and its stored signature. You sign and broadcast it yourself; Predictefy verifies chain, recovered maker, and byte-equal `{to, data, value}`, then records `signed` without claiming a broadcast. A target that was never submitted has no stored signature and fails closed. So the honest summary is: no single call fully invalidates a Predict.fun order, but both halves are implemented — one on each hosted cancel mode — and only the venue-direct SDK client refuses. This implementation is source-ready. One eligible submit → status pass remains pending. --- # PredictStreet > The build request schema, signing scheme, and bounds for PredictStreet. Source: https://docs.predictefy.com/guides/trading/predictstreet/ ## What you need first - **Production status:** Armed for build and submit, with no hosted cancel capability, as verified on 2026-08-15. - **Wallet and chain:** An EOA on ADI Chain (`36900`) signs a VAULT-type EIP-712 order. - **Venue account:** Yes. Provision the caller-specific PredictStreet vault before building and obtain the caller's PredictStreet API key before submitting. - **Credentials:** The EOA private key stays client-side. The API key transits only submit, after signature recovery over the stored digest, and is never retained. - **Funding:** Deposit six-decimal ADI-chain USDC.e into the caller's vault. Approve that dynamic vault and call `depositERC20`; the execution lane only resolves `vaultOf` and has no funding helper. PredictStreet is a strict server-built lane with no client-echo compatibility path. It is **source-ready but default off and unregistered**. An empty or unknown-only `PREDICTSTREET_EXCHANGE_ADDRESSES` registers nothing; check `GET /v1/exec/venues` before building. :::note[Production update — 2026-08-15] The default-off wording above records the source-ready phase. Production now advertises build and submit for PredictStreet; it does not advertise cancel. ::: Send the native decimal token id as `asset`, or `outcome` + `outcomeSide`, with `isBuy`, a `0.01`-tick probability-dollar `price` from `0.01` through `0.99`, up to six-decimal share `size`, and the EOA `owner`. Optional `expiresAt: 0` builds GTC, `postOnly` defaults false, and the only accepted signature type is VAULT `1`. The server resolves the catalog outcome, then rechecks the live condition id, token id, open/tradable state, taker fee, and NegRisk flag. It also calls the verified VaultFactory's read-only `vaultOf(owner)`; a missing vault fails before any execution is persisted. The artifact pins ADI Chain `36900`, six-decimal USDC.e `0x9cb8142aEBBcdc60AF7c97Af897A67A8f3CA71C2`, `buildVersion: 1`, and EIP-712 domain `PredictStreet` version `1`. Standard markets use exchange `0x90EA87493E208A14011EC700Ac9cbAf4d064acc0`; NegRisk markets use `0x79ACbb874dd01044FA38a89c1478E60FaAB40D00`. The signed 11-field order is `salt`, `maker`, `signer`, `taker`, `tokenId`, `makerAmount`, `takerAmount`, `expiration`, `feeRateBps`, `side`, and `signatureType`. BUY notional rounds up one collateral atom; SELL notional rounds down. Sign the returned typed data in the owner's wallet. Submit `{ executionId, signature, owner, apiKey }`. Predictefy validates the versioned server-stored artifact and recovers the signer over its stored digest before it reads the API key. It then relays that exact stored order once and discards the key. Cancel, status refresh, and hosted settlement are not implemented. Predict Street Limited states it operates under Gibraltar licence 167, and FIFA names it the official prediction-market partner of the FIFA World Cup 2026; jurisdiction and user eligibility remain the operator's and integrator's compliance responsibility. --- # Rain > The build request schema, signing scheme, and bounds for Rain. Source: https://docs.predictefy.com/guides/trading/rain/ ## Step zero — from nothing to your first trade 1. **Create the account.** Start at the venue's site and connect an Arbitrum EOA. The repository sources do not establish whether Rain registration is required or what identity check applies, so verify that prerequisite with the venue. 2. **Set up signing.** A fresh user must first create an EVM wallet and secure the seed phrase offline. Rain needs no caller API credential: the wallet signs and broadcasts locally, and Predictefy never sees its private key. 3. **Fund it.** Hold USDT on Arbitrum and a little ETH for gas. Start with at least 1 USDT for a limit buy; a sell must also meet 1 USDT of notional. Approve only the operator-armed per-market Diamond with a finite allowance. If your funds are on another chain, use `GET /v1/bridge/quote` as the assisted LI.FI route. 4. **Allow time.** Budget about 1–2 hours for a first wallet, bridge, and approval; an already-funded Arbitrum wallet is faster. The allowed sources list no Rain-specific geo gate, so confirm venue eligibility before funding. ## What you need first - **Production status:** Armed for build, submit, and cancel as verified on 2026-08-15. Hosted submit verifies the signed transaction and records `signed`; broadcasting remains client-owned. - **Wallet and chain:** An EOA on Arbitrum (`42161`) signs and broadcasts the raw transaction. - **Venue account:** The lane uses no account credential. Repository evidence does not establish whether Rain registration is required, so verify that prerequisite with the venue. - **Credentials:** No caller venue credential enters the lane. The EOA private key and selected RPC remain in the caller's process. - **Funding:** Arbitrum USDT in the EOA, with a finite approval to the operator-armed per-market Diamond. The LI.FI helper can fund the wallet. :::note[Production update — 2026-08-15] Rain's earlier code-ready/default-off phase is superseded for production arming. The client-owned broadcast boundary and per-market safety checks below are unchanged. ::: Rain's standard hosted order path is a source-verified **LIMIT** build. Omit `action` or set it to `"limit"`; choose `orderSide: "BUY"` or `"SELL"`. The opt-in `action: "market"` path requires explicit slippage and deadline protection. `action: "approve"` remains available for the pool's USDT allowance. Every build resolves the pool Diamond from the Core catalog. Caller-supplied pool fields cannot replace it. At one pinned Arbitrum block, the server verifies that the pool's `FACTORY()` is the official RainDeployer, its UUPS implementation, `createdPools(pool)`, Arbitrum USDT with a `1e6` scale, and every required `facetAddress(selector)` route on that pool. A mismatch returns `RAIN_POOL_SAFETY_CHECK_FAILED` and no calldata. :::danger[Rain's public option-side documentation is wrong] Verified V2 Solidity maps `1 = YES` and `2 = NO`; `0` reverts `InvalidSide()`. The API accepts `YES`/`NO` or `1`/`2` and rejects every other value. Option ids are 1-indexed, so option `0` is invalid. ::: For LIMIT builds: | Field | BUY | SELL | | -------- | ------------------------------------------------------ | ------------------------------------------------------- | | Quantity | `amount`: base-token atoms (Arbitrum USDT, 6dp) | `shares`: share atoms (6dp) | | ABI | `placeBuyOrder(option, side, price, amount, postOnly)` | `placeSellOrder(option, side, price, shares, postOnly)` | | Minimum | `amount >= 1_000_000` (1 USDT) | `shares * price / 1e18 >= 1_000_000` | | Phase | Order-book phase only | Order-book phase only | `price` is an integer string scaled by `1e18`: `0.01e18` through `0.99e18`, in exact `0.01e18` one-cent ticks. BUY's fourth ABI argument is base-token `amount`; SELL's same-position, same-type argument is `shares`. Swapping those units is rejected before calldata is built. For protected market builds, provide `slippageBps` from 1 through 9999 and `deadlineSecondsFromNow` from 1 through 3600. Zero slippage is rejected because a zero minimum disables protection. The server reads the live block timestamp, uses `getEntryShares` for BUY or `getSellProceeds` for SELL, derives a nonzero `minSharesOut`/`minAmountOut`, and encodes an **absolute Unix-seconds deadline**. The duration itself is never encoded as the deadline. `enterOption` can execute in a live AMM phase or an order-book phase. Verified `sellOption` requires the order-book phase and is rejected during an AMM pool's live window. The AMM-only `getCurrentPrice`, `getImpactedPrice`, and target-price-first `getAmountRequired` views are not used for order-book quotes. Rain permits fewer than 50 active BUY orders and fewer than 50 active SELL orders per option/user. The source counters are per order direction, not keyed by YES/NO. A partial remainder below $0.10 does not rest: BUY dust is refunded and SELL dust remains available. These limits and the 1 USDT minimum surface as typed errors before signing. Cancel one stored limit build through the standard cancel route and provide its emitted positive uint256 `orderId`. The server inherits option, side, price, owner, and BUY/SELL direction from the stored build and encodes `cancelBuyOrders` or `cancelSellOrders` as one scalar option plus three parallel one-element arrays. Cancel has no phase gate in the verified source. Predictefy returns only unsigned `{to,data,value}`. The client signs and broadcasts on Arbitrum. Submit recovers the owner, pins chain `42161`, byte-compares all three fields, and records `signed`; it never relays or claims a broadcast. The lane was introduced default-off and still requires `RAIN_ENVIRONMENT`, a read-only `RAIN_RPC_URL`, and explicit `RAIN_MARKET_ADDRESSES`; production's 2026-08-15 arming supersedes only that earlier deployment state. --- # Settlement claims > Redeeming settled positions with intent redeem through the build and submit path. Source: https://docs.predictefy.com/guides/trading/settlement-claims/ Redeem rides the same build and submit routes. Check the `redeem` flag on `GET /v1/exec/venues` before assuming a venue serves it in your deployment. Every redeem builds with `notionalUsd` 0 and no fee estimate: it withdraws your own winnings to your own wallet and authorizes no spend. - EVM CTF venues (Polymarket, Limitless, Opinion, Predict.fun) take `owner` plus a `conditionId` (`conditionId must be bytes32 hex`) and either `indexSets` (standard redeem) or `amounts` (neg-risk). The collateral and target contract are server-pinned; declaring your own is rejected. Limitless also accepts `mode`: `limitless redeem mode must be 'standard', 'neg-risk-v1', 'neg-risk-v2', or 'neg-risk-v3'`. - Myriad takes `marketId`, `outcomeId`, and `networkId`; the route and calldata are server-resolved (`myriad claim route and calldata are server-resolved`). - Rain takes `marketId` and `owner`; V2 also requires a positive 1-indexed `optionId`. It resolves the pool across both official catalogs, then uses the Diamond loupe to select V1 `claim()` or V2 `claim(uint256 optionId)`. Unknown or ambiguous generations fail closed. --- # Smarkets > The client-side trading lane for Smarkets, its credential doctrine, and the venue limits behind it. Source: https://docs.predictefy.com/guides/trading/smarkets/ :::caution **Dark venue.** Smarkets is implemented but not served on this deployment: hosted `/api/smarkets/…`, account and funding routes return 404, it is excluded from router fan-outs and from the served venue count. The venue-direct SDK client remains in the package for customers who hold their own Smarkets API approval, but it is not a supported product lane until a commercial API agreement is in place. ::: ## What you need first - **Production status:** **Dark.** The venue-direct SDK client shipped in the published `1.0.0-beta.5` packages as a client-side-only integration after the owner decision on 2026-08-18. The hosted execution lane never existed; the client remains only for customers who hold their own Smarkets API approval. - **Wallet and chain:** None. Smarkets is an off-chain fiat exchange; there is no signer, no chain, and no on-chain artifact anywhere in this lane. - **Venue account:** Yes, and it must be **approved by Smarkets as an API user**. An ordinary account that has not been granted venue API access is rejected by Smarkets itself. That approval is granted by the venue, not by Predictefy. - **Credentials:** Your Smarkets account **email and password**. They are sent only from your process directly to `api.smarkets.com` and **never transit Predictefy**. The session token they produce is held in volatile memory and is never logged, returned, persisted, or sent to Predictefy. - **Funding:** The venue's own GBP card or bank rails. The funding registry records `fundingClass: fiat_custodied` with no collateral asset, no chain, and no hosted deposit helper. ## Why there is no hosted lane This is a custody decision, not a missing integration. Smarkets authenticates with a **full account login**. It publishes no scoped, revocable API key that could be limited to order placement — the same credential that places an order can also read the account and move money. A hosted lane would therefore mean relaying, to Predictefy's servers, a credential that controls the caller's entire Smarkets account. That crosses the custody line this platform does not cross: Predictefy holds no user funds and no user credentials, and the way it keeps that promise is by never being on the path where an account credential travels. So the credential doctrine here is stricter than on the hosted lanes, not looser. On a hosted lane a transient venue credential passes through the execution service for exactly one request and is then discarded. On Smarkets, nothing passes through at all. A hosted lane remains a **future maybe** — if Smarkets ships scoped, revocable API credentials, the objection above disappears and the decision can be revisited. It is not planned work today, and nothing on this page should be read as a commitment to build it. ## The client-side write surface ```ts import Predictefy from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, venueCredentials: { smarkets: { email: process.env.SMARKETS_EMAIL!, password: process.env.SMARKETS_PASSWORD!, }, }, }); const order = await client.accounts.smarkets.createOrder({ marketId: '...', outcomeId: '...', side: 'buy', amount: 5, type: 'limit', price: 0.5, }); const cancelled = await client.accounts.smarkets.cancelOrder(order.id); ``` | Verb | Venue call | Notes | | ------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `createOrder` | `POST /v3/orders/` | `type: 'limit'` sends `good_til_halted`; `type: 'market'` sends an aggressive `immediate_or_cancel` limit at buy 9999 / sell 1 | | `cancelOrder` | `DELETE /v3/orders/{id}/`, then a read-back of the cancelled order | Returns the venue's own post-cancel order state rather than an assumed one | `amount` is **your own money at risk** — back stake on a buy, lay liability on a sell — not Smarkets' `quantity` pot. At venue price `p`, the back stake is `quantity × p / 100,000,000` and the lay liability is `quantity × (10,000 − p) / 100,000,000`. The client converts your `amount` into a venue `quantity` and floors it, so the relevant contribution can never exceed what you asked for. ## Venue properties, not gaps Three limits on this lane come from Smarkets and would not change if a hosted lane existed: - **No modify.** Smarkets exposes no order-amend verb, so there is none to wrap. Cancel and replace. - **MFA is unsupported.** This client does not accept MFA secrets, so a login that returns an MFA factor raises `NOT_SUPPORTED` rather than prompting. Use an account without MFA, or do not use this lane. - **No WebSocket and no deep tape.** There is no Smarkets streaming lane. The public trades tape is real but hard-capped by the venue at **five fills per outcome**, and the venue accepts no tape parameters at all, so `limit` is applied locally and a window older than those five reachable fills returns fewer rows rather than a fabricated one. Public quotes are also delayed by the venue. See [Venue coverage](/reference/venues/). `fetchPositions` is likewise unsupported: Smarkets publishes exposure but no positions resource, and this lane does not derive one. :::caution[Protect the login] The email and password can access the entire Smarkets account, including withdrawals, and they exist in your process for as long as the client does. Prefer a dedicated, restricted account, keep them out of application logs, and rotate them if a process that held them is ever compromised. Venue and jurisdiction eligibility remain the operator's and integrator's compliance responsibility. ::: --- # SX Bet > The client-side trading lane for SX Bet, its account preconditions, and the V3 API cutover. Source: https://docs.predictefy.com/guides/trading/sxbet/ ## What you need first - **Production status:** **Client-side only.** There is no hosted SX Bet execution lane, so `GET /v1/exec/venues` never lists it and every `/v1/exec/sxbet/…` route answers `404 VENUE_NOT_SUPPORTED`. The venue-direct SDK integration at `client.accounts.sxbet` has been live since 2026-07-27. - **Wallet and chain:** An EVM wallet on SX Network (chain `4162`). The private key stays in your process and signs every order locally; V3 cancellation uses the caller's API key instead. - **Venue account:** Yes, and the preconditions are hard. The trading wallet must have a **registered sx.bet account** — a key-only wallet is rejected with `INSUFFICIENT_KYC` even when its signature is correct. V3 also requires the account's **proxy wallet to be deployed and pre-funded** before the venue accepts an order. The retired V2 `TokenTransferProxy` approval is not a V3 precondition. - **Credentials:** The wallet private key and an SX Bet API key. V3 requires the API key on every trading and cancellation request. Both stay in your process and go only to sx.bet; neither transits Predictefy. - **Funding:** Six-decimal SX Network USDC in the account proxy. SX Network is not on LI.FI, so the funding lane routes through Glide: `POST /v1/bridge/session`; then `client.accounts.sxbet.depositToProxy` moves funds from the EOA into the proxy. V3's minimum order size is 5 USDC (live `orderSizeMinimum`, obv3 metadata). ## The client-side write surface ```ts import Predictefy from '@predictefy/sdk'; const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY, venueCredentials: { sxbet: { privateKey: process.env.SXBET_WALLET_KEY!, apiKey: process.env.SXBET_API_KEY, // required on V3; optional on V2 }, }, }); ``` - **Limit and market orders:** Both sign one EIP-712 order and post to `POST /orders-v3`. A limit order uses `GTC`; a market order uses `IOC`, where `percentageOdds` is the worst accepted price. - **Cancellation:** `cancelOrder` / `cancelOrders`, `cancelOrdersByEvent`, and `cancelAllOrders` use API-key-authenticated `DELETE /orders-v3`, `/orders-v3/event`, and `/orders-v3/all`. By-id cancellation reports its outcome synchronously. Event/all acknowledge asynchronous batches, and the SDK drains them while `hasMore` remains true. - **Proxy funding:** `client.accounts.sxbet.depositToProxy` builds, signs, and submits the V3 transfer permit. `client.funding.buildPermitRequest` and `POST /orders/approve` are V2-only and refuse under V3. - **Dead-man switch:** `armHeartbeat` and `disarmHeartbeat` use `/heartbeat/v3`. The same `x-sx-api-key` credential is mandatory for V3 trading. ## The V3 API cutover SX Bet retired its V2 order-book and trading API on 2026-08-26 14:00 UTC. The SDK ships both implementations and **defaults to `v3`**. Select a version with the `SXBET_API_VERSION` environment variable (`v2` or `v3`), or pin one explicitly through `SxbetAccountClient`'s `apiVersion` option. `SXBET_API_VERSION=v2` is the only way back and works only against a V2 sandbox. Three V3 differences remain load-bearing, and each one is a hard rejection rather than a soft fallback: - **Routes move.** Order create, cancel, cancel-by-event, cancel-all, metadata, and heartbeat all move to their `-v3` paths. - **The API-key header is renamed.** V3 reads `x-sx-api-key` and rejects the V2 spelling. - **An API key becomes mandatory for trading.** V2 order placement was signature-only; V3 requires the key as well. Disarming the heartbeat also changes shape: V2 has a separate cancel route, while V3 disarms by posting `timeoutSeconds: 0` to the same heartbeat route. The SDK handles that difference for you. ## Venue properties, not gaps - **No hosted lane, no hosted relay.** This client-side integration does not imply hosted execution or unlisted verb coverage. Signing keys and venue credentials never leave your process. - **No withdrawal route.** SX Bet publishes none, so `withdrawFromProxy()` throws `NOT_SUPPORTED` by design. Withdrawals and proxy-to-proxy transfers are Safe-authorised in the sx.bet app, and this lane will not emulate a route the venue does not offer. See [Accounts & funding](/guides/accounts/). - **Order books.** SX Bet has a real CLOB. An earlier Railway-egress incident that returned honest empty books **healed on 2026-08-15**; production `fetchOrderBook` returned real two-sided depth. That healing changed book availability only — the execution boundary above is unchanged. Venue and jurisdiction eligibility remain the operator's and integrator's compliance responsibility. --- # Venue credentials > Which venues need their own API credential, exactly what each one issues, and whether it reaches Predictefy or stays in your process. Source: https://docs.predictefy.com/guides/trading/venue-credentials/ You need one Predictefy API key to read data across every venue. Trading is different: the venue owns the account, the funds, and the credential, so you get that credential from the venue itself. **Fourteen of the sixteen served venues trade through the hosted API.** Four of those need no venue credential at all — a wallet signature is the whole story. Arming is per deployment, so `GET /v1/exec/venues` is the live answer for any given environment. This page answers three questions in one place — does this venue need a credential, what exactly does it issue, and does that credential reach Predictefy or stay in your process. For the full walkthrough of any single venue, follow its guide; each one carries a "Step zero" section covering account creation, funding, and eligibility. ## Two credential paths Both are real, and which one a venue offers is a property of that venue, not a limitation of the API. **Hosted lane.** You call `/v1/exec/{venue}/orders/…` and Predictefy performs the venue call. Where the venue needs a credential, you send it on that request. Predictefy uses it in memory for that one call and never stores or logs it. Predictefy never holds your funds — the venue custodies them. **Client-side lane.** The credential never leaves your process. The venue SDK signs locally and talks to the venue directly. Two venues offer only this path, by custody design. Read [Trading & execution](/guides/trading/) for the full contract, including the required scopes and the spend caps that bound every hosted submit. If you have not chosen a venue yet, start with [Choose your first venue](/guides/trading/getting-started/). ## No venue credential — your wallet is the whole auth story These venues issue no API credential to fetch. You sign with a key that stays yours, so there is no API key screen to visit — though Pascal still requires an eligible venue account first. | Venue | What authenticates you | Where to start | | ---------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------- | | [Hyperliquid](/guides/trading/hyperliquid/) | An EVM master wallet, or an agent you approve with it, signs the L1 action. | [app.hyperliquid.xyz](https://app.hyperliquid.xyz) | | [Limitless](/guides/trading/limitless/) | A Base EOA signs the order. Predictefy holds the partner HMAC credential itself. | [limitless.exchange](https://limitless.exchange) | | [Rain](/guides/trading/rain/) | An Arbitrum EOA signs and broadcasts the returned transaction. | [www.rain.one](https://www.rain.one) | | [Pascal](/guides/trading/pascal/) | Your registered wallet key, or a revocable delegated trading key — no relay credential. Needs an eligible Pascal account and custody wallet first. | [pascal.trade](https://pascal.trade) | Never paste a seed phrase or private key into Predictefy. It never asks for one, and no hosted lane needs it. ## Venue credential required Create these in your own account at the venue, then supply them as each guide describes. Store them outside source control — several are shown once and never again. | Venue | What the venue issues | Where to create it | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | | [Kalshi](/guides/trading/kalshi/) | An API key ID and its matching RSA private key. Record your subaccount if you use one — it must be a non-negative integer. | [kalshi.com](https://kalshi.com) | | [Gemini](/guides/trading/gemini/) | A Trader API key and secret. See the settings note below — three account options must be right or auth fails. | [exchange.gemini.com](https://exchange.gemini.com) | | [XO](/guides/trading/xo/) | A CLOB API key, secret, and passphrase. Your EVM wallet must own an XO smart account for the default model. | [xo.market](https://xo.market) | | [Polymarket](/guides/trading/polymarket/) | A CLOB key, secret, and passphrase. The direct SDK path can derive these locally instead. | [polymarket.com](https://polymarket.com) | | [Polymarket US](/guides/trading/polymarket-us/) | A UUID key ID and a base64 Ed25519 API secret. This is request auth, not a Solana wallet key. | [polymarket.us](https://polymarket.us) | | [Myriad](/guides/trading/myriad/) | A wallet-bound API key and secret, tied to the connected EOA. Bare credentials are not supported. | [myriad.markets](https://myriad.markets) | | [Novig](/guides/trading/novig/) | An account access token you own. Confirm personal API credential access before depositing. | [novig.us](https://novig.us) | | [Opinion](/guides/trading/opinion/) | A user API key, needed for hosted cancel and status. Hosted build and submit use Predictefy's builder key. | [app.opinion.trade](https://app.opinion.trade) | | [Predict.fun](/guides/trading/predict-fun/) | A session bearer for the hosted lane. On the direct SDK path the venue API key and wallet key stay in your process. | [predict.fun](https://predict.fun) | | [PredictStreet](/guides/trading/predictstreet/) | An API key, plus a vault that already exists for your account. **Build and submit only — this lane has no hosted cancel.** | [adipredictstreet.com](https://adipredictstreet.com) | ### Gemini needs three account settings, not just a key A Gemini Trader key authenticates only when the account is configured for it: **time-based nonce enabled, heartbeat disabled, and trusted-IP mode set to Unrestricted**. A key created without these looks correct and still fails. Set them yourself — Predictefy never changes venue account settings or accepts terms on your behalf. Master API keys must also supply an `account`. ## Venue-direct only — your credentials never reach Predictefy One venue integrates client-side by design. This is a custody property rather than a gap: the signing key and the venue credential stay in your process and go only to the venue, and Predictefy never sees either. | Venue | How you trade it | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | [SX Bet](/guides/trading/sxbet/) | `client.accounts.sxbet`, live since 2026-07-27. Your key signs each order locally; a venue API key covers V3 cancellation. | SX Bet does not appear in `GET /v1/exec/venues`, and `/v1/exec/sxbet/…` answers `404 VENUE_NOT_SUPPORTED` by design. Its guide documents the client-side integration in full. ## Smarkets is not served [Smarkets](/guides/trading/smarkets/) is a **dark venue** as of 2026-09-02: implemented, but excluded from hosted routes, router fan-outs, and the served venue count until a commercial API agreement is in place. `/api/smarkets/…`, account, and funding routes return 404. The venue-direct SDK client stays in the package for customers holding their own Smarkets API approval, but it is not a supported product lane. ## PRED is unavailable [PRED](/guides/trading/pred/) has been darked since 2026-08-13: the venue refuses Predictefy platform-key submissions for caller-owned Safes. This is the venue's own model, not a missing integration. Do not fund it for hosted submit while it stays darked. ## A note on where these screens live Each link above is the venue's onboarding entry point, taken from the execution service's own requirements table (`packages/execution/src/venue-requirements.ts`), so it stays tied to what the lane actually expects. The specific screen that issues a credential belongs to the venue and can move without notice, so sign in there and look for API keys, API settings, or developer settings. Before you deposit anywhere, check that venue's guide for eligibility: several venues restrict access by jurisdiction, and one is geo-gated at the network level. ## Check what is actually armed Arming is per deployment and can change. `GET /v1/exec/venues` reports the live state, and it is the answer that matters — not this page, and not the venue's own marketing. --- # XO > The build request schema, signing scheme, and bounds for XO. Source: https://docs.predictefy.com/guides/trading/xo/ ## Step zero — from nothing to your first trade 1. **Create the account.** Start at [xo.market](https://xo.market) and obtain caller-owned XO CLOB access. XO access is partner-provided; the sources do not document its signup or KYC flow. 2. **Set up credentials.** Create the API key, secret, and passphrase in your venue account's API settings. For the default model, your EVM wallet must own an XO smart account. Secure the wallet seed phrase offline. Predictefy never sees the wallet private key; the credential triple transits Predictefy only for a bounded submit and is never persisted. **Known limitation for social-login accounts (2026-08-23):** an XO account created via social login is a ZeroDev smart account whose embedded key produces a wrapped ERC-1271 signature — but XO's `POST /order` documents the wire signature as exactly 65 bytes, so a bare embedded-key signature is rejected by the venue's smart-account check and the wrapped form does not fit the documented wire format. Until this is resolved with the venue, trade XO with a wallet-native account (the EOA holds the collateral and signs directly, `signatureType` 0). Funded verification of the wrapped-signature path is in progress. 3. **Fund it.** Hold six-decimal Bridged USDC (XO) on chain `3223`, approve the verified exchange, and leave collateral headroom above your first order. No numeric minimum or verified deposit route is published in the allowed sources. If funding crosses chains, query `GET /v1/bridge/quote` first and use a venue-confirmed route if XO is unsupported. 4. **Allow time.** Budget 1–2 hours after CLOB access arrives; partner provisioning can take longer. Predictefy has not yet submitted an XO order; confirm your eligibility. ## What you need first - **Production status:** Building, submitting, and cancelling are all served, rebuilt on 2026-08-18 against the venue's current order contract. The builder that shipped before that date signed the venue's previous order struct against its previous exchange and was disarmed the same day; it has been replaced, and the reason to disarm went with it. **Nothing has yet been submitted to XO, so the first live submit is the confirmation checkpoint** — see the note below. - **Wallet and chain:** An XO smart account on chain `3223` is the default identity, and it is both the order's `maker` and its `signer`. A direct EOA is also accepted; see "Signature models". - **Venue account:** Use caller-owned XO CLOB access that provides an API key, secret, and passphrase. The lane establishes no separate account-provisioning flow. - **Credentials:** Your signing key stays client-side. The CLOB triple transits only submit, creates HMAC headers in-process, and is never persisted. - **Funding:** Six-decimal Bridged USDC (XO) in the `maker` account, approved to the verified exchange. There is no hosted helper, and the deposit route remains unverified by the funding registry. Fees are charged in collateral to the `maker`, so leave a little headroom above the order principal. XO is a strict server-built lane and has no client-echo compatibility path. Its hosted lane is registered and armed for build, submit, and cancel. Re-check `GET /v1/exec/venues` before integrating because runtime arming can change independently of this guide. :::caution[No XO order has ever been submitted through Predictefy] Every claim on this page is verified against the venue's own API documentation and, for the signing contract, against the deployed exchange on chain `3223` — the EIP-712 domain our builder computes is byte-identical to the contract's own `domainSeparator()`, and the contract's read-only order-hashing view returns the exact digest we ask you to sign. What has **not** happened is a live credentialed call: no XO order has been placed through this service, so the first live submit is the point at which the wire contract is confirmed rather than derived. Treat an early failure as new information about the venue, not as a regression. ::: Send `asset` (the catalog outcome id), or `outcome` + `outcomeSide`, with `isBuy`, `price`, `size`, and the `owner` account. The indexed catalog lookup resolves the outcome first, and XO's catalog `token_id` becomes the signed `tokenId` verbatim. ### Signature models `signatureType` selects how the venue validates your signature. **`3` is the default** and is the XO smart-account model: the account is both `maker` and `signer`, and the venue checks the signature through ERC-1271 on that contract, so the 65 bytes you supply come from an owner key of the smart account rather than from the maker address itself. `signatureType: 0` is the direct-EOA model, where `maker` and `signer` are your own wallet and the venue recovers the signature normally. The venue's documentation is **not self-consistent here**: its authentication and smart-account guides both describe `3` as the only order-signing model the orderbook accepts today, while the order-placement schema documents `0` for direct EOA integrations and lists it in the accepted enum. The default is `3` because that is the model every page agrees on; `0` is available for callers who want it, and is the contested one. The Polymarket proxy models (`1` and `2`) have no XO identity and are rejected before any venue call. ### Fees **Orders carry no fee rate.** There is no fee field to set or sign; settlement applies the market category's rate to each filled leg. Predictefy reads that rate from the venue's public fee endpoint at build time and quotes it with the venue's documented conviction curve — `fee = size × (rate / 10,000) × price × (1 − price)`, which peaks at a price of `0.5` and falls toward the extremes. The quote assumes a complete fill, so it is an upper bound. The category rate is currently `0`, which makes the quote `0` by that formula rather than by assumption; a rate above the exchange's own on-chain maximum is refused rather than quoted. `expiresAt` takes Unix **seconds**. An explicit `0` is the documented never-expiring GTC opt-in; a future second builds GTD and must be within two years, so a millisecond stamp is rejected rather than signed. **Omitting it no longer means GTC**: the order expires at the catalog market's close date when the row carries a future one, bounded by that same two-year horizon, so a resting order cannot outlive the market it was written against. If the catalog has no usable close date, an omitted `expiresAt` still builds GTC. The returned artifact includes `types`, `primaryType: "Order"`, `domain`, `message`, and `buildVersion: 2`. The exact signed fields are `salt`, `maker`, `signer`, `beneficiary`, `tokenId`, `makerAmount`, `takerAmount`, `expiration`, `nonce`, `identifier`, `metadata`, `side`, and `signatureType`, in that order — thirteen fields; the `signature` is carried alongside them on the wire but is not part of the hash. `beneficiary`, `identifier`, and `metadata` are always zero-filled by server builds: a zero `beneficiary` pays the `maker`, and routing proceeds elsewhere is not a decision this service takes on your behalf. The domain is `XO Market CLOB`, version `1`, chain `3223`, verifying contract `0x4bC5E872256D12E6017dfe466E04c867DC761B77`. Collateral is six-decimal Bridged USDC (XO), `0x80c12230ce677e6f304027a14780edd2a829ab0c`. A missing or stale build version is rejected on both idempotent build replay and submit, before any XO relay — artifacts built by the previous builder carry `buildVersion: 1` and are refused rather than relayed. You sign the complete typed data in your own wallet and submit `{ executionId, signature, owner, apiKey, apiSecret, apiPassphrase }`. Predictefy never receives a private key, never signs, and never persists the credential triple or headers. XO account and funding routes are not implemented. **What the server verifies depends on the signature model, and the difference is real.** State it before anything else, because "the server checks your signature" is only true for one of the two: - **`signatureType: 0` (direct EOA).** Full binding. The signature must recover to the `signer` stored at build, against the **server-stored digest**. A signature that recovers to anyone else is rejected before any venue call. - **`signatureType: 3` (XO smart account — the default).** The server **cannot** bind the signature to an address. The 65 bytes come from an owner key of your smart account, an address this service does not know and has no way to learn, so it checks only that the signature is well-formed. **The authority is the venue**, which passes the digest and signature to your smart account's ERC-1271 `isValidSignature`. That check, on-chain and at the venue, is what accepts or refuses the order. In both models the server enforces that the `owner` you submit equals the `maker`/`signer` it wrote at build, and it relays only the order it built and stored. That is why a signature the server cannot bind is still not a route to anyone else's funds. The order it relays has `maker == signer == ` your own account, pinned at build and re-checked on reload; its `beneficiary` is the zero address, which pays the `maker` and nobody else; and the digest is re-derived from the stored order before relay, so the bytes signed and the bytes sent are the same order. A mismatched signature under `3` can at worst cause the venue to execute **your own** order — or, far more likely, reject it. After those checks Predictefy reads XO `/time`, creates the venue's `XO_*` L2 HMAC headers in-process, and posts the stored order to `/order`. ## Cancel and status refresh Cancel and a client-credentialed status refresh are **served**: `GET /v1/exec/venues` reports `cancel: true` for XO. They were armed on 2026-08-18, when XO's own API documentation answered the two questions that had kept them off — every authenticated XO route reads `XO_*` HMAC header names (not the `POLY_*` forms this CLOB family uses elsewhere), and a cancel acknowledges with an explicit list of the order ids it removed. They need only the CLOB base URL and your own credentials, never an exchange address or an order struct. Cancel follows the same non-custodial choreography as an order: `POST /v1/exec/xo/orders/{id}/cancel` mints a new cancel intent from server-stored truth, and you send that new execution id to `/submit` with your own `apiKey`, `apiSecret`, and `apiPassphrase`. A cancel carries **no signature** — XO authenticates it by HMAC alone — and it spends nothing. A status refresh takes the same credential triple and reads your order back from the venue. A status refresh is also the only way to see a **partial fill**. A resting order keeps its `live` status from the first matched share to the last, so the fill shows up as a matched size against the original size rather than as a status change. A submit acknowledgement reports the same thing in its own units when an order matches part way and then rests. **Scope, stated plainly:** both routes act on an execution **Predictefy already holds** — they take an `executionId`, not a raw venue order id, so they reach XO orders recorded through this service and not orders you placed elsewhere. Cancelling an order placed outside Predictefy is not a route this service offers. **A cancel can legitimately fail, and the venue says so with a `200`.** XO acknowledges a cancel with the ids it actually removed plus a per-id reason for the ones it did not, so a successful HTTP call can still report `failed` with a reason such as `already filled` — the order matched before your cancel arrived. That is a real outcome, not an error in your request. The mapping stays fail-closed in both directions: `canceled` is reported only when the acknowledgement names your order among the cancelled ids, and any answer we do not recognise is reported as `failed` with the venue's own words. :::caution[What an XO cancel can and cannot do] Cancelling removes the order from XO's **order book**. It does **not** invalidate the signed order on-chain: the deployed exchange ships no cancel function, and self-service nonce bumping is restricted to the venue operator, so there is no maker-side on-chain invalidation to offer. The residual exposure is bounded by the exchange's fill functions being operator-only — the only party that could still fill a removed order is the operator you removed it from. Treat a cancel as "withdrawn from the book", not "revoked". ::: :::caution[XO liquidity and eligibility] A read-only scan found only **2 active markets in 3,000 catalog rows**. Treat fill probability and depth as thin until a live book proves otherwise. Venue access and jurisdiction restrictions are an operator/compliance matter; source-ready code is not permission to enable or trade from every region. ::: --- # Webhooks > Register an HTTPS endpoint and receive signed events when a job finishes or an execution changes status, instead of polling. Source: https://docs.predictefy.com/guides/webhooks/ Predictefy can POST a signed event to your HTTPS endpoint when a background job finishes or an execution changes status, so you do not have to poll for either. Endpoint management is API-only today — there is no console screen for it yet. ## Events | Event | Scope | Fires when | | -------------------------- | ------------- | ----------------------------------------------------------------------------- | | `ingest.run.completed` | Platform-wide | the catalog ingest job finishes a run | | `matcher.run.completed` | Platform-wide | the cross-match job finishes a run | | `execution.status.changed` | Your account | a submitted execution, cancel, or refresh commits a **different** status | The two job events carry that job's `run_log` row — `job`, `started_at`, `finished_at`, `ok`, and a `counts` tally. On a failed run `ok` is `false` and `counts.error` holds the message. `execution.status.changed` carries lifecycle metadata only: ```json { "executionId": "8a4fdf19-49e1-4702-9cb8-31fd9c46f7e1", "venue": "hyperliquid", "status": "filled", "previousStatus": "acked", "occurredAt": "2026-08-01T02:03:04.000Z" } ``` It is tenant-scoped: it reaches only endpoints owned by the execution's account. Your account id is routing context and is deliberately **not** in the payload. It fires only on a real transition. An unchanged result, a lost concurrent update, or an already-terminal row emits nothing — so a quiet period means nothing changed, not that a delivery was dropped. ## Registering an endpoint All routes are API-key authenticated and scoped to your account. ```sh curl -s "$PREDICTEFY_API_URL/v1/webhooks" \ -H "Authorization: Bearer pk_live_YOUR_KEY" \ -H "content-type: application/json" \ -d '{"url":"https://example.com/hooks/predictefy","events":["execution.status.changed"]}' ``` The response includes a **`secret` shown exactly once.** Store it immediately; it is never returned again, and listing your endpoints redacts it. `url` must be public **HTTPS**. Localhost, loopback, and private or link-local addresses are rejected — IPv4, IPv6, and IPv4-mapped-IPv6 literals alike. `events` must be a non-empty subset of the table above. - `GET /v1/webhooks` — your endpoints, secrets redacted. - `DELETE /v1/webhooks/{id}` — delete one, along with its queued deliveries. An id that is not yours returns `404`. - `GET /v1/webhooks/{id}/deliveries?after=&limit=` — delivery rows with `id`, `event`, `payload`, `status`, `createdAt`, and `deliveredAt`. `limit` defaults to 50, capped at 200. Rows are always oldest-to-newest; pass `nextCursor` back as `after`. Both SDKs expose this as `client.webhooks.deliveries(id, params)`. For local development, `predictefy webhooks listen [--endpoint ]` polls that route every two seconds. Without an endpoint it creates a temporary non-routable one and deletes it on exit. `--forward http://localhost:PORT/path` re-POSTs each payload locally. ## What a delivery looks like Each delivery is a `POST` to your URL with these headers: | Header | Value | | ------------------------ | -------------------------------------------- | | `Content-Type` | `application/json` | | `X-Predictefy-Event` | the event name | | `X-Predictefy-Timestamp` | Unix seconds when the delivery was signed | | `X-Predictefy-Signature` | hex HMAC-SHA256 — see below | Redirects are **not followed**, and each attempt times out after 10 seconds. ## Verifying the signature The signature is a hex HMAC-SHA256 over `` `${timestamp}.${rawBody}` `` keyed by your endpoint secret. Recompute it over the **exact bytes you received** — not a re-serialized object — and compare in constant time: ```ts import { createHmac, timingSafeEqual } from 'node:crypto'; function verify(rawBody: string, headers: Record, secret: string): boolean { const timestamp = headers['x-predictefy-timestamp']; const signature = headers['x-predictefy-signature']; if (!timestamp || !signature) return false; const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'); const a = Buffer.from(signature, 'hex'); const b = Buffer.from(expected, 'hex'); return a.length === b.length && timingSafeEqual(a, b); } ``` Optionally reject deliveries whose `X-Predictefy-Timestamp` is far from your clock, to blunt replay attacks. ## Retries Respond `2xx` to acknowledge. Any non-`2xx`, network error, or timeout is retried with exponential backoff, then abandoned: | Attempt | Retry after | | ----------------------- | -------------------------------------- | | 1 → 2 | 1 minute | | 2 → 3 | 5 minutes | | 3 → 4 | 30 minutes | | 4 → 5 | 2 hours | | after 5 failed attempts | marked **failed** (no further retries) | **Make your handler idempotent.** For job events, dedupe on the payload's `run_log` identity; for execution events, on `executionId`, `status`, and `occurredAt`. ## Availability Delivery requires `EXEC_WEBHOOKS_ENABLE=true` on the execution service, which defaults to `false`. Endpoint management is gated by `READS_ENABLE_WEBHOOK_ROUTES`. Register an endpoint and check `GET /v1/webhooks/{id}/deliveries` to confirm both are live on the deployment you are calling. --- # WebSocket API reference > Connection, authentication, subscriptions, acknowledgements, data frames, and capability-honest errors. Source: https://docs.predictefy.com/reference/streaming/ Connect to the production WebSocket endpoint: ```text wss://stream.predictefy.com/v1/stream ``` The service accepts JSON text frames. Venue names are normalized to lowercase; venue-native market ids and feed symbols keep their case. API keys are never accepted in the URL. ## Authentication Non-browser clients should send the API key in the upgrade request: ```http Authorization: Bearer pk_live_YOUR_KEY ``` Browser clients cannot set that header. Their first frame must arrive within 10 seconds and have this exact shape: ```json { "op": "auth", "apiKey": "pk_live_YOUR_KEY" } ``` Successful first-frame authentication returns this acknowledgement before queued subscription acknowledgements: ```json { "type": "auth", "status": "ok" } ``` Header-authenticated clients do not receive an auth acknowledgement. A missing, invalid, unknown, revoked, or non-read-scoped key first receives an `UNAUTHORIZED` error frame, then the service closes the socket with code `4001`. A browser client that sends another operation before auth, or does not authenticate before the deadline, is closed the same way. ## Client subscription frames `marketId` is the venue-native upstream id. For example, a Polymarket order book uses the outcome's CLOB asset/token id, while Hyperliquid uses its coin symbol. Per-market order books and trades: ```json { "op": "subscribe", "channel": "orderbook", "venue": "polymarket", "marketId": "" } { "op": "unsubscribe", "channel": "orderbook", "venue": "polymarket", "marketId": "" } { "op": "subscribe", "channel": "trades", "venue": "hyperliquid", "marketId": "BTC" } { "op": "unsubscribe", "channel": "trades", "venue": "hyperliquid", "marketId": "BTC" } ``` Venue-wide order books are available only when the upstream implements a real firehose or multiplexed stream: ```json { "op": "subscribeAll", "channel": "orderbook", "venue": "polymarket" } { "op": "unsubscribeAll", "channel": "orderbook", "venue": "polymarket" } ``` Reference-feed tickers use `feed` and `symbol`, not `venue` and `marketId`: ```json { "op": "subscribeFeedTicker", "feed": "binance", "symbol": "BTC/USDT" } { "op": "unsubscribeFeedTicker", "feed": "binance", "symbol": "BTC/USDT" } ``` The venue option-price lane carries the on-chain market address separately. It is a price stream, not an order book: ```json { "op": "subscribePrice", "venue": "rain", "marketId": "", "marketAddress": "0x..." } { "op": "unsubscribePrice", "venue": "rain", "marketId": "", "marketAddress": "0x..." } ``` The executable-arbitrage lane is cross-venue. Like the feed-ticker lane it carries neither `venue` nor `marketId` — one shared surface spans every priced venue: ```json { "op": "subscribeArbitrage", "executableOnly": true, "venues": ["polymarket", "kalshi"], "minEdge": 0.02 } { "op": "subscribeArbitrage" } { "op": "unsubscribeArbitrage" } ``` `subscribeArbitrage` accepts optional filters applied server-side: `executableOnly` (boolean), `venues` (string array of allowed venues), and `minEdge` (number). Omitting filters preserves the default unfiltered surface. ## Acknowledgement frames A successful per-market subscription is acknowledged before any cached snapshot or live frame: ```json { "type": "subscribed", "channel": "orderbook", "venue": "polymarket", "marketId": "" } { "type": "unsubscribed", "channel": "orderbook", "venue": "polymarket", "marketId": "" } ``` The other acknowledgement shapes are: ```json { "type": "subscribed", "channel": "trades", "venue": "hyperliquid", "marketId": "BTC" } { "type": "subscribed", "channel": "orderbook:all", "venue": "polymarket" } { "type": "subscribed", "channel": "feedTicker", "feed": "binance", "symbol": "BTC/USDT" } { "type": "subscribed", "channel": "price", "venue": "rain", "marketId": "" } ``` An `unsubscribed` acknowledgement uses the same fields. A Rain, XO, or PRED trade subscription served from the configured chain-scanner tape also adds a `disclosure` object: ```json { "type": "subscribed", "channel": "trades", "venue": "rain", "marketId": "", "disclosure": { "provenance": "chain-scan", "latencyMs": 90000, "completeness": "" } } ``` The cross-venue arbitrage lane has nothing to echo, so its acknowledgements carry `channel` alone: ```json { "type": "subscribed", "channel": "arbitrage" } { "type": "unsubscribed", "channel": "arbitrage" } ``` ## Order-book frames For a per-market order-book subscription, the first book is a `snapshot`. Later venue ticks are `update` frames. Both contain a complete book, never a delta. Prices are probabilities in `[0, 1]`; bids are best-first descending and asks are best-first ascending. ```json { "type": "snapshot", "venue": "polymarket", "marketId": "", "data": { "bids": [{ "price": 0.4, "size": 10 }], "asks": [{ "price": 0.42, "size": 8 }], "timestamp": 1780000000000 }, "ts": 1780000000123 } ``` ```json { "type": "update", "venue": "polymarket", "marketId": "", "data": { "bids": [{ "price": 0.41, "size": 9 }], "asks": [{ "price": 0.43, "size": 7 }], "timestamp": 1780000000200 }, "ts": 1780000000210 } ``` When backpressure coalesces skipped book ticks, the latest complete book is sent as another `snapshot` once the socket drains. ## Trade frames Native and configured chain-scanner trade subscriptions share one frame shape: ```json { "type": "trade", "venue": "hyperliquid", "marketId": "BTC", "data": { "id": "", "time": "2s ago", "timestamp": 1780000000000, "type": "Buy", "usd": 125.5, "outcome": "Yes", "outcomeIndex": 0, "shares": 10, "price": 0.55, "maker": "hyperliquid", "transactionHash": "", "wallet": "0x...", "counterparty": "0x..." }, "ts": 1780000000123 } ``` `outcomeIndex`, `wallet`, and `counterparty` can be absent or `null`. `usd` and `price` can be `null` on parimutuel venues where execution-time values do not exist. A chain-scanner frame adds `"provenance": "chain-scan"` at the top level. Trade frames are dropped rather than buffered while the client is backpressured. ## Feed-ticker and option-price frames Feed tickers carry the normalized ticker under `data`. Only `symbol`, `asOf`, and `provenance` are always present; price, volume, timestamp, datetime, and `sourceMetadata` fields are present only when the upstream proves them. ```json { "type": "feedTicker", "feed": "binance", "symbol": "BTC/USDT", "data": { "symbol": "BTC/USDT", "last": 61714.63, "asOf": "2026-08-13T12:00:00.000Z", "provenance": { "source": "binance-ws" }, "sourceMetadata": { "transport": "websocket" } }, "ts": 1780000000123 } ``` The option-price lane currently relays the normalized Rain frame: ```json { "type": "price", "venue": "rain", "marketId": "", "marketAddress": "0x...", "data": { "provider": "rain", "marketId": "", "marketAddress": "0x...", "prices": [ { "choiceIndex": 0, "label": "Yes", "price01": 0.55, "rawPrice": "550000000000000000" } ], "triggeredBy": { "eventName": "", "transactionHash": "0x...", "blockNumber": "123", "logIndex": 4 }, "asOfISO": "2026-08-13T12:00:00.000Z" }, "ts": 1780000000123 } ``` `choiceIndex`, `label`, and `rawPrice` can be `null`. Every field inside `triggeredBy` can also be `null`, and the whole object can be `null`. Feed-ticker and option-price frames are dropped rather than buffered under backpressure. ## Arbitrage frames The arbitrage lane relays one shared server-side recompute of the cross-venue executable-arbitrage surface — the streaming twin of `GET /api/router/fetchArbitrage`. There is one client operation for this lane: `subscribeArbitrage`. It delivers the complete selected surface — the full surface when no filters are set — as `kind: "snapshot"` and sequence-ordered `kind: "delta"` frames. A complete snapshot is due every 30 seconds; on that recompute pass it is sent immediately before the pass's delta. Every successful recompute sends a delta, including an empty `upserts`/`removes` delta when no row changed. Every arbitrage data message has `type: "arbitrage"`, the arbitrage frame in `data`, and a socket-write `ts`. Both data variants carry `exchange`, `seq`, `computedAt`, `publishedAt`, `intervalMs`, `heartbeatMs`, `contracts`, `limit`, and `part: { i, n }`. Snapshot and delta frames are split into messages no larger than 200KiB when needed, and every part of one logical frame shares its `seq`. A new or reconnecting subscriber receives the current coherent snapshot after its `subscribed` acknowledgement. If a client falls behind, the relay repairs it with a current snapshot before resuming incremental delivery. Each base cluster emits every ordered cross-venue pair, up to 90 rows. One batched live-book read per venue supplies the selected outcome books reused across those pairs. A row's `clusterId` is the composite `${clusterId}:${venueA}:${venueB}`. Base cluster ids can contain `:`, so strip the last two colon-delimited segments to recover the base id. ### Snapshot frame ```json { "type": "arbitrage", "data": { "exchange": "router", "kind": "snapshot", "seq": 1042, "computedAt": "2026-08-31T12:00:00.000Z", "publishedAt": "2026-08-31T12:00:00.004Z", "intervalMs": 3000, "heartbeatMs": 30000, "contracts": 100, "limit": 500, "part": { "i": 1, "n": 1 }, "rows": [ { "clusterId": "cluster:real:polymarket:kalshi", "question": "Will Team A win?", "similarity": 0.92, "contracts": 100, "legs": { "buyYes": { "venue": "polymarket", "canonicalMarketId": "polymarket:real", "side": "yes", "executable": true, "reasons": [], "vwap": 0.41, "cost": 41, "fee": 0, "filled": 100, "fullyFilled": true }, "buyNo": { "venue": "kalshi", "canonicalMarketId": "kalshi:real", "side": "no", "executable": true, "reasons": [], "vwap": 0.45, "cost": 45, "fee": 0.7, "filled": 100, "fullyFilled": true } }, "resolution": { "compatible": true, "reason": "", "auditReasons": [] }, "settlementFee": 0, "totalCost": 86.7, "payout": 100, "netEdge": 13.3, "roi": 0.1534, "resolutionEquivalence": "verified", "executable": true, "reasons": [], "label": "arbitrage", "asOf": "2026-08-31T11:59:58.000Z" } ] }, "ts": 1780000000123 } ``` ### Delta frame ```json { "type": "arbitrage", "data": { "exchange": "router", "kind": "delta", "seq": 1043, "computedAt": "2026-08-31T12:00:03.000Z", "publishedAt": "2026-08-31T12:00:03.004Z", "intervalMs": 3000, "heartbeatMs": 30000, "contracts": 100, "limit": 500, "part": { "i": 1, "n": 1 }, "upserts": [ { "clusterId": "cluster:real:polymarket:kalshi", "question": "Will Team A win?", "similarity": 0.92, "contracts": 100, "legs": { "buyYes": { "venue": "polymarket", "canonicalMarketId": "polymarket:real", "side": "yes", "executable": true, "reasons": [], "vwap": 0.41, "cost": 41, "fee": 0, "filled": 100, "fullyFilled": true }, "buyNo": { "venue": "kalshi", "canonicalMarketId": "kalshi:real", "side": "no", "executable": true, "reasons": [], "vwap": 0.45, "cost": 45, "fee": 0.7, "filled": 100, "fullyFilled": true } }, "resolution": { "compatible": true, "reason": "", "auditReasons": [] }, "settlementFee": 0, "totalCost": 86.7, "payout": 100, "netEdge": 13.3, "roi": 0.1534, "resolutionEquivalence": "verified", "executable": true, "reasons": [], "label": "arbitrage", "asOf": "2026-08-31T12:00:02.000Z" } ], "removes": ["cluster:stale:polymarket:kalshi"] }, "ts": 1780000003123 } ``` A row is labeled `arbitrage` only when it has positive net edge, both legs are depth-executable at the requested size against live asks, and resolution equivalence is `verified`. Every other row is served as `indicative price discrepancy` with the per-leg `reasons` codes explaining why. Rows are never filtered down to the winners — the indicative rows are part of the surface, with their evidence. `part` tags each message chunk with `{ i, n }` (1-based part index `i` of total parts `n`), ensuring every published chunk remains ≤200KiB. `seq` is a monotonically increasing sequence counter across snapshots and deltas. `intervalMs` is the true recompute cadence (3000 ms by default). This is a shared server-side recompute, not a tick-by-tick feed. Every successful pass publishes a delta; when the priced surface did not change, that delta has empty `upserts` and `removes`. A pass with a due snapshot publishes the snapshot first and then its delta under the next `seq`, so `computedAt` and `publishedAt` keep publisher liveness observable without pretending the market moved. `heartbeatMs` is the conservative liveness bound the server computed for its own configuration, not a nominal target. It is the first recompute tick at or after the 30000 ms target — 30000 ms at the default 3000 ms interval, and 40000 ms at a 20000 ms one. Successful per-pass deltas normally arrive more often, at the disclosed `intervalMs`; an empty delta is liveness, not a market change. Take `heartbeatMs` from the frame rather than hard-coding it. Sustained silence beyond it means a publisher outage or an entitlement teardown, not a quiet market. The three timestamps let you measure the lane instead of trusting it. `publishedAt − computedAt` is the time the server spent turning a finished computation into a published frame. On live delivery, `ts − publishedAt` is relay and fan-out latency, and nothing on that path deliberately buffers, batches, or waits for a timer. On retained replay, that same gap is the last-known frame's age. The recompute interval is the only deliberate live-delivery delay, and it exists to bound upstream venue API cost rather than as a design preference — expect a change to surface within one interval, and on average within half of one. A new subscriber receives a snapshot of the current surface immediately after its `subscribed` acknowledgement. The replay preserves the frame's original `publishedAt`; only the outer `ts` records the new socket-write time. Compare that age with `heartbeatMs`: a retained frame older than the advertised heartbeat is stale and does not claim that the publisher is still live. If this relay has never observed a valid publisher frame, the request gets `NOT_SUPPORTED` instead of a success acknowledgement. **Staleness contract.** The retained frame's `publishedAt` is the publisher-liveness signal: a healthy publisher refreshes it on every successful recompute, including an empty delta. If `publishedAt` trails the envelope's `ts` by more than roughly 90 seconds (the current SDK default), treat the publisher as stale and fall back to REST. A publisher that goes permanently dark after publishing once therefore continues to yield an aging retained frame instead of reverting to `NOT_SUPPORTED`; that is intentional, and client-side age detection is the safeguard. The TypeScript SDK will surface this condition as a staleness event in the current SDK release train. Per-leg `vwap`, `cost`, and `fee` are `null` whenever the leg cannot honestly be priced as the claimed trade; `note` and `feeBasis` are present only where the venue's fee model needs them. `settlementFee`, `totalCost`, `netEdge`, and `roi` are `null` when the pair cannot be priced, and `asOf` is `null` when either book is unavailable. Arbitrage frames are dropped rather than buffered under backpressure; clients that fall behind receive a fresh snapshot automatically via hub-side stale-client recovery. ## Errors and unsupported capabilities Protocol errors are JSON frames. Depending on the failed operation they echo `venue`, `marketId`, `channel`, `feed`, or `symbol`: ```json { "type": "error", "code": "NOT_SUPPORTED", "message": "venue 'predictstreet' has no native live trade stream", "venue": "predictstreet", "marketId": "", "channel": "trades" } ``` If a venue has neither a native live trade stream nor a configured chain-scanner tape, a trades subscription returns this honest `NOT_SUPPORTED` frame. The socket stays open, no subscription is created, and the service never fabricates polling or trade data. Unsupported order-book, venue-wide, feed-ticker, and option-price subscriptions follow the same non-fatal pattern. `subscribeArbitrage` answers the same `NOT_SUPPORTED` code — echoing `channel` alone — when no Redis relay is configured or when the wired relay has not yet observed any valid publisher frame. No success acknowledgement or client subscription is created; the client can retry after the publisher is enabled. The arbitrage channel is additionally gated on the same `arbitrage` plan feature as the REST verb `GET /api/router/fetchArbitrage`. A key whose plan does not include it receives a non-fatal `PLAN_UPGRADE_REQUIRED` frame instead of a subscription: ```json { "type": "error", "code": "PLAN_UPGRADE_REQUIRED", "message": "the \"arbitrage\" feature requires the Builder plan or higher (current plan: \"free\")", "channel": "arbitrage" } ``` The Free plan does not include it. The socket stays open and no subscription is created. The same frame is also sent mid-stream. Entitlements are re-checked on the connection's per-minute metering tick against uncached key state, so a plan that stops entitling the feature loses the arbitrage subscription within about a minute: the service unsubscribes it, releases its subscription slot, and sends `PLAN_UPGRADE_REQUIRED`. If instead the API key itself has stopped verifying — revoked, deleted, or rotated — the channel is torn down the same way but the frame carries `UNAUTHORIZED`, because that caller needs to re-authenticate rather than upgrade. In both cases the socket is never closed and every other subscription on it continues. A verification attempt that FAILS to complete changes nothing: only a fresh, conclusive answer tears the channel down, so an unreachable key store never interrupts a paying customer. Other non-fatal operation codes are `BAD_MESSAGE`, `NOT_SUBSCRIBED`, `MARKET_NOT_FOUND`, `SUBSCRIPTION_LIMIT`, and post-auth `RATE_LIMITED`. Authentication, credit, platform, connection, and server-lifecycle failures can close the connection after their error frame or close reason. --- # API operations Every operation of the Predictefy Unified Data API, generated from packages/reads/openapi.yaml. Base URL: https://data.predictefy.com Auth: `Authorization: Bearer pk_live_…` on every route (anonymous requests answer 401 UNAUTHORIZED). - `GET /api/{exchange}/fetchMarkets` — **fetchMarkets** — List catalog markets for an exchange (or `router` for all served venues). - `GET /api/{exchange}/fetchMarketsPaginated` — **fetchMarketsPaginated** — Legacy alias of fetchMarkets (same handler, identical semantics). - `GET /api/{exchange}/fetchMarket` — **fetchMarket** — Get a single catalog market by marketId or slug (specific exchange; not router). - `GET /api/{exchange}/fetchCategories` — **fetchCategories** — List canonical categories with served-market counts. - `GET /api/{exchange}/fetchTags` — **fetchTags** — List active canonical tags with served-market counts. - `GET /api/{exchange}/fetchEvents` — **fetchEvents** — List catalog-derived events for an exchange (router = all served venues). - `GET /api/{exchange}/fetchEventsPaginated` — **fetchEventsPaginated** — Legacy alias of fetchEvents (same handler, identical semantics). - `GET /api/{exchange}/fetchEvent` — **fetchEvent** — One catalog-derived event by eventId or slug. - `GET /api/{exchange}/fetchEventMetadata` — **fetchEventMetadata** — Fetch venue-native metadata for one event (Kalshi only; not router). - `GET /api/{exchange}/fetchSeries` — **fetchSeries** — List catalog-derived series for an exchange (empty for venues without a series concept). - `GET /api/{exchange}/fetchOHLCV` — **fetchOHLCV** — Fetch historical OHLCV candles for one venue outcome. - `GET /v1/history/books/events` — **listHistoryBookEvents** — List raw lossless order-book tape events for one venue outcome. - `GET /api/{exchange}/has` — **has** — The venue's exchange-style capability map (specific exchange, not router). - `GET /api/{exchange}/fetchOrderBook` — **fetchOrderBook** — Live or archived order book for one outcome - `POST /api/{exchange}/fetchOrderBooks` — **fetchOrderBooks** — Batch live order books (body { args:[[outcomeId,...]] }) - `GET /api/{exchange}/fetchTrades` — **fetchTrades** — Recent trades (live tape) for one outcome (cached; capability-gated) - `POST /api/{exchange}/getExecutionPrice` — **getExecutionPrice** — VWAP execution price for a size over a client-supplied order book (0 if unfillable) - `POST /api/{exchange}/getExecutionPriceDetailed` — **getExecutionPriceDetailed** — Execution price + fill breakdown over a client-supplied order book - `GET /api/{exchange}/fetchMarketMatches` — **fetchMarketMatches** — Cross-venue matches for one market (lookup) or all matched pairs (browse). Router only. - `GET /api/{exchange}/fetchMatches` — **fetchMatches** — Deprecated legacy alias of fetchMarketMatches (identical request/response shape). Router only. - `GET /api/{exchange}/fetchMatchedMarkets` — **fetchMatchedMarkets** — Browse indicative cross-venue price-difference pairs from matched clusters. Router only. - `GET /api/{exchange}/fetchMatchedPrices` — **fetchMatchedPrices** — Deprecated legacy alias of fetchMatchedMarkets (identical request/response shape). - `GET /api/{exchange}/compareMarketPrices` — **compareMarketPrices** — Per-venue stored indicative prices plus live bestBid/bestAsk for one market's matches. Router only. - `GET /api/{exchange}/fetchHedges` — **fetchHedges** — Indicative hedge CANDIDATES for one market (honest subset). Router only. - `GET /api/{exchange}/fetchMatchedMarketClusters` — **fetchMatchedMarketClusters** — Full cross-venue matched-market clusters. Router only. - `GET /api/{exchange}/fetchMatchedEventClusters` — **fetchMatchedEventClusters** — Full cross-venue matched-event clusters. Router only. - `GET /api/{exchange}/fetchEventMatches` — **fetchEventMatches** — Cross-venue event matches from stored event clusters. Router only. - `GET /api/{exchange}/fetchRelatedMarkets` — **fetchRelatedMarkets** — Verified subset/superset related markets from stored relation edges. Router only. - `POST /api/{exchange}/filterMarkets` — **filterMarkets** — Stateless market filter over caller-supplied market objects. Router only. - `POST /api/{exchange}/filterEvents` — **filterEvents** — Stateless event filter over caller-supplied event objects. Router only. - `GET /v1/traders/{venue}/markets/{marketId}/trades` — **fetchTraderTrades** — List wallet-attributed trades for one venue market. - `GET /v1/traders/{venue}/markets/{marketId}/holders` — **fetchHolders** — List public outcome holders for one venue market. - `GET /v1/traders/leaderboard` — **fetchTopTraders** — Rank current stored wallet scores across Trader Intelligence venues. - `GET /v1/traders/{venue}/leaderboard` — **fetchLeaderboard** — Rank wallets on one venue by native profit/volume or stored score. - `GET /v1/traders/{venue}/wallets/{addr}` — **fetchWalletProfile** — Get one venue-scoped wallet profile and its score coverage. - `GET /v1/traders/{venue}/wallets/{addr}/trades` — **fetchWalletTrades** — List wallet-attributed trades for one venue-scoped wallet. - `GET /v1/traders/smart-money` — **fetchSmartMoney** — List the informational scored-trade feed. - `GET /v1/clusters` — **listClusters** — List cross-venue market clusters. - `GET /v1/clusters/{clusterId}` — **getCluster** — Get one cross-venue market cluster and its members. - `GET /v1/discrepancies` — **listDiscrepancies** — List honestly labeled indicative price discrepancies. - `GET /v1/discrepancies/history` — **listDiscrepancyHistory** — List persisted discrepancy snapshots. - `GET /v1/discrepancies/{clusterId}/qualification` — **qualifyDiscrepancy** — Fail-closed live execution qualification for one discrepancy cluster. - `GET /api/{exchange}/fetchArbitrage` — **fetchArbitrage** — Live executable-arbitrage assessment of discrepancy clusters. Router only. - `GET /v1/funding/{venue}/requirements` — **getFundingRequirements** — Get one venue's non-custodial funding requirements. - `POST /v1/funding/{venue}/steps` — **buildFundingSteps** — Read venue funding readiness and build complete caller-signable steps. - `GET /v1/funding/transfer-plan` — **getTransferPlan** — Plan a cross-venue collateral transfer as ordered caller-signed legs. - `GET /v1/bridge/quote` — **getBridgeQuote** — Get a provider-native bridge quote. - `POST /v1/bridge/session` — **createBridgeSession** — Create a caller-paid Glide bridge session. - `GET /v1/bridge/session/{sessionId}` — **getBridgeSession** — Get a Glide bridge session's current status. - `POST /v1/bridge/session/{sessionId}/payment` — **updateBridgeSessionPayment** — Report a caller-broadcast Glide payment transaction. - `GET /v1/bridge/status` — **getBridgeStatus** — Get LI.FI bridge execution status. - `GET /v1/portfolio` — **getPortfolio** — Value one public address across every hosted account venue in one call. - `GET /v1/accounts/{venue}/capabilities` — **getAccountCapabilities** — Get one venue's hosted account-resource capability registry. - `GET /v1/accounts/{venue}/{accountId}` — **getAccountSnapshot** — Get a bounded, independently available account snapshot. - `GET /v1/accounts/{venue}/{accountId}/balances` — **listAccountBalances** — List hosted public balances for an account. - `GET /v1/accounts/{venue}/{accountId}/positions` — **listAccountPositions** — List hosted public positions for an account. - `GET /v1/accounts/{venue}/{accountId}/open-orders` — **listAccountOpenOrders** — List hosted public open orders for an account. - `GET /v1/accounts/{venue}/{accountId}/fills` — **listAccountFills** — List hosted public fills for an account. - `GET /v1/usage` — **getUsage** — Read the authenticated account's balance, current-period usage, and credit ledger. - `POST /v1/billing/checkout` — **createCheckoutSession** — Create a Stripe Checkout session for an active credit pack. - `POST /v1/billing/subscribe` — **createSubscriptionSession** — Create a Stripe Checkout session for an active subscription plan. - `POST /v1/billing/portal` — **createBillingPortalSession** — Create a Stripe-hosted customer portal session. - `POST /v1/billing/webhook/stripe` — **stripeWebhook** — Stripe webhook receiver (auth-exempt; the signature IS the auth). - `POST /v1/mappings` — **fetchMappings** — Resolve venue-native market ids to canonical catalog and cluster identities. - `GET /v1/webhooks` — **listWebhooks** — List webhook endpoints owned by the calling account. - `POST /v1/webhooks` — **createWebhook** — Register an account-scoped webhook endpoint. - `DELETE /v1/webhooks/{id}` — **deleteWebhook** — Delete one account-scoped webhook endpoint. - `GET /v1/webhooks/{id}/deliveries` — **listWebhookDeliveries** — Poll recent delivery rows for one owned webhook endpoint. - `GET /v1/venues/metrics` — **fetchVenueMetrics** — Latest free-source size metrics grouped by venue. - `POST /v1/sql` — **executeSql** — Enterprise read-only SQL over the served Postgres catalog. - `GET /api/feeds` — **listFeeds** — List the available reference data feeds with honest capability maps. - `GET /api/feeds/{feed}/loadMarkets` — **feedLoadMarkets** — Static reference-market metadata for a feed (network-free). - `GET /api/feeds/{feed}/fetchTicker` — **feedFetchTicker** — One reference-price ticker (curated symbols only). - `GET /api/feeds/{feed}/fetchTickers` — **feedFetchTickers** — A keyed map of tickers — ONE upstream request regardless of count. - `GET /api/feeds/{feed}/fetchOHLCV` — **feedFetchOHLCV** — Reference candles (binance only; chainlink answers NOT_SUPPORTED). - `GET /api/feeds/{feed}/fetchOrderBook` — **feedFetchOrderBook** — Legacy endpoint name — NO current feed serves depth. - `GET /api/feeds/{feed}/fetchOracleRound` — **feedFetchOracleRound** — The latest Chainlink oracle round, decoded from the on-chain proxy. - `GET /api/feeds/{feed}/fetchOracleHistory` — **feedFetchOracleHistory** — Recent Chainlink rounds, walked back from latest (newest first). - `GET /api/feeds/{feed}/fetchHistoricalPrices` — **feedFetchHistoricalPrices** — Timestamp-ranged reference prices as tickers, from on-chain / candle sources. - `GET /v1/exec/venues` — **execListVenues** — List execution lanes armed on this deployment - `POST /v1/exec/{venue}/orders/build` — **execBuildOrder** — Build a bounded order or redemption intent - `POST /v1/exec/{venue}/orders/submit` — **execSubmitOrder** — Validate and relay a venue-specific execution - `POST /v1/exec/{venue}/orders/{executionId}/reserve` — **execReserveOrder** — Reserve stored notional before a client-direct venue submission - `POST /v1/exec/{venue}/orders/{executionId}/ack` — **execAckOrder** — Acknowledge a client-direct venue order submission - `GET /v1/exec/{venue}/orders/{executionId}` — **execGetOrder** — Read one owned execution and its fills - `POST /v1/exec/{venue}/orders/{executionId}/cancel` — **execBuildCancel** — Build a bounded cancel intent for an owned execution - `POST /v1/exec/{venue}/orders/{executionId}/modify` — **execBuildModify** — Build an unsigned modification for a resting execution - `POST /v1/exec/{venue}/orders/{executionId}/refresh` — **execRefreshOrder** — Refresh one execution from venue truth - `GET /v1/exec/{venue}/orders` — **execListOrders** — List owned executions for a venue - `GET /v1/exec/{venue}/trades` — **execListTrades** — List fills for owned executions - `GET /v1/exec/{venue}/positions` — **execListPositions** — Read owned positions or derive them from fills - `GET /v1/exec/{venue}/balance` — **execGetBalance** — Read an owned venue balance when the lane supports it