Offers API

Fixed-odds bets between friends. One person posts a side and a price, others take it. Poll signs and submits the on-chain transactions for you.

How offers work

A Poll (pari-mutuel) pools everyone's money and splits it by outcome share. An offer is different: the price is fixed when the creator posts it.

  • Contract. A two-sided question Poll publishes for a real event, with a cutoff time and a resolution deadline. Poll creates and resolves contracts. You cannot create your own.
  • Offer. A creator picks a side of a contract, locks a total stake (creatorTotalMicros) and names the total they want matched on the other side (takerTargetMicros). The ratio between the two is the price. It never changes.
  • Take. Any other user takes part of the target. Each take gets the same proportional slice of the creator's stake. An offer can be filled by several people.
  • Position. One taker's matched amount on one offer. Repeat takes by the same person add to the same position.

Example: a creator stakes 8 USDC on "Italy or draw" against a 10 USDC target. A friend who takes 5 USDC on "Belgium" is matched with 4 USDC of the creator's stake. If Belgium wins, the friend gets their 5 back plus 4, minus the fee on the 4 they won.

Units and limits

Every amount is micro-USDC as a decimal string. One USDC is 1000000. Parse with BigInt. The API returns the current fee and launch limits on every list call, so read them from the response instead of hard-coding them.

live · from GET /contracts, refreshed every 30 seconds

SettingValueMeaning
offersEnabledtrueGlobal switch. When false, create and take return an error. Read endpoints keep working.
feeBps1000Fee in basis points, charged on the winner's profit only. Send it back as expectedFeeBps when you create.
minimumCreatorMicros$1Smallest creator stake.
minimumTakeMicros$1Smallest minimumTakeMicros a creator may set.
maximumWagerMicros$500Cap per creator stake and per taker position.
odds range1:10 to 10:1Creator stake to taker target. Outside this range the create is rejected. Above 5:1 either way the offer carries termsGuardrail.status: "warning".

Endpoints

https://api.poll.fun/api/bets-usdc/offers

Paths below are relative to that base. Reads work without a token. Signed-in reads add the viewer's role and position. Writes need a Personal Access Token with the bet:write scope, sent as Authorization: Bearer polld_.... See authentication.

MethodPathPurpose
GET/contractsOpen contracts you can offer on, plus fee and limits
GET/Browse open offers, paginated, filter by sport or text
GET/:offerAddressOne offer with the viewer's position if signed in
GET/:offerAddress/fillsFills on one offer, paginated
GET/mineOffers you created or took
GET/shareableOffers formatted for sharing into a chat
POST/Create an offer on a contract
POST/:offerAddress/takeTake part or all of an offer
POST/:offerAddress/closeWithdraw the unmatched part of your offer

List contracts

Start here. A contract tells you the two sides, when offers stop (cutoffAt), and the latest time Poll will resolve it (resolutionDeadline). Only Open contracts before their cutoff accept offers.

$ curl https://api.poll.fun/api/bets-usdc/offers/contracts

live · 1016 open contracts right now, first 5 shown

TitleCutoff
>0.5 goals in Georgia vs North. Ireland game?UEFA Nations League2026-09-25 16:00Z
North. Ireland to beat Georgia on Friday?UEFA Nations League2026-09-25 16:00Z
Armenia to beat Latvia on Friday?UEFA Nations League2026-09-25 16:00Z
>1.5 goals in Georgia vs North. Ireland game?UEFA Nations League2026-09-25 16:00Z
Georgia to beat North. Ireland on Friday?UEFA Nations League2026-09-25 16:00Z
json
{
  "contracts": [
    {
      "address": "6jjA8sHK81RNkd7rNCg1urpTqgBMeyeB8wSnmiQPy1Tk",
      "templateId": "6eda374b-e359-489f-9d4d-f33e326f3897",
      "eventId": "9c226f29-abb4-405c-9c3d-07e8fcc8af4a",
      "title": ">0.5 goals in Georgia vs North. Ireland game?",
      "description": "",
      "event": "Georgia vs North. Ireland",
      "sport": "FOOTBALL",
      "competition": "UEFA Nations League",
      "startsAt": "2026-09-25T16:00:00.000Z",
      "cutoffAt": "2026-09-25T16:00:00.000Z",
      "resolutionDeadline": "2026-09-26T18:30:00.000Z",
      "status": "Open",
      "outcomes": [
        {
          "side": "sideA",
          "label": "Over 0.5"
        },
        {
          "side": "sideB",
          "label": "Under 0.5"
        }
      ],
      "visuals": [
        {
          "src": "https://crests.football-data.org/ge.svg",
          "alt": "Georgia"
        },
        {
          "src": "https://crests.football-data.org/829.svg",
          "alt": "North. Ireland"
        }
      ]
    }
  ],
  "feeBps": 1000,
  "offersEnabled": true,
  "limits": {
    "minimumCreatorMicros": "1000000",
    "minimumTakeMicros": "1000000",
    "maximumWagerMicros": "500000000",
    "warningRatioNumerator": "5",
    "warningRatioDenominator": "1",
    "minimumOddsNumerator": "1",
    "minimumOddsDenominator": "10",
    "maximumOddsNumerator": "10",
    "maximumOddsDenominator": "1"
  }
}

