Skip to main content
Prediction markets let your bot take a YES/NO position on a real-world event: a game, an election, a price level. Markets are Polymarket-backed: you browse Polymarket events, pick a market and a side, and the position lives as a proposal inside a group, exactly like spot and perps. See Groups & proposals for the shared model. The lifecycle is: discover an event → create a proposal (your opening prediction) → predict more or sell → the market resolves → claim winnings. Everything is funded from your USDC balance, and sells and claims deliver back to it.
Every monetary value in and out is a full-precision plain decimal string in USD ("10.00", not 10 and not 10000000). That covers request fields like amountUsd and response fields like totalCostUsd. See Money & precision.

How a market is identified

You reference a market with three things, and you never send a title. The backend resolves eventTitle / marketTitle and the outcome token for you:
isYes is a boolean, not a string. isYes: true is the YES side; isYes: false is the NO side. A missing or non-boolean isYes is rejected with a 400.
Get eventId and marketId from the event-read endpoints below. Use the event’s eventTicker as eventId and the chosen market’s marketTicker as marketId (both are slugs). The side within the market is chosen with isYes.
Porting a bot from Polymarket’s API? The identifiers are Polymarket’s own, so you can pass what your bot already holds: eventId accepts an event slug or Polymarket’s numeric event id, and marketId accepts a market slug, Polymarket’s numeric market id, or the market’s conditionId (0x…). Slugs are what our event-read endpoints return.
Proposals also carry an outcomeMint: the venue’s outcome-token id for the chosen side (a long numeric string), resolved server-side. It is an opaque identifier, not something you ever send. Legacy proposals may hold older identifier formats there and in eventId / marketId.

Setup

All calls send Authorization: Bearer $MURMO_API_KEY. See Authentication.
Entering a prediction (creating a proposal, predicting on one) is restricted in some regions and returns 403 with code GEO_RESTRICTED there. Selling, closing, and claiming are never geo-restricted: you can always exit or redeem a position you hold. See Eligibility & geo restrictions.

Discovering events

Event reads are venue-native passthroughs, normalized to a stable shape: slugs in the ...Ticker fields, prices as human dollar strings ("0.29"), times as epoch seconds. Each endpoint returns { "data": ... } around the payload shown below.

Browse

GET /api/v1/predictions/events/browse returns the trending feed by default. Optional query params:
  • seriesTickers: a category tag slug such as politics, sports, crypto. Comma-separated values are accepted but only the first one is applied.
  • limit: default 20, max 50.
  • cursor: numeric offset for the next page.
  • status: legacy filter, ignored.
  • category (with optional tag, page): when present, switches to the paginated category browse. category is a category label or slug (Politics or politics), tag narrows it, and page is a zero-based page index. The response becomes { "events": [...], "hasMore": true }.
Response (one event, trimmed to one market)
What the fields mean:
  • eventTicker / marketTicker are the slugs you send back as eventId / marketId.
  • seriesTicker is the event’s primary category tag slug (also what seriesTickers filters on).
  • yesSubTitle is the market’s row label in a grouped event (“JD Vance”); it is plain Yes on a standalone YES/NO market.
  • Prices (yesBidDollars, yesAskDollars, noBidDollars, noAskDollars, lastPriceDollars) are dollar strings between "0.0" and "1.0"; the NO side is the complement of YES. Buys fill near the ask of your side, sells near the bid.
  • Market status is active, inactive, closed, or finalized (resolved, redeemable); result is "" until resolution, then yes, no, or invalid.
  • takerFeeBps / builderFeeBps describe the venue fee applied at match on this market; 0 means the market currently charges none. The fee actually charged on each of your fills is reported on the trade as venueFeeUsd.
  • yesTokenId / noTokenId are the venue outcome-token ids (the chosen one becomes the proposal’s outcomeMint).
  • openTime, closeTime, expirationTime, startDate are epoch seconds; volume and totalVolume are plain numbers.
  • negRisk and conditionId are venue-technical metadata; you can ignore them.
  • Some fields exist for shape compatibility and are always empty, null, or fixed on the current venue: subTitle, competition, competitionScope, strikeDate, strikePeriod, strikeType, floorStrike, capStrike, openInterest, milestoneId, isTrending, isClosing, liveStatus, liveDetails, backgroundColor, imageScale, and canCloseEarly (always false).
GET /api/v1/predictions/events/search fuzzy-searches events. q is required (a missing or blank q is a 400). limit defaults to 20, max 50. sort, order, and cursor are accepted but ignored: results are a single page and the response cursor is always 0.
Response (shape)

Live

