> For the complete documentation index, see [llms.txt](https://docs.opinion.trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opinion.trade/developer-guide/opinion-open-api/resolution.md).

# Resolution

Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.

The three GET endpoints are public (no API key). Responses are cached server-side for \~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.

Markets in the resolution flow are in one of three phases:

* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`
* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`
* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)

Fields default to `""` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.

#### Error codes

Returned with HTTP 200 in the envelope:

* `10003` — invalid parameters (`errmsg` says which)
* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used
* `11020` — no resolution record for this market
* `11021` — no dispute filed for this question
* `11022` — signer is not the on-chain disputor
* `11023` — rationale frozen (already finalized)
* `11024` — rationale too long (max 2000 bytes)

## Get resolution market list

> Markets in the resolution flow, viewed from the resolution side.\
> \
> Fixed ordering: \`proposed\` rows by \`disputeDeadline\` ascending, then \`disputed\` rows by \`arbitrationDeadline\` ascending, then \`finalized\` rows by \`finalizedAt\` descending.\
> \
> \### Self-indexing with events\
> \
> If you index on-chain events directly, subscribe on the \`disputeResolver\` addresses returned by the API. Event signatures:\
> \
> \`\`\`text\
> ResolutionReceived(bytes32 indexed questionId, uint256\[2] payouts, uint256 disputeDeadline)\
> RewardDeposited(bytes32 indexed questionId, address indexed stakeToken, uint256 amount)\
> DisputeFiled(bytes32 indexed questionId, address indexed disputor, uint256 stakeAmount, uint8 reason, uint256 reVoteDeadline)\
> DisputeResolved(bytes32 indexed questionId, bool overturned, uint256\[2] finalPayouts)\
> DisputeTimedOut(bytes32 indexed questionId)\
> ResolutionFinalized(bytes32 indexed questionId, uint256\[2] payouts)\
> StakeClaimed(bytes32 indexed questionId, address indexed recipient, uint256 amount)\
> \`\`\`\
> \
> A market enters the feed as \`proposed\` when \`RewardDeposited\` lands (that transaction fixes the dispute window); \`ResolutionReceived\` is a slightly earlier step carrying the proposed result, and anchors \`proposedAt\`.<br>

````json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/markets":{"get":{"tags":["Resolution"],"summary":"Get resolution market list","description":"Markets in the resolution flow, viewed from the resolution side.\n\nFixed ordering: `proposed` rows by `disputeDeadline` ascending, then `disputed` rows by `arbitrationDeadline` ascending, then `finalized` rows by `finalizedAt` descending.\n\n### Self-indexing with events\n\nIf you index on-chain events directly, subscribe on the `disputeResolver` addresses returned by the API. Event signatures:\n\n```text\nResolutionReceived(bytes32 indexed questionId, uint256[2] payouts, uint256 disputeDeadline)\nRewardDeposited(bytes32 indexed questionId, address indexed stakeToken, uint256 amount)\nDisputeFiled(bytes32 indexed questionId, address indexed disputor, uint256 stakeAmount, uint8 reason, uint256 reVoteDeadline)\nDisputeResolved(bytes32 indexed questionId, bool overturned, uint256[2] finalPayouts)\nDisputeTimedOut(bytes32 indexed questionId)\nResolutionFinalized(bytes32 indexed questionId, uint256[2] payouts)\nStakeClaimed(bytes32 indexed questionId, address indexed recipient, uint256 amount)\n```\n\nA market enters the feed as `proposed` when `RewardDeposited` lands (that transaction fixes the dispute window); `ResolutionReceived` is a slightly earlier step carrying the proposed result, and anchors `proposedAt`.\n","operationId":"getResolutionMarketList","parameters":[{"name":"phase","in":"query","description":"Filter by phase","schema":{"type":"string","enum":["proposed","disputed","finalized"]}},{"name":"labelId","in":"query","description":"Filter by category label id — same ids as `GET /label`","schema":{"type":"integer","format":"int64"}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ResolutionMarketsResponse"}}}]}}}}}}}},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ResolutionMarketsResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of markets for the filter"},"list":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionMarketRow"}}}},"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}}}}}
````

## Get resolution market detail

> Full resolution view of one market: all list-row fields plus the rules text, the tx-anchored timeline, and the \`dispute\` object carrying everything needed to send dispute/claim transactions.\
> \
> Returns \`errno 11020\` when the market has no resolution record (nothing proposed yet, categorical parent id — children resolve independently — or a market predating the feed).\
> \
> \### Disputing on-chain\
> \
> Filing and claiming are direct contract calls with the parameters from the \`dispute\` object.\
> \
> \*\*Always call \`isDisputable(questionId)\` on-chain immediately before sending a dispute transaction.\*\* API responses are cached \~10 s; \`isDisputable\` is the on-chain truth for deadline boundaries and the one-dispute-per-resolution slot. Skipping it risks a reverted transaction.\
> \
> \`\`\`javascript\
> const DR\_ABI = \[\
> &#x20; "function dispute(bytes32 questionId, uint8 reason) external",\
> &#x20; "function claim(bytes32 questionId) external",\
> &#x20; "function isDisputable(bytes32 questionId) external view returns (bool)",\
> ];\
> const ERC20\_ABI = \["function approve(address spender, uint256 amount) returns (bool)"];\
> \
> const dr  = new ethers.Contract(dispute.disputeResolver, DR\_ABI, wallet);\
> const opn = new ethers.Contract(dispute.stakeToken, ERC20\_ABI, wallet);\
> \
> if (!(await dr.isDisputable(questionId))) throw new Error("not disputable");\
> \
> const stake = ethers.parseUnits(dispute.requiredStake, dispute.stakeTokenDecimals);\
> await (await opn.approve(dispute.disputeResolver, stake)).wait();\
> await (await dr.dispute(questionId, 0 /\* 0 = WrongResult, 1 = TooEarly \*/)).wait();\
> \
> // after finalization, when claimStatus = "claimable":\
> await (await dr.claim(questionId)).wait();\
> \`\`\`<br>

````json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/market/{marketId}":{"get":{"tags":["Resolution"],"summary":"Get resolution market detail","description":"Full resolution view of one market: all list-row fields plus the rules text, the tx-anchored timeline, and the `dispute` object carrying everything needed to send dispute/claim transactions.\n\nReturns `errno 11020` when the market has no resolution record (nothing proposed yet, categorical parent id — children resolve independently — or a market predating the feed).\n\n### Disputing on-chain\n\nFiling and claiming are direct contract calls with the parameters from the `dispute` object.\n\n**Always call `isDisputable(questionId)` on-chain immediately before sending a dispute transaction.** API responses are cached ~10 s; `isDisputable` is the on-chain truth for deadline boundaries and the one-dispute-per-resolution slot. Skipping it risks a reverted transaction.\n\n```javascript\nconst DR_ABI = [\n  \"function dispute(bytes32 questionId, uint8 reason) external\",\n  \"function claim(bytes32 questionId) external\",\n  \"function isDisputable(bytes32 questionId) external view returns (bool)\",\n];\nconst ERC20_ABI = [\"function approve(address spender, uint256 amount) returns (bool)\"];\n\nconst dr  = new ethers.Contract(dispute.disputeResolver, DR_ABI, wallet);\nconst opn = new ethers.Contract(dispute.stakeToken, ERC20_ABI, wallet);\n\nif (!(await dr.isDisputable(questionId))) throw new Error(\"not disputable\");\n\nconst stake = ethers.parseUnits(dispute.requiredStake, dispute.stakeTokenDecimals);\nawait (await opn.approve(dispute.disputeResolver, stake)).wait();\nawait (await dr.dispute(questionId, 0 /* 0 = WrongResult, 1 = TooEarly */)).wait();\n\n// after finalization, when claimStatus = \"claimable\":\nawait (await dr.claim(questionId)).wait();\n```\n","operationId":"getResolutionMarketDetail","parameters":[{"name":"marketId","in":"path","required":true,"description":"Numeric market id, or a 0x-prefixed question id","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ResolutionMarketDetail"}}}]}}}}}}}},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ResolutionMarketDetail":{"allOf":[{"$ref":"#/components/schemas/ResolutionMarketRow"},{"type":"object","properties":{"rules":{"type":"string","description":"This market's resolution rules — the criteria the outcome is judged against"},"parentRules":{"type":"string","nullable":true,"description":"Parent event's rules for categorical child markets (read both when evaluating a proposal); null for binary markets"},"dispute":{"$ref":"#/components/schemas/ResolutionDisputeParams"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionTimelineStage"},"description":"Fixed three stages (`proposed` / `disputed` / `finalized`), each anchored to its on-chain transaction; stages not reached are null. Records indexed before tx tracking was introduced may show null `at` / `txHash` for stages that did occur"}}}]},"ResolutionMarketRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id — same id space as `GET /market/{marketId}` (binary markets and categorical child markets)"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id — the key for all DisputeResolver contract calls"},"conditionId":{"type":"string","description":"Conditional-tokens condition id"},"parentMarketId":{"type":"integer","format":"int64","nullable":true,"description":"Parent market id for categorical child markets; null for binary"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label (team names for sports markets)"},"noLabel":{"type":"string","description":"No outcome label"},"phase":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Resolution phase (see the Resolution section)"},"proposedPayouts":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Proposed outcome as the on-chain payout vector `[yes, no]`: `[\"1\",\"0\"]` Yes wins, `[\"0\",\"1\"]` No wins, `[\"1\",\"1\"]` tie. Null on price-oracle records"},"proposedTokenId":{"type":"string","description":"Proposed winning token id (= `yesTokenId` or `noTokenId`); the literal string `\"draw\"` for a tie — check before using it as a token id"},"resultTokenId":{"type":"string","description":"Final outcome after finalization, same encoding as `proposedTokenId`; `\"\"` until finalized"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set only on finalized markets that were disputed; null otherwise (including during review)"},"requiredStake":{"type":"string","nullable":true,"description":"OPN stake required to dispute, fixed for the whole window (display units)"},"stakeTokenSymbol":{"type":"string","description":"Stake token symbol"},"lastTradePrice":{"type":"string","nullable":true,"description":"Latest trade price of the proposed winning token; null for tie proposals"},"proposedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of the proposal transaction (Unix seconds)"},"disputeDeadline":{"type":"integer","format":"int64","nullable":true,"description":"End of the dispute window (Unix seconds); null only on price-oracle records"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Set once disputed. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `phase` / `disputeOutcome`, not this deadline alone"},"finalizedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time of finalization (Unix seconds)"}}},"ResolutionDisputeParams":{"type":"object","nullable":true,"description":"Everything needed to send dispute/claim transactions, plus the state of the current dispute. Null for price-oracle records (nothing to dispute).","properties":{"chainId":{"type":"string","description":"Chain id"},"disputeResolver":{"type":"string","description":"Contract to call `dispute` / `claim` / `isDisputable` on for this market — always take the address from here"},"requiredStake":{"type":"string","nullable":true,"description":"Stake required to dispute (display units)"},"stakeToken":{"type":"string","description":"ERC-20 address of the stake token — `approve` it to `disputeResolver` before disputing"},"stakeTokenSymbol":{"type":"string"},"stakeTokenDecimals":{"type":"integer","description":"Stake token decimals — use with `requiredStake` to build the raw approve amount"},"disputor":{"type":"string","nullable":true,"description":"Wallet that filed the dispute; null if none"},"reason":{"type":"integer","nullable":true,"description":"Dispute reason as filed: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason (`WrongResult` / `TooEarly`); `\"\"` if never disputed"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Set once finalized"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake — `not_applicable` (no dispute, dispute rejected, or not yet finalized) / `claimable` (dispute won, unclaimed) / `claimed`"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","nullable":true,"description":"Transaction hash of finalization"},"claimTxHash":{"type":"string","nullable":true,"description":"Transaction hash of the stake claim"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline. After it passes, an undecided dispute becomes eligible for a `timeout` ruling (applied by the operator, not automatic) — rely on `disputeOutcome`, not the deadline"}}},"ResolutionTimelineStage":{"type":"object","description":"One tx-anchored stage of the resolution timeline","properties":{"stage":{"type":"string","enum":["proposed","disputed","finalized"],"description":"Stage name, always in this order"},"at":{"type":"integer","format":"int64","nullable":true,"description":"Unix time of the stage's transaction; null if not reached"},"txHash":{"type":"string","nullable":true,"description":"Transaction hash anchoring the stage; null if not reached"}}}}}}
````

## Get dispute list

> Disputes across all markets, with reason, rationale, outcome and claim state. Ordered by \`disputedAt\` descending (newest first).\
> \
> \`proposedPayouts\` / \`proposedTokenId\` are the original proposal that was disputed; \`resultTokenId\` / \`finalizeTxHash\` stay empty while review is in progress.<br>

```json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/disputes":{"get":{"tags":["Resolution"],"summary":"Get dispute list","description":"Disputes across all markets, with reason, rationale, outcome and claim state. Ordered by `disputedAt` descending (newest first).\n\n`proposedPayouts` / `proposedTokenId` are the original proposal that was disputed; `resultTokenId` / `finalizeTxHash` stay empty while review is in progress.\n","operationId":"getResolutionDisputes","parameters":[{"name":"disputor","in":"query","description":"Filter by disputor wallet address","schema":{"type":"string"}},{"name":"outcome","in":"query","description":"Filter by outcome (`pending` = review in progress)","schema":{"type":"string","enum":["pending","upheld","overturned","timeout"]}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","description":"Number of items per page (max 20)","schema":{"type":"integer","default":10,"maximum":20}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ResolutionDisputesResponse"}}}]}}}}}}}},"components":{"schemas":{"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"ResolutionDisputesResponse":{"type":"object","properties":{"total":{"type":"integer","format":"int64","description":"Total number of disputes for the filter"},"list":{"type":"array","items":{"$ref":"#/components/schemas/ResolutionDisputeRow"}}}},"ResolutionDisputeRow":{"type":"object","properties":{"marketId":{"type":"integer","format":"int64","description":"Market id"},"questionId":{"type":"string","description":"0x-prefixed on-chain question id"},"marketTitle":{"type":"string","description":"Market title"},"slug":{"type":"string","description":"Market slug"},"yesTokenId":{"type":"string","description":"Yes outcome token ID"},"noTokenId":{"type":"string","description":"No outcome token ID"},"yesLabel":{"type":"string","description":"Yes outcome label"},"noLabel":{"type":"string","description":"No outcome label"},"disputor":{"type":"string","description":"Wallet that filed this dispute"},"reason":{"type":"integer","description":"Dispute reason: `0` WrongResult / `1` TooEarly"},"reasonEnum":{"type":"string","description":"Human-readable reason"},"rationale":{"type":"string","nullable":true,"description":"Public statement submitted by the disputor; null if none"},"stake":{"type":"string","description":"Amount actually staked for this dispute (display units)"},"stakeTokenSymbol":{"type":"string"},"proposedPayouts":{"type":"array","items":{"type":"string"},"description":"The original proposal that was disputed, as the payout vector `[yes, no]`"},"proposedTokenId":{"type":"string","description":"Proposed winning token id of the disputed proposal (`\"draw\"` for a tie)"},"resultTokenId":{"type":"string","description":"Final outcome; `\"\"` while review is in progress"},"disputeOutcome":{"type":"string","enum":["upheld","overturned","timeout"],"nullable":true,"description":"Null while review is in progress"},"claimStatus":{"type":"string","enum":["not_applicable","claimable","claimed"],"description":"Claim state of the disputor's stake"},"claimableAmount":{"type":"string","nullable":true,"description":"Stake + reward claimable by the winning disputor (display units)"},"disputeTxHash":{"type":"string","description":"Transaction hash of the dispute"},"finalizeTxHash":{"type":"string","description":"Transaction hash of finalization; `\"\"` while review is in progress"},"claimTxHash":{"type":"string","description":"Transaction hash of the stake claim; `\"\"` if not claimed"},"disputedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the dispute was filed (Unix seconds)"},"claimedAt":{"type":"integer","format":"int64","nullable":true,"description":"Time the stake was claimed (Unix seconds)"},"arbitrationDeadline":{"type":"integer","format":"int64","nullable":true,"description":"Review deadline of this dispute"}}}}}}
```

## Submit dispute rationale

> After your dispute transaction confirms, attach a public statement. It is shown with the dispute in the endpoints above. Authenticated by an EIP-712 signature from the disputing wallet — no API key or account needed; the server verifies the signer against the on-chain disputor, so you can submit as soon as the dispute transaction confirms. Verification is ECDSA recovery, so the disputing wallet must be an EOA — disputes filed from a contract wallet (e.g. a Safe) cannot attach a rationale.\
> \
> Accepted while the dispute is under review; resubmit to overwrite; frozen after finalization. Signatures expire after 5 minutes and are single-use.\
> \
> Sign the following typed data — \`walletAddress\` lowercase, \`questionId\` exactly as in the URL, \`rationale\` exactly the string sent in the body. The server strips leading/trailing whitespace before verifying, so sign and send the rationale without surrounding whitespace. Maximum 2000 bytes (UTF-8):\
> \
> \`\`\`javascript\
> const domain = { name: "Opinion OpenAPI", version: "1", chainId: 56 };\
> const types = {\
> &#x20; OpinionDisputeRationale: \[\
> &#x20;   { name: "walletAddress", type: "address" },\
> &#x20;   { name: "questionId",    type: "string"  },\
> &#x20;   { name: "rationale",     type: "string"  },\
> &#x20;   { name: "timestamp",     type: "uint256" },\
> &#x20; ],\
> };\
> const timestamp = Math.floor(Date.now() / 1000).toString();\
> const message = {\
> &#x20; walletAddress: wallet.address.toLowerCase(),\
> &#x20; questionId,\
> &#x20; rationale: "Final score was 3-2 per MLB.com; the proposal resolves the wrong side.",\
> &#x20; timestamp,\
> };\
> const signature = await wallet.signTypedData(domain, types, message);\
> \`\`\`\
> \
> Send \`signature\` in \`OPINION\_SIGNATURE\`, the wallet address in \`OPINION\_ADDRESS\` and the signed timestamp in \`OPINION\_TIMESTAMP\`.<br>

````json
{"openapi":"3.0.3","info":{"title":"OPINION Prediction Market OpenAPI","version":"1.0.0"},"tags":[{"name":"Resolution","description":"Resolution lifecycle endpoints: observe every market's resolution status, read the parameters needed to file a dispute on-chain, and submit a dispute rationale. How disputes work (stakes, rewards, review) is described in [Dispute](https://docs.opinion.trade/trade-on-opinion.trade/dispute) — this section covers the API.\n\nThe three GET endpoints are public (no API key). Responses are cached server-side for ~10 seconds. Business errors return HTTP 200 with a non-zero `errno` — handle errors by `errno`, not HTTP status.\n\nMarkets in the resolution flow are in one of three phases:\n\n* `proposed` — an outcome has been proposed; the dispute window is open until `disputeDeadline`\n* `disputed` — a dispute has been filed; review in progress until `arbitrationDeadline`\n* `finalized` — terminal: `resultTokenId` is set; if disputed, `disputeOutcome` = `upheld` / `overturned` / `timeout` (dispute rejected / accepted / review timed out)\n\nFields default to `\"\"` (strings) or `null` (numbers/times) until their stage occurs. Markets resolved automatically by a price oracle (e.g. recurring crypto price markets) have no dispute window: they appear as `finalized`-only records with `disputeDeadline: null` and `dispute: null`.\n\n### Error codes\n\nReturned with HTTP 200 in the envelope:\n\n* `10003` — invalid parameters (`errmsg` says which)\n* `11011` / `11012` / `11013` — rationale signature invalid / expired / already used\n* `11020` — no resolution record for this market\n* `11021` — no dispute filed for this question\n* `11022` — signer is not the on-chain disputor\n* `11023` — rationale frozen (already finalized)\n* `11024` — rationale too long (max 2000 bytes)\n"}],"servers":[{"url":"https://openapi.opinion.trade/openapi","description":"Production server"}],"security":[],"paths":{"/resolution/dispute/{questionId}/rationale":{"post":{"tags":["Resolution"],"summary":"Submit dispute rationale","description":"After your dispute transaction confirms, attach a public statement. It is shown with the dispute in the endpoints above. Authenticated by an EIP-712 signature from the disputing wallet — no API key or account needed; the server verifies the signer against the on-chain disputor, so you can submit as soon as the dispute transaction confirms. Verification is ECDSA recovery, so the disputing wallet must be an EOA — disputes filed from a contract wallet (e.g. a Safe) cannot attach a rationale.\n\nAccepted while the dispute is under review; resubmit to overwrite; frozen after finalization. Signatures expire after 5 minutes and are single-use.\n\nSign the following typed data — `walletAddress` lowercase, `questionId` exactly as in the URL, `rationale` exactly the string sent in the body. The server strips leading/trailing whitespace before verifying, so sign and send the rationale without surrounding whitespace. Maximum 2000 bytes (UTF-8):\n\n```javascript\nconst domain = { name: \"Opinion OpenAPI\", version: \"1\", chainId: 56 };\nconst types = {\n  OpinionDisputeRationale: [\n    { name: \"walletAddress\", type: \"address\" },\n    { name: \"questionId\",    type: \"string\"  },\n    { name: \"rationale\",     type: \"string\"  },\n    { name: \"timestamp\",     type: \"uint256\" },\n  ],\n};\nconst timestamp = Math.floor(Date.now() / 1000).toString();\nconst message = {\n  walletAddress: wallet.address.toLowerCase(),\n  questionId,\n  rationale: \"Final score was 3-2 per MLB.com; the proposal resolves the wrong side.\",\n  timestamp,\n};\nconst signature = await wallet.signTypedData(domain, types, message);\n```\n\nSend `signature` in `OPINION_SIGNATURE`, the wallet address in `OPINION_ADDRESS` and the signed timestamp in `OPINION_TIMESTAMP`.\n","operationId":"submitDisputeRationale","parameters":[{"name":"questionId","in":"path","required":true,"description":"0x-prefixed question id, exactly as returned by the API","schema":{"type":"string"}},{"$ref":"#/components/parameters/RationaleAddressHeader"},{"$ref":"#/components/parameters/RationaleSignatureHeader"},{"$ref":"#/components/parameters/RationaleTimestampHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeRationaleRequest"}}}},"responses":{"200":{"description":"Successful response (check `errno` in the body)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ResolutionAPIBaseResponse"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/DisputeRationaleResult"}}}]}}}}}}}},"components":{"parameters":{"RationaleAddressHeader":{"name":"OPINION_ADDRESS","in":"header","required":true,"description":"Disputor wallet address (must equal the on-chain disputor)","schema":{"type":"string"}},"RationaleSignatureHeader":{"name":"OPINION_SIGNATURE","in":"header","required":true,"description":"EIP-712 signature over the `OpinionDisputeRationale` typed data (see the endpoint description)","schema":{"type":"string"}},"RationaleTimestampHeader":{"name":"OPINION_TIMESTAMP","in":"header","required":true,"description":"Unix timestamp in seconds; must equal the signed `timestamp` field. Valid for 5 minutes.","schema":{"type":"string"}}},"schemas":{"DisputeRationaleRequest":{"type":"object","required":["rationale"],"properties":{"rationale":{"type":"string","description":"Public statement explaining the dispute — must equal the signed `rationale`; at most 2000 bytes (UTF-8). Surrounding whitespace is stripped server-side"}}},"ResolutionAPIBaseResponse":{"type":"object","description":"Envelope used by the Resolution endpoints. The outcome is reported in `errno` / `errmsg` with HTTP 200; see the Resolution section for the error-code list.","properties":{"errno":{"type":"integer","description":"Business result code (0 for success)"},"errmsg":{"type":"string","description":"Error message (empty on success)"},"result":{"type":"object","description":"Response data"}}},"DisputeRationaleResult":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}
````


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.opinion.trade/developer-guide/opinion-open-api/resolution.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