Browse offers

$ curl "https://api.poll.fun/api/bets-usdc/offers?page=1&pageSize=20&sport=FOOTBALL"

Query parameters: page (from 1), pageSize (1 to 50, default 20), sport, query (text search), status (Open, Filled, Closed, Settled) and view (browse for open offers, results for finished ones).

live · one of 12 open offers, refreshed every 30 seconds

json
{
  "offers": [
    {
      "address": "FEMb8vEcLFLBv4yjxD4eGf1yG3mUyvd2zVB9gMkBcod9",
      "contract": {
        "address": "7siBgfhyb6KWgLRiuRt9yeV5gCmKpS7A8Mk4b2XGFVbw",
        "templateId": "a7409d46-0f83-42e2-aae1-867d96ab2051",
        "eventId": "b5b61af1-36b6-48d7-b60a-ce1ba74a1b8d",
        "title": "Belgium to beat Italy on Friday?",
        "description": "",
        "event": "Italy vs Belgium",
        "sport": "FOOTBALL",
        "competition": "UEFA Nations League",
        "startsAt": "2026-09-25T18:45:00.000Z",
        "cutoffAt": "2026-09-25T18:45:00.000Z",
        "resolutionDeadline": "2026-09-26T21:15:00.000Z",
        "status": "Open",
        "outcomes": [
          {
            "side": "sideA",
            "label": "Belgium"
          },
          {
            "side": "sideB",
            "label": "Italy or draw"
          }
        ],
        "visuals": [
          {
            "src": "https://crests.football-data.org/784.svg",
            "alt": "Italy"
          },
          {
            "src": "https://crests.football-data.org/805.svg",
            "alt": "Belgium"
          }
        ]
      },
      "creator": {
        "displayName": "pollintern",
        "profileImageUrl": "https://r2.poll.fun/profiles/2773/1766401147791.jpg"
      },
      "creatorOutcome": "sideB",
      "creatorSideLabel": "Italy or draw",
      "takerSideLabel": "Belgium",
      "creatorTotalMicros": "8000000",
      "creatorAllocatedMicros": "0",
      "takerTargetMicros": "10000000",
      "takerFilledMicros": "0",
      "remainingTakerMicros": "10000000",
      "minimumTakeMicros": "1000000",
      "feeBps": 1000,
      "termsGuardrail": {
        "status": "normal"
      },
      "status": "Open",
      "fillCount": 0,
      "positionCount": 0,
      "settledPositionCount": 0,
      "revision": "0",
      "createdAt": "2026-09-24T14:48:11.000Z",
      "closedAt": null,
      "viewer": {
        "role": "none",
        "canTake": true,
        "canClose": false,
        "returnMicros": null,
        "position": null
      }
    }
  ]
}

The fields that matter when you decide to take: remainingTakerMicros (what is still open), minimumTakeMicros, revision (send it back with your take) and viewer.canTake.

Create an offer

$ curl -X POST https://api.poll.fun/api/bets-usdc/offers \
  -H "Authorization: Bearer polld_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "contractAddress": "6jjA8sHK81RNkd7rNCg1urpTqgBMeyeB8wSnmiQPy1Tk",
    "creatorOutcome": "sideB",
    "creatorTotalMicros": "8000000",
    "takerTargetMicros": "10000000",
    "minimumTakeMicros": "1000000",
    "expectedFeeBps": 1000,
    "idempotencyKey": "my-app-offer-2026-09-24-001"
  }'
