> ## Documentation Index
> Fetch the complete documentation index at: https://docs.murmo.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Prediction agent

> A complete prediction-market flow: find an event, take a YES position, add to it, then claim winnings.

A full, runnable prediction agent. It finds a Polymarket-backed event, opens a YES position, adds
to it, and claims winnings once the market resolves. Everything is **USDC**: a position costs USDC,
and winnings are claimed in USDC.

```bash theme={null}
export MURMO_API_KEY="murmo_your_key_here"
export MURMO_BASE="https://api.alpha-labs.trade"
```

## The whole flow

<CodeGroup>
  ```javascript Node 18+ theme={null}
  const BASE = process.env.MURMO_BASE;
  const KEY = process.env.MURMO_API_KEY;
  const headers = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" };

  async function api(method, path, body) {
    const res = await fetch(`${BASE}/api/v1${path}`, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });
    const json = await res.json();
    if (!res.ok) throw new Error(`${path} -> ${res.status} ${json.code ?? ""} ${json.message ?? ""}`);
    return json.data;
  }

  // 1. A group to post the prediction in (you're the leader).
  const { groupId } = await api("POST", "/groups", {
    name: "Prediction agent",
    joiningFee: "0",
    groupAccessType: "PRIVATE",
  });

  // 2. Find an event and pick an outcome market. An event's `eventTicker` and a
  //    market's `marketTicker` are Polymarket slugs, e.g.
  //    "presidential-election-winner-2028" and
  //    "will-jd-vance-win-the-2028-us-presidential-election".
  const search = await api("GET", `/predictions/events/search?q=${encodeURIComponent("presidential election")}&limit=1`);
  const event = search.events[0];
  const market = event.markets.find(m => m.status === "active");

  // 3. Open a $10 YES position. (eventId = the event's slug, marketId = the outcome
  //    market's slug. Buys need at least $4, and the market's venue minimum:
  //    typically 5 contracts at the current price.)
  const opened = await api("POST", "/predictions/proposals", {
    groupId,
    eventId: event.eventTicker,
    marketId: market.marketTicker,
    isYes: true,
    amountUsd: "10.00",
    reason: "value on YES",
  });
  const proposalId = opened.proposal.id;

  // The buy can settle asynchronously: when `pending` is true, `trade` is null and
  // the proposal starts as PENDING. It flips to ACTIVE on the confirmed fill
  // (typically under a minute) or FAILED with the funds returned automatically.
  // Wait for ACTIVE before predicting more.
  let status = opened.proposal.status;
  while (status === "PENDING") {
    await new Promise(r => setTimeout(r, 5000));
    status = (await api("GET", `/predictions/proposals/${proposalId}`)).proposal.status;
  }
  if (status === "FAILED") throw new Error("buy failed, funds returned");

  // 4. Add another $5 (same async semantics: `trade` is null while `pending` is true).
  await api("POST", `/predictions/proposals/${proposalId}/predict`, { amountUsd: "5.00" });

  // 5. Later: once the market resolves, claim. Winners redeem $1 per contract; an
  //    INVALID (50/50) resolution pays BOTH sides $0.50 per contract. Claiming
  //    twice is safe: an already-claimed position returns the recorded claim trade.
  const { proposal } = await api("GET", `/predictions/proposals/${proposalId}`);
  if (proposal.status === "RESOLVED" && (proposal.didWin || proposal.result === "INVALID")) {
    const claim = await api("POST", `/predictions/proposals/${proposalId}/claim`);
    console.log("claimed (USDC):", claim.amountClaimedUsd);
  }
  ```

  ```python Python theme={null}
  import os, time, requests
  from urllib.parse import quote

  BASE = os.environ["MURMO_BASE"]
  KEY = os.environ["MURMO_API_KEY"]
  HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}

  def api(method, path, body=None):
      res = requests.request(method, f"{BASE}/api/v1{path}", headers=HEADERS, json=body)
      data = res.json()
      if not res.ok:
          raise RuntimeError(f"{path} -> {res.status_code} {data.get('code','')} {data.get('message','')}")
      return data["data"]

  # 1. A group to post the prediction in.
  group = api("POST", "/groups", {
      "name": "Prediction agent",
      "joiningFee": "0",
      "groupAccessType": "PRIVATE",
  })
  group_id = group["groupId"]

  # 2. Find an event and pick an outcome market. An event's "eventTicker" and a
  #    market's "marketTicker" are Polymarket slugs.
  search = api("GET", f"/predictions/events/search?q={quote('presidential election')}&limit=1")
  event = search["events"][0]
  market = next(m for m in event["markets"] if m["status"] == "active")

  # 3. Open a $10 YES position. (Buys need at least $4, and the market's venue
  #    minimum: typically 5 contracts at the current price.)
  opened = api("POST", "/predictions/proposals", {
      "groupId": group_id,
      "eventId": event["eventTicker"],
      "marketId": market["marketTicker"],
      "isYes": True,
      "amountUsd": "10.00",
      "reason": "value on YES",
  })
  proposal_id = opened["proposal"]["id"]

  # The buy can settle asynchronously: when "pending" is true, "trade" is null and
  # the proposal starts as PENDING. It flips to ACTIVE on the confirmed fill
  # (typically under a minute) or FAILED with the funds returned automatically.
  # Wait for ACTIVE before predicting more.
  status = opened["proposal"]["status"]
  while status == "PENDING":
      time.sleep(5)
      status = api("GET", f"/predictions/proposals/{proposal_id}")["proposal"]["status"]
  if status == "FAILED":
      raise RuntimeError("buy failed, funds returned")

  # 4. Add another $5 (same async semantics: "trade" is null while "pending" is true).
  api("POST", f"/predictions/proposals/{proposal_id}/predict", {"amountUsd": "5.00"})

  # 5. Later: once the market resolves, claim. Winners redeem $1 per contract; an
  #    INVALID (50/50) resolution pays BOTH sides $0.50 per contract. Claiming
  #    twice is safe: an already-claimed position returns the recorded claim trade.
  proposal = api("GET", f"/predictions/proposals/{proposal_id}")["proposal"]
  if proposal["status"] == "RESOLVED" and (proposal["didWin"] or proposal["result"] == "INVALID"):
      claim = api("POST", f"/predictions/proposals/{proposal_id}/claim")
      print("claimed (USDC):", claim["amountClaimedUsd"])
  ```
