---
title: "Discover active-open prediction markets for quoting"
method: GET
path: "/api/agent/pm/discover"
tags: ["prediction-markets"]
---

# Discover active-open prediction markets for quoting

`GET /api/agent/pm/discover`

## Request

- Base URL: `https://api.coinrithm.com`
- URL: `https://api.coinrithm.com/api/agent/pm/discover`
- Auth: HTTP bearer

Finds active-open, quote-ready prediction markets on Kalshi and
Polymarket by default. Returns source/slug + quoteable outcome
externalMarketIds, freshness, metrics, decisionSupport. Requires scope
`read`. Call pm/quote with a returned externalMarketId before pm/open.

Results are ordered openable-markets-first (then effectively-decided
`pinned` markets last). Each market carries `eligible` /
`eligibleBlockReasons` (and each outcome an `eligible`) so an agent can
skip multi-outcome / non-binary / non-openable books before wasting a
quote.

## Query parameters

- `limit` integer
- `offset` integer
- `q` string
- `source` 'all' | 'kalshi' | 'polymarket'
- `sort` 'best' | 'volume24h_desc' | 'priceChange24h_desc' | 'priceChange24h_asc' | 'endDate_desc' | 'trending'

## Response `200`

OK

- PmDiscoveryResponse
  - `data` PmDiscoveryMarket[]
    - `source` 'kalshi' | 'polymarket'
    - `slug` string
    - `title` string
    - `endDate` string, date-time, nullable
    - `relatedCoins` string[]: CoinRithm slugs of the coins this event is DIRECTLY about (admin- rejected links excluded; the same set the public event page shows), de-duplicated and sorted. Empty = no linked coin. Lets an agent group separate events on one coin and horizon (opt-in cross-event exposure cap); it is not a correlation model. Absent on older API versions.
    - `freshness` Freshness: Data-freshness descriptor. Futures + spot use ageSeconds; PM uses ageMinutes. `status` is a freshness label; `basis` (PM only) names which timestamp the age was measured against.
      - `asOf` string, date-time, nullable
      - `ageSeconds` number, nullable: futures / spot
      - `ageMinutes` number, nullable: PM
      - `status` 'fresh' | 'lagging' | 'stale' | 'unknown' | 'null', nullable: fresh | lagging (PM only) | stale | unknown. PM lagging>=45m & stale>=2h; spot/futures stale>120s.
      - `basis` 'latest_snapshot' | 'source_update' | 'processed' | 'event_update' | 'unknown', nullable: PM only — which timestamp the age was measured against.
    - `pinned` boolean: True when the market is effectively decided (leading outcome at/ above the pinned-probability threshold). Deranked in this listing — agents can skip these without recomputing.
    - `eligible` boolean, nullable: Can an agent open this binary book right now (shared mock-entry gate)? null when scalars are unavailable. Eligible markets are listed first.
    - `eligibleBlockReasons` string[]: Structured reasons the market is not openable (e.g. multi-outcome / non-binary / settled). Empty when eligible or unknown.
    - `quality` PmQuality: Persisted quality assessment from CoinRithm's truth engine — the aggregator's proven, versioned verdict for this event (one current state per event, updated when facts change). Markets with critical failures remain visible everywhere; `decisionEligible: false` means new paper opens are BLOCKED (pm/open returns 422 with these stored reasons) and alerts are suppressed. Omitted entirely when no assessment row exists yet (brand-new events) — never fabricated.
      - `decisionEligible` boolean: Fresh, structurally valid, metrics-supported, open/quoteable, and not source-degraded.
      - `warningReasons` string[]: Non-blocking flags (e.g. anomaly_flagged). Informational.
      - `blockReasons` string[]: Stable decision-blocking codes (stale_freshness, quote_dead, dead_zero, unpriced, not_open, freshness_unknown).
      - `policyVersion` string: Version of the quality policy that produced this verdict (e.g. pm-quality-2).
      - `assessedAt` string, date-time, nullable: Source-capture time backing the verdict (freshness SSOT), not a row-write timestamp.
    - `outcomes` PmDiscoveryOutcome[]
      - `externalMarketId` string
      - `name` string
      - `probability` number: 0..100
      - `tokenId` string, nullable
      - `eligible` boolean, nullable: Per-outcome openability (structural + 0<p<100 live yes fill). null when scalars are unavailable.
      - `rules` PmDiscoveryOutcomeRule: This outcome's own settlement rule as the provider states it, bound by provider market id (Kalshi ticker = `externalMarketId`), never by title words. `unknown` when the market is not in the provider data, has no rule, or is listed twice with different text (never resolved silently).
        - `status` 'exact' | 'unknown', required
        - `basis` 'provider_market_rules', required
        - `marketId` string, required
        - `primary` string: exact only; cut with … when truncated
        - `secondary` string, nullable: exact only. null when the outcome has no secondary rule or it is the shared one in `resolution.rules` (`secondaryShared` true).
        - `secondaryShared` boolean: exact only
        - `truncated` boolean: exact only; primary or secondary was cut at 700 characters
        - `reason` 'source_rules_unavailable' | 'market_not_found' | 'rule_missing' | 'conflicting_duplicates': unknown only. `source_rules_unavailable`: the venue's per-market rules could not be read at all; `market_not_found`: they were read but do not include this market.
    - `volume24h` number
    - `liquidity` number
    - `spread` number, nullable
    - `decisionSupport` DecisionSupport: Pre-computed market-quality grade for a prediction market (the same builder the web event/hub cards use): a quality score + tiered liquidity/volume/spread + risk flags. Lets an agent gauge tradability without running its own analysis. Returned by get_market_context's relatedMarkets and by pm/quote.
      - `qualityScore` number
      - `qualityTier` 'high' | 'medium' | 'low'
      - `spreadTier` 'tight' | 'moderate' | 'wide' | 'unknown'
      - `liquidityTier` 'high' | 'medium' | 'low' | 'unknown'
      - `volumeTier` 'high' | 'medium' | 'low' | 'unknown'
      - `flags` object
        - `thinMarket` boolean
        - `inactiveMarket` boolean
        - `highAmbiguity` boolean
        - `nearResolution` boolean
    - `resolution` PmDiscoveryResolution
      - `published` boolean, required: False when the venue publishes no real rule (a question-only body); `rules` is then null and the settlement terms are unknown.
      - `rules` string, nullable, required: The de-duplicated rule text joined into one string: the condition, the "otherwise" branch and the resolution source when the venue states them. Cut at 700 characters with "…".
      - `rulesTruncated` boolean, required
      - `settlementSource` string, nullable, required
      - `settlementSources` object[], nullable, required: Named outlets the venue says it settles from (Kalshi); null elsewhere.
        - `name` string, required
        - `url` string, nullable, required: http(s) only; anything else is null
      - `scope` 'per_outcome': Present for venues whose markets each state their own rule (Kalshi), even when those rules could not be read (every outcome is then `unknown`); absent for other venues, where `rules` is the event text as before. `per_outcome`: each outcome carries its own exact `rules`, bound by provider market id, and this event-level `rules` is ONLY a secondary rule every market states identically (compared after whitespace normalization), else null. The event-level bullets, which mix several markets' conditions, are not served. `published` is true when at least one outcome has an exact rule.
    - `quoteHint` PmDiscoveryQuoteHint
      - `endpoint` string
      - `source` 'kalshi' | 'polymarket'
      - `slug` string
      - `stakeMusdMin` number
      - `outcomeExternalMarketIdField` string
  - `pagination` object
    - `limit` integer
    - `offset` integer
    - `hasMore` boolean
  - `meta` object
    - `source` 'all' | 'kalshi' | 'polymarket'
    - `sources` string[]
    - `sourceHealth` object[]: Per-source ingestion freshness derived from aggregator-updated source rows.
      - `slug` string
      - `lastIngestAt` string, date-time, nullable
      - `ingestAgeSeconds` integer, nullable
      - `status` 'fresh' | 'stale' | 'never_ingested'
    - `sort` string
    - `q` string, nullable
    - `note` string
  - `observation` AgentObservation: Compact provenance block for an agent-facing market observation. It is also stored in the private ledger responseSummary when the request uses agentTrace/run headers, giving run exports a verifiable snapshot of what the agent observed without creating a full market archive.
    - `schema` string
    - `endpoint` string
    - `source` string
    - `observedAt` string, date-time
    - `sourceAsOf` string, date-time, nullable
    - `freshness` Freshness: Data-freshness descriptor. Futures + spot use ageSeconds; PM uses ageMinutes. `status` is a freshness label; `basis` (PM only) names which timestamp the age was measured against.
      - `asOf` string, date-time, nullable
      - `ageSeconds` number, nullable: futures / spot
      - `ageMinutes` number, nullable: PM
      - `status` 'fresh' | 'lagging' | 'stale' | 'unknown' | 'null', nullable: fresh | lagging (PM only) | stale | unknown. PM lagging>=45m & stale>=2h; spot/futures stale>120s.
      - `basis` 'latest_snapshot' | 'source_update' | 'processed' | 'event_update' | 'unknown', nullable: PM only — which timestamp the age was measured against.
    - `inputs` object
    - `dataset` object
    - `rowCount` integer, nullable
    - `hash` string: Short SHA-256 digest of the observed payload metadata.

## Other responses

- `400`: Invalid or missing parameters. On a route with a query allowlist, an undocumented query parameter returns `{"error": "unsupported_query_parameter", "message": "Unsupported query parameter: <key>. Supported: <keys>."}`.
- `401`: Missing/malformed or invalid/revoked API key
- `403`: API key missing the required scope
- `429`: Rate limit exceeded for this API key (120 req/min baseline, 20 trade-writes/min). Honor `Retry-After`, then resume; the `RateLimit-*` headers on every response let you pace proactively. (The platform also runs a per-IP limiter; its 429 body is `{"error": "rate_limited", "message": "..."}`.)

---

[API](https://skmtc.dev/coinrithm/apis/coinrithm-agent-trading-api.md) · [All operations](https://skmtc.dev/coinrithm/apis/coinrithm-agent-trading-api/llms.txt) · [OpenAPI document](https://skmtc.dev/coinrithm/apis/coinrithm-agent-trading-api/revisions/28fb59f285fe?raw)
