Skip to content

GuidesCookbook

One request returns at most 100 markets. Screening the catalog means paginating, and doing it with the cursor rather than with a page count.

router searches every venue at once; a venue id scopes it to one.

Terminal window
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:

ParameterValues
statusactive, inactive, closed, resolved, all
sortvolume, liquidity, newest
limit1–100
searchIntitle, description, both
searchModelexical (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.

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;
}

Every market comes back in the same shape whichever venue served it. The fields worth screening on:

FieldNote
sourceExchangeThe venue that served this row — not venue
volume / volume24hAll-time and rolling; volume24h is nullable
liquidityNullable
statusMatches the filter vocabulary above
category / tagsThe venue’s own vocabulary
canonicalCategory / canonicalTagsPredictefy’s cross-venue vocabulary, both nullable
outcomesThe sides you can hold; books key on these
outcomes[].priceChange*Eight windows: 1m, 5m, 15m, 1h, 6h, 24h, 7d, 30d
outcomes[].coverageTape coverage mechanism, interval, and 24h gap metrics
asOf / provenance / capabilitiesRequired on every record — see below

Every unified market carries four image fields. Image URLs are venue-hosted or Predictefy-hosted strings, passed through without a proxy or CDN:

  • image: the venue’s own art for the market. It follows this order: the market’s image, then a Kalshi market’s series art, then its parent event’s image. It is null when the venue publishes none of those.
  • eventImage: the parent event’s art alone. It is null when there is none.
  • imageResolved and imageSource: the picture to show and where it came from. imageSource is venue when imageResolved is the venue’s own art (the URL image held when the image stage last ran). The values league, team, sibling and category name substitutes Predictefy stored for a market whose venue publishes no art. league and team are the fixture’s competition image and a team logo from Predictefy’s sports catalog, attached through the market’s fixture key. These values appear once the sports catalog holds that art; until then, sports markets without venue art fall through to sibling or category. sibling is another venue’s newest venue-art market in the same cluster; category is a canonical-category placeholder (/img/categories/<slug>.svg). Both fields are null until Predictefy’s image stage has looked at the market, and stay null when it finds no picture. imageSource is never set without imageResolved. The source values series and event are reserved.
  • outcomes[].image: venue-stated art for one outcome, such as team logos on Polymarket, Polymarket US and Pred sports legs. It is present only when the venue states one.

For a thumbnail, select market.imageResolved ?? market.image ?? market.eventImage; if all three are null, use your own placeholder. To prefer the venue’s newest art, use market.image ?? market.imageResolved ?? market.eventImage; both thumbnail orders agree whenever the pair is current.

When enabled, the image stage resolves open, active markets once per matcher run. Closed, inactive and resolved markets keep the pair from their last pass while open and active. They have no pair if they left that scope before the stage first saw them.

The image stage writes imageResolved, while ingest writes image on a separate schedule, so imageResolved can be older than image. A substitute is chosen only while the venue’s image is empty. When venue art arrives later, ingest exposes it in image immediately. If the market remains in scope, the next image pass replaces the substitute.

A null imageSource means no resolved picture is available. The market may not be resolved yet, the stage may have found no picture, or the market may be outside its scope. It does not mean the venue has no art: venue art can still be present in image and eventImage. To show only the venue’s own art, skip the resolved pair and use market.image ?? market.eventImage. To accept only some sources, use imageResolved when imageSource is in your list; otherwise use market.image ?? market.eventImage.

Each market stores at most one resolved picture, chosen in the order venue, league, team, sibling, category. A market resolved to a source outside your list has no category placeholder to fall back to. If image and eventImage are also null, use your own placeholder. league, team and sibling are art derived from another source; each is only as right as the fixture key or cluster behind it.

Market-level art is available from Polymarket, Polymarket US, PredictStreet, PredictFun, Limitless, Myriad, XO and Rain, though individual markets may have none. Polymarket also publishes parent-event art. Gemini and Pred supply event-level art only, so image inherits it there. Opinion supplies market-level art on standalone markets and parent-event art on grouped markets. Kalshi art is series art: every market in a series shares one picture, served in image and eventImage. The series also exposes its art through the optional image field on fetchSeries. Predictefy collects a series’ art when it refreshes that series, so a new series’ markets can return null for a while.

Treat every URL as an untrusted string: validate the scheme and host before rendering. Predictefy’s own console does not render venue images; the category placeholders are the only Predictefy-hosted pictures.

Eight-window price changes and tape coverage

Section titled “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.

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.

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 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 do not go stale.

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.

fetchMarketsPaginated exists as an offset-paginated alternative when you genuinely need to jump to a position rather than walk forward.