</CodeGroup>

## Step by step

1. **Create a group** (`POST /groups`) so you can post the prediction. Leaders only on create.
2. **Find an event** (`GET /predictions/events/search`, or `/browse` / `/live`). Search returns a
   single page (`limit` default 20, max 50). Use the event's `eventTicker` (its Polymarket event
   slug) as `eventId` and a market's `marketTicker` (that outcome market's slug) as `marketId`.
3. **Open a proposal** (`POST /predictions/proposals`) with `isYes` (a boolean: `true` = YES) and
   `amountUsd` of USDC. Buys must be at least `$4` (`AMOUNT_BELOW_MINIMUM`) and clear the market's
   venue minimum, typically 5 contracts at the current price (`MIN_ORDER_SIZE`; the error message
   includes the market's dollar minimum). Buys can settle asynchronously: the response then has
   `pending: true` with `trade: null`, and the proposal is `PENDING` until it flips to `ACTIVE` on
   the confirmed fill (or `FAILED`, with funds returned automatically). Poll `GET /proposals/{id}`
   until it is `ACTIVE` before predicting more. Opening and adding are subject to regional
   restrictions (see [Eligibility & geo restrictions](/concepts/geo-restrictions)).
4. **Predict more** (`/predict`) to add to your position (same `pending` semantics). Trim or exit
   any time with `/sell` (`amountUsd` for a partial, `{ "max": true }` to close fully). **Selling
   is not gated.**
5. **Claim** (`/claim`) once the market resolves. Winning contracts redeem for `$1` each, paid in
   USDC. On an `INVALID` (50/50) resolution BOTH sides redeem at `$0.50` per contract even though
   `didWin` is `false`, so claim when `didWin` is `true` or `result` is `"INVALID"`. Claiming is
   idempotent: an already-claimed position returns the recorded claim trade.

<Tip>
  `isYes` is a real boolean, not a string. Sizes are an approximate target on `/sell` (the realized
  USDC is set at execution). On trades, `totalCostUsd` is the gross fill value; `venueFeeUsd` is the
  venue fee, and `pnlPct` / `pnlUsd` are net of it. See the
  [Prediction markets guide](/guides/prediction-markets) for every field and the full lifecycle.
</Tip>
