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
| Setting | Value | Meaning |
|---|---|---|
| offersEnabled | true | Global switch. When false, create and take return an error. Read endpoints keep working. |
| feeBps | 1000 | Fee in basis points, charged on the winner's profit only. Send it back as expectedFeeBps when you create. |
| minimumCreatorMicros | $1 | Smallest creator stake. |
| minimumTakeMicros | $1 | Smallest minimumTakeMicros a creator may set. |
| maximumWagerMicros | $500 | Cap per creator stake and per taker position. |
| odds range | 1:10 to 10:1 | Creator 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/offersPaths 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.
| Method | Path | Purpose |
|---|---|---|
| GET | /contracts | Open contracts you can offer on, plus fee and limits |
| GET | / | Browse open offers, paginated, filter by sport or text |
| GET | /:offerAddress | One offer with the viewer's position if signed in |
| GET | /:offerAddress/fills | Fills on one offer, paginated |
| GET | /mine | Offers you created or took |
| GET | /shareable | Offers formatted for sharing into a chat |
| POST | / | Create an offer on a contract |
| POST | /:offerAddress/take | Take part or all of an offer |
| POST | /:offerAddress/close | Withdraw 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/contractslive · 1016 open contracts right now, first 5 shown
| Title | Cutoff | |
|---|---|---|
| >0.5 goals in Georgia vs North. Ireland game?UEFA Nations League | Over 0.5 · Under 0.5 | 2026-09-25 16:00Z |
| North. Ireland to beat Georgia on Friday?UEFA Nations League | North. Ireland · Georgia or draw | 2026-09-25 16:00Z |
| Armenia to beat Latvia on Friday?UEFA Nations League | Armenia · Latvia or draw | 2026-09-25 16:00Z |
| >1.5 goals in Georgia vs North. Ireland game?UEFA Nations League | Over 1.5 · Under 1.5 | 2026-09-25 16:00Z |
| Georgia to beat North. Ireland on Friday?UEFA Nations League | Georgia · North. Ireland or draw | 2026-09-25 16:00Z |
{
"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
{
"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"
}'| Field | Meaning |
|---|---|
| contractAddress | From the contracts list. Must be Open and before cutoff. |
| creatorOutcome | sideA or sideB, matching contract.outcomes[].side. |
| creatorTotalMicros | Your total stake. Locked in the offer pool until matched, closed, or settled. |
| takerTargetMicros | Total you want matched on the other side. Stake to target is the price. |
| minimumTakeMicros | Smallest take you accept. A taker may go below it only to take the last remaining amount. |
| expectedFeeBps | Echo feeBps from the contracts response. A mismatch fails, so you never pay a fee you did not see. |
| idempotencyKey | 8 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:
{
"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"
}'takeAmountMicrosmust be at leastminimumTakeMicros, or exactlyremainingTakerMicrosto close out the offer.expectedRevisionis the offer's currentrevision. 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 /:offerAddressreturns 200 andviewer.roleiscreator. - After take:
revisionhas moved past yourexpectedRevisionandviewer.positionis set. - After close:
statusisClosedandclosedAtis 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 result | Position status | Payout |
|---|---|---|
| Creator's side wins | SettledCreatorWin | Creator gets both stakes minus fee on the taker's stake. Taker gets 0. |
| Taker's side wins | SettledTakerWin | Taker gets both stakes minus fee on the creator's matched stake. Creator gets 0. |
| Void (no result by the deadline, or Poll voids it) | Refunded | Both 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:
| Status | When |
|---|---|
| 400 | Bad 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. |
| 401 | Missing or expired token, or a token without bet:write on a write. |
| 403 | Your account, or the offer creator's, is blocked from wagering by compliance. Close still works. |
| 404 | Unknown offer address. |
| 409 | Something 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. |
| 503 | Offers 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.