# 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:
{venues.map(([name, file, onDark]) => (
-
{file ? (
<>
{onDark && (
)}
>
) : (
Sm
)}
))}
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