FieldMeaning
contractAddressFrom the contracts list. Must be Open and before cutoff.
creatorOutcomesideA or sideB, matching contract.outcomes[].side.
creatorTotalMicrosYour total stake. Locked in the offer pool until matched, closed, or settled.
takerTargetMicrosTotal you want matched on the other side. Stake to target is the price.
minimumTakeMicrosSmallest take you accept. A taker may go below it only to take the last remaining amount.
expectedFeeBpsEcho feeBps from the contracts response. A mismatch fails, so you never pay a fee you did not see.
idempotencyKey8 to 128 characters, unique per intended action. A replay with the same key and body returns the original result. Same key with a different body is rejected.

Response:

json
{
  "signature": "5Kx...",
  "status": "pending",
  "offerAddress": "FEMb8vEcLFLBv4yjxD4eGf1yG3mUyvd2zVB9gMkBcod9"
}

Poll builds the transaction, signs it with your Poll wallet, and submits it. Your USDC balance on Poll must cover creatorTotalMicros.

Take an offer

$ curl -X POST https://api.poll.fun/api/bets-usdc/offers/FEMb8vEcLFLBv4yjxD4eGf1yG3mUyvd2zVB9gMkBcod9/take \
  -H "Authorization: Bearer polld_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "takeAmountMicros": "5000000",
    "expectedRevision": "0",
    "idempotencyKey": "my-app-take-2026-09-24-001"
  }'
  • takeAmountMicros must be at least minimumTakeMicros, or exactly remainingTakerMicros to close out the offer.
  • expectedRevision is the offer's current revision. It changes on every fill and close. If someone filled before you, the take fails instead of matching at terms you did not see. Re-read the offer and retry.
  • You cannot take your own offer.
  • Your total position on one offer cannot exceed maximumWagerMicros.

Close an offer

$ curl -X POST https://api.poll.fun/api/bets-usdc/offers/FEMb8vEcLFLBv4yjxD4eGf1yG3mUyvd2zVB9gMkBcod9/close \
  -H "Authorization: Bearer polld_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "expectedRevision": "0", "idempotencyKey": "my-app-close-2026-09-24-001" }'

Close returns the unmatched part of your stake. Matched positions stay live and settle with the contract. The creator can close at any time. After the contract's cutoff anyone can close it, which is how stale offers get swept. Close is allowed even when your account is blocked from creating or taking, so you are never locked out of your own unmatched money.

Track a submission

Every write returns status: "pending" and a Solana signature. Confirmation takes a few seconds. Poll until the offer reflects your change:

  • After create: GET /:offerAddress returns 200 and viewer.role is creator.
  • After take: revision has moved past your expectedRevision and viewer.position is set.
  • After close: status is Closed and closedAt is set.

Poll every 2 to 3 seconds for up to a minute. If the signature never lands, resend with the same idempotencyKey; you get the original result back, not a duplicate offer.

Settlement and fees

Poll resolves the contract after the event, then settles each position. You do not call anything. Read the outcome from GET /:offerAddress/fills or GET /mine.

Contract resultPosition statusPayout
Creator's side winsSettledCreatorWinCreator gets both stakes minus fee on the taker's stake. Taker gets 0.
Taker's side winsSettledTakerWinTaker gets both stakes minus fee on the creator's matched stake. Creator gets 0.
Void (no result by the deadline, or Poll voids it)RefundedBoth sides get their stake back. No fee.

Fee is feeBps of the winner's profit, rounded down in micro-USDC. At 1000 bps, winning 4 USDC costs $0.40. Unmatched creator stake is refunded in full when the contract cuts off.

Errors

Every error is { "error": "message" }. Common ones:

StatusWhen
400Bad address, amount not an integer string, minimum take above target, odds outside the launch range, stake outside the limits, idempotency key reused with a different body.
401Missing or expired token, or a token without bet:write on a write.
403Your account, or the offer creator's, is blocked from wagering by compliance. Close still works.
404Unknown offer address.
409Something moved under you: the fee changed (expectedFeeBps), the offer changed (expectedRevision), or the amount is no longer available. Re-read and retry with fresh values.
503Offers switched off, or temporarily unavailable. Retry with backoff and the same idempotency key.

Want to sign your own transactions instead? The same flow is available on-chain through the SDK offers guide.