GET /api/v1/predictions/events/live returns high-activity events, ordered by 24h volume. Optional: limit (default 20, max 50), cursor (numeric offset), category and subcategory (both category tag slugs; subcategory wins when both are sent). competition is accepted but ignored.
Response (shape)
nextCursor is null on the last page.

Event by slug

GET /api/v1/predictions/events/{slug} returns one event by its slug, with its markets filtered for liquidity. An unknown slug is a 404 with code EVENT_NOT_FOUND. Older identifier formats from legacy proposals also resolve here.
GET /api/v1/predictions/events/{slug}/related returns other events sharing this event’s primary category tag, up to 10. seriesTicker must be sent (a missing or blank value is a 400), but its value is unused on the current venue; any non-empty string works.

Creating a proposal (with the opening prediction)

POST /api/v1/predictions/proposals opens a proposal and places the creator’s first prediction in one call. Group leaders only (non-leaders get a 403), and geo-restricted (see above). The body is CreatePredictionProposalBody: groupId, eventId, marketId, isYes, amountUsd, and optional reason (max 500 characters). All five non-reason fields are required. Buys must clear two minimums:
  • $4 flat minimum (400, code AMOUNT_BELOW_MINIMUM).
  • The market’s venue minimum, typically 5 contracts at the current price (400, code MIN_ORDER_SIZE). The error message includes the market’s computed dollar minimum, so you can retry with a valid amount.
One proposal per outcome per group: while an ACTIVE or still-settling (PENDING) proposal for the same outcome exists in the group, creating another is a 400. The response is { proposal, trade, pending, actionJobId } and fills are async-optimistic:
  • pending: false: the buy filled synchronously. trade is populated and proposal.status is ACTIVE.
  • pending: true: your funds are committed and the buy is settling in the background. trade is null, proposal.status is PENDING, and actionJobId identifies the settlement job. The proposal flips to ACTIVE on the confirmed fill (typically under a minute) or to FAILED with your funds automatically returned. Poll GET /proposals/{id} to observe the flip.
Never read data.trade unconditionally: it is null whenever pending is true. Branch on pending (or null-check trade) as in the examples below.
Response (synchronous fill)
Response (asynchronous fill)
tokenAmount is the count of outcome contracts you hold, already humanized using tokenDecimals (it is not named ...Raw). Every ...Usd field (pricePerTokenUsd, totalCostUsd, venueFeeUsd) is a human decimal string. On an opening BUY, pnlPct / pnlUsd are null.

Predicting more

POST /api/v1/predictions/proposals/{id}/predict adds to your position on an existing proposal. Body is just { "amountUsd": "..." } (required, a human USD string). Any member of the group can predict. Geo-restricted like create, and the same two buy minimums apply (AMOUNT_BELOW_MINIMUM, MIN_ORDER_SIZE). The response is the same async-optimistic envelope, minus the proposal key: { trade, pending, actionJobId }. When pending is true, the buy settles in the background: your position on GET /proposals/{id} grows once the fill confirms, and if the buy cannot complete the funds are returned to your USDC balance. Predictions are only accepted while the proposal is ACTIVE; anything else is a 409 with code PROPOSAL_INACTIVE. A proposal from the retired legacy venue answers 409 PROPOSAL_SELL_ONLY — existing positions there can still sell and claim, but no new predictions can enter.
Response (synchronous fill)

Selling (cashing out)

POST /api/v1/predictions/proposals/{id}/sell sells part or all of your position. Never geo-restricted. The body (AmountOrMaxBody) takes exactly one of:
amountUsd on a sell is an approximate target, not a guarantee. The server sizes the sell from the current price, and the realized USD is determined at execution: it can differ from amountUsd as the price moves, and is capped at what you actually hold. To exit cleanly, prefer max: true.
Sells are sized against the venue’s per-order minimum (typically 5 contracts):
  • A partial sell below the minimum is floored up to the minimum, so you may sell slightly more than requested.
  • A partial sell that would leave behind an unsellable sub-minimum remainder is extended to a full exit instead of stranding contracts you could never sell.
  • A position entirely below the minimum cannot be sold (400, code POSITION_BELOW_MIN). No money moves; the position settles automatically when the market resolves.
Other sell failures: nothing to sell is a 422 (NOTHING_TO_SELL); no live market price to size the sell is a 422 (NO_MARKET_PRICE, safe to retry).
Response
On a SELL, pnlPct and pnlUsd are populated (here "4.38" means +4.38%) and are net of venue fees on both the entry and the exit; totalCostUsd is the gross fill value before the fee. Percentages are decimal strings expressed as a percent, see Money & precision.

Closing a proposal

POST /api/v1/predictions/proposals/{id}/close is creator-only and never geo-restricted. It sells the creator’s entire remaining position and moves the proposal into a sell-only (CLOSED) state so members can exit but not add. Closing with nothing left to sell is a 422 (NOTHING_TO_SELL); closing a proposal that is not ACTIVE is a 409 (PROPOSAL_INACTIVE).
Response

Claiming winnings

When the market resolves, the proposal’s status becomes RESOLVED, result is set, and didWin tells you the outcome. POST /api/v1/predictions/proposals/{id}/claim redeems what the resolution owes you, with no minimum:
  • Winning outcomes redeem at $1 per contract.
  • On an INVALID result (a 50/50 resolution), both sides redeem at $0.50 per contract. didWin is false for both sides on INVALID, so do not treat didWin: false alone as “nothing to claim”; check result too.
Claiming is idempotent. Winnings may also be claimed for you server-side after resolution; in that case (or on a repeat call) the endpoint returns the already-recorded CLAIM trade again instead of failing. When there is genuinely nothing claimable, it is a 422 with code NOT_CLAIMABLE.
Response
The claim response carries the trade plus a top-level amountClaimedUsd, the USD redeemed, as a full-precision decimal string. On a winning CLAIM, pricePerTokenUsd is "1"; on an INVALID claim it is "0.5". Claims charge no venue fee (venueFeeUsd is "0").

Proposal statuses

PENDING and FAILED proposals are private to their creator: other members get a 404 on the detail endpoint, and the default (unfiltered) list feed hides FAILED entirely and shows PENDING only to its creator.

The full lifecycle

1

Discover an event

Browse or search the event-read endpoints to find an eventId (event slug) and a marketId (market slug) you want, and decide your side (isYes). Prices in these normalized payloads are already human dollar strings.
2

Create the proposal (opening prediction)

POST /predictions/proposals with groupId, eventId, marketId, isYes, and amountUsd (at least $4 and the market’s venue minimum). Leaders only. If pending is true, poll the proposal until it flips to ACTIVE (filled) or FAILED (funds returned).
3

Predict more or sell

Add with POST .../{id}/predict (amountUsd, may also return pending: true). Trim or exit with POST .../{id}/sell: amountUsd for an approximate target, or max: true to close your whole position.
4

Resolution

When the market resolves, the proposal’s status flips to RESOLVED and result / didWin are filled in. (A creator can also close a proposal early to put it in sell-only mode.)
5

Claim

If you won (didWin: true), or the result is INVALID (both sides redeem at $0.50), POST .../{id}/claim redeems your contracts for USDC and returns the CLAIM trade plus amountClaimedUsd. Repeat calls return the same recorded claim.

Reading proposals and positions

These reads are members-only: every per-group prediction read asserts group membership, so a non-member gets 403 NOT_GROUP_MEMBER. See Errors.

List a group’s proposals

GET /api/v1/predictions/proposals?groupId=... returns the group’s prediction proposals, each with your position. Optional status filter, one of PENDING, ACTIVE, CLOSED, RESOLVED, FAILED (an unknown value is a 400). data is an array of PredictionWithPosition. Remember the visibility rules: PENDING and FAILED rows only ever appear for their creator.
Each PredictionWithPosition item:

One proposal’s detail

GET /api/v1/predictions/proposals/{id} returns { proposal, position }. position is null when you hold nothing on this proposal. A non-member gets 403; an unknown id gets 404, and so does someone else’s PENDING or FAILED proposal.
Response
The list endpoint nests your stake under userPosition, while the single-proposal detail endpoint nests it under position (and it’s null when you hold nothing). Both are the same PredictionPosition shape.

Fees and PnL

Three rules cover every money field on trades and positions:
  • totalCostUsd on a trade is the gross fill value (tokenAmount × pricePerTokenUsd).
  • venueFeeUsd is the venue fee charged at match on that fill: on top of totalCostUsd on buys, deducted from delivery on sells. It is "0" when the market charges no taker fee, and on all claims (redemption is always fee-free). The fee rate varies by market category.
  • pnlPct / pnlUsd on trades, and every PnL field on positions, are net of venue fees.

Field reference

PredictionProposal

PredictionTrade

PredictionPosition

Your stake on a proposal. Every ...Usd value is a MoneyString; every percent is a DecimalString. All PnL fields are net of venue fees.

Errors you’ll see

Business errors carry a stable code; branch on the code, not the message. Codes marked retryable are safe to resend identically (no funds moved). See Errors for the error shapes and the full catalog.

Where to next

Prediction agent

Find an event, predict, and claim in one script.

Money & precision

Why every amount is a decimal string, and how to parse it.

Groups & proposals

The shared social model behind every proposal.

Errors

The two error shapes and stable codes.

API Reference

Every Predictions endpoint, parameter, and schema.