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

# Settlement

> Who credits the user at resolution, how to detect that a market resolved, the five settlement fields, and cancelled markets.

Settlement is the one place in this integration where a mistake pays a user twice, so start with who is allowed to originate a credit.

<Info>
  Before this page: the two-firm identity model,
  [Firms, participants and accounts](/identity); how cash reaches a
  participant account, [Move cash](/funding#move-cash); the trigger stream,
  [Position change](/streams#position-change); and the scales,
  [Money on the wire](/instruments#money-on-the-wire).
</Info>

<Info>
  **API reference:** <a href="https://docs.polymarket.us/institutional/report/overview" target="_blank" rel="noreferrer">Report</a>. Opens on the public documentation site in a new tab.
  Where it disagrees with this page, this page is authoritative for the partner surface.
</Info>

## Who credits the user

<Note>
  **If you are an IB, this differs.**
  An ISV is told not to pay its user from its own ledger. **You may not accept or hold customer
  funds at all**, so there is no ledger you could legitimately pay from — see
  [Funds handling](/funds). You do own the customer-facing explanation of a settlement, including
  the cases where we cannot tell you why a market settled as it did (see
  [Support and escalation for IBs](/ib/support)), and cancelled events settle inconsistently,
  which you will be asked about. The gross-versus-net `RESOLUTION` question is also open for
  Customer Statements and reconciliation — see [Reporting pack](/regulatory#reporting-pack).
</Note>

**We credit the participant's clearing account at resolution.** You must not pay your user independently from your own ledger: if both sides credit, every settled position pays out twice. Two partners shipped settlement code switched off in production rather than risk exactly this.

<Warning>
  **This sentence needs explicit sign-off before publication.**

  The division of responsibility above is inferable from the funding flow — settlement credits land in
  the participant account and stay there — but it has never been stated to partners as a
  responsibility. Confirm it with your integration lead in writing before you build or disable a
  payout path on it.
</Warning>

### What lands where

At resolution, winning contracts settle at $1.00 and losing contracts at $0.00. The credit goes to the **participant's clearing account** — the same account the position was held in.

Three things stay in that account and become **buying power** there:

* settlement credits,
* realized profit,
* collateral released when a position closes or settles.

**Nothing needs sweeping after a fill or a settlement.** There is no post-settlement transfer you must run to make the money usable, and `OdfSweep` — which used to exist for this shape of problem — was removed on 2026-08-06 along with `CreateFundedOrder` and `PreviewFundedOrder`. If your design has a sweep step in it, delete the step.

### What you must not do

**Do not credit your user from your own balance when a market resolves.** The user's money is already in their participant account. A second credit on your side is a real double payout, not a display bug, and you will discover it in reconciliation after the user has withdrawn.

Two partners shipped their settlement crediting code switched off in production because this was never written down. Switching it off was the right call with the information available.

The safe design is one ledger of record per dollar:

| Concern                                                     | Owner                                             |
| ----------------------------------------------------------- | ------------------------------------------------- |
| Crediting the participant account at resolution             | **Us**                                            |
| Showing the user their balance and settled positions        | **You**, mirrored from our numbers                |
| Moving cash out of the participant account into your wallet | **You**, explicitly, with a `WITHDRAWAL` transfer |
| Tracking your own vendor-fee accruals                       | **You** — the platform never knows your fee basis |

### Mirroring settlement into your own wallet

If your product needs the money to appear in a wallet you operate, move it deliberately with `CashMovementService` rather than crediting it twice.

<Steps>
  <Step title="Detect the resolution">
    `CreatePositionChangeSubscription` tells you something happened on a market your user holds; the
    market data stream or `GET /v1/orderbook/{symbol}` carries the values. Gate it properly —
    `settlement_preliminary: false` does not mean resolved. See
    [Detect that a market resolved](#detect-that-a-market-resolved).
  </Step>

  <Step title="Read the account, do not compute it">
    Take the balance from the newest balance-ledger entry's `afterBalance`.
    `GetAccountBalance` returns `InvalidArgument: invalid account` for a participant clearing
    account, so a ledger read is the working path — see [Balance ledger](/streams#balance-ledger).
  </Step>

  <Step title="Move it with a WITHDRAWAL transfer">
    `CashMovementService` reasons are `DEPOSIT`, `WITHDRAWAL` and `VENDOR_FEES`, and **direction is
    fixed by the reason**. You name only the participant account; we resolve your funding account
    from your configured relationship. Transfers to your firm as a destination you choose, between
    participants, or to an external destination are structurally impossible.
  </Step>

  <Step title="Send no participant header">
    `CashMovementService` calls are **firm-scoped**. Do not send `x-participant-id` — that is the
    opposite of the account-scoped reads.
  </Step>

  <Step title="Round to 2 decimal places and keep the residue">
    Transfers accept at most **2 decimal places** while balances carry more — `172.395` and
    `1377.57275` have both been observed. There is no sanctioned handling for the sub-cent residue,
    so your balance sheet cannot reach zero on those accounts. Track the residue rather than forcing
    it.
  </Step>

  <Step title="Never reuse a terminally-rejected idempotency key">
    A terminally-rejected `idempotency_key` **replays its stored rejection**. After a terminal reject,
    use a new key; an identical retry will not make a fresh attempt.
  </Step>
</Steps>

<Warning>
  **Transfer rate limit: do not build against a number yet.**

  Three different values are live in our own material, and they disagree on both the number and the
  scope — one of them puts the ceiling **shared across all partners** rather than per firm. We are not
  publishing any of them here, because a settlement sweep sized against the wrong one fails in
  production: this already blocked a partner's entire order flow as their stated number-one issue.
  Get the value and its scope from your integration lead before you size a sweep, and build
  backpressure regardless. [Rate limits](/limits-and-errors#rate-limits) tracks this.
</Warning>

### Fees do not come out of settlement

There is **no `settlement_fee`**. It was cited to a partner in error and retracted. The only fees are the trading fees in the published Fee Schedule, and the platform never knows your own fee basis — you track your accruals yourself and reconcile against the daily Vendor Fees report. That report has no delivery mechanism today ("Delivery method TBD"), so agree the channel with your integration lead. See [Settlement fields](#settlement-fields).

## Detect that a market resolved

With the crediting question settled, the next problem is knowing that a market resolved at all. Resolution detection is two calls, not one: a stream tells you something happened, and a read tells you what the value is.

### The pipeline

<Steps>
  <Step title="CreatePositionChangeSubscription tells you a market you hold moved">
    It is resumable and position-driven, so you hear about markets your users actually hold and a
    disconnect does not drop the event.
  </Step>

  <Step title="The market data stream or GET /v1/orderbook/{symbol} carries the settlement values">
    `settlement_px`, `settlement_preliminary`, `settlement_price_calculation_method`,
    `settlement_price_calculation_text` and `settlement_set_time` live here. `/v1/orderbook/*` needs
    no `x-participant-id`.
  </Step>

  <Step title="You gate, then you act">
    The gate is the hard part. Read the warning below before you write it.
  </Step>
</Steps>

**`CreateInstrumentStateChangeSubscription` does not carry the settlement result fields.** It returns the updated `Instrument` and nothing more, so it is not a settlement source and not a gap-free replay source for settlement values. We told a partner the opposite and then corrected it. See [Instrument state changes](/streams#instrument-state-changes).

### `settlementPreliminary: false` is not a resolved flag

<Warning>
  **`settlement_preliminary: false` does not mean "resolved to an outcome."** A non-binary
  `settlementPx` on a non-resolved instrument is a **mark**, and marks carry
  `settlement_preliminary: false` too.

  **There is no positive `resolved` indicator anywhere on the API.** Any partner gating a payout on
  `settlementPreliminary` alone **will pay out on a mark**. One partner called this the most
  important open item in their entire integration.
</Warning>

Two more facts that break the obvious gates:

* **Do not gate on `settlement_price_calculation_text`.** It is not guaranteed present on a settled instrument. A partner who gated on it had a market stuck suspended.
* **`settlementSetTime` can precede `expirationDate` by \~13 hours on a mark**, so a populated `settlementSetTime` is not evidence of resolution on its own.

### The safest gate available today

This is a workaround, not a supported resolved flag. It is built from the facts we can stand behind: winning contracts settle at $1.00 and losing at $0.00, de-scaled with `priceScale`.

Require **all** of these before you treat an instrument as resolved:

1. `settlementPreliminary` is `false`,
2. `settlementPx ÷ priceScale` is exactly `1.00` or `0.00`,
3. `settlementSetTime` is at or after `expirationDate`,
4. the instrument has reached a terminal lifecycle state.

Anything else — a fractional price, a missing field, a `settlementSetTime` before expiry — **holds for manual review**. Do not auto-pay and do not auto-void.

<Warning>
  **\[VERIFY] — condition 4 is the weakest link in this gate, and it decides payouts.** `InstrumentState`
  has **nine** values, and **which of them are terminal for resolution is not published anywhere.**
  The only wire form we have evidence for is prefixed — `INSTRUMENT_STATE_EXPIRED`, seen in preprod —
  so the code below spells the set `INSTRUMENT_STATE_CLOSED` and `INSTRUMENT_STATE_TERMINATED`. Both
  the **membership** of that set and those two **spellings** are unconfirmed.

  **Confirm the terminal set with your integration lead before you let this gate release money.**
  Until then, treat an instrument state you do not recognise as a review case, never as terminal —
  and note that a partner coding to the lossy five-state subset on the legacy market-data page will
  miss states outright.
</Warning>

<Warning>
  **This gate is honest about its limits: it will also hold some genuine resolutions.** Binary markets
  have been observed settling to non-binary values (`-1.5 → 0.28`, `+1.5 → 0.77`), unexplained, and
  cancelled events have settled inconsistently within a single event. Those cases fail condition 2
  and land in your review queue, which is the correct outcome while the semantics are unresolved —
  see [Cancelled and voided markets](#cancelled-and-voided-markets).
</Warning>

`settlementPriceScale` is reserved and reads 0 — de-scale with `priceScale`. `int64` fields serialize as strings in JSON. proto3 does not put a scalar at its default value on the wire, so an absent field and a zero are indistinguishable: treat a missing `settlementPx` as "no value", never as `0.00`.

<Snippet file="scales-warning.mdx" />

### Detection code

<CodeGroup>
  ```python Python theme={null}
  import os
  from datetime import datetime
  from decimal import Decimal

  import requests

  REST_BASE = "https://api.preprod.polymarketexchange.com"
  SYMBOL    = "aec-cfb-clmsn-lsu-2026-09-05"  # from the position change that triggered this check
  ACCESS_TOKEN = os.environ["PMX_ACCESS_TOKEN"]  # mint per /connect#authentication; expires_in is 180s

  # Gate on event series and symbol prefix. automaticResolution controls nothing.
  # VERIFY: `aec-atp` is the only symbol prefix we have evidence for. The authoritative
  # list of auto-resolving series and prefixes must come from us — never guess one.
  AUTO_RESOLVING_PREFIXES = ("aec-atp",)

  # VERIFY: which InstrumentState values are terminal for resolution is not published,
  # and these two spellings are unconfirmed. See the warning above.
  TERMINAL_STATES = {"INSTRUMENT_STATE_CLOSED", "INSTRUMENT_STATE_TERMINATED"}


  def instant(value):
      """Parses an RFC 3339 timestamp. Returns None when the field is absent or empty.

      Never compare these as strings: an absent field is an empty string, which
      compares LOW against every real timestamp and silently passes the gate.
      """
      if not value:
          return None
      return datetime.fromisoformat(str(value).replace("Z", "+00:00"))


  def settlement_view(symbol: str) -> dict:
      # /v1/orderbook/* needs no x-participant-id.
      response = requests.get(
          f"{REST_BASE}/v1/orderbook/{symbol}",
          headers={"authorization": f"Bearer {ACCESS_TOKEN}"},
          timeout=10,  # the edge 504s past 30 seconds; fail fast instead
      )
      response.raise_for_status()  # never swallow: 401 means a stale token, 504 means >30s at the edge
      return response.json()


  def resolution_state(view: dict) -> str:
      """Returns "resolved", "mark" or "review". Never returns "resolved" on a fractional price."""
      px_raw = view.get("settlementPx")          # int64 arrives as a string
      price_scale = view.get("priceScale")       # 100 and 1000 are both live in production
      if px_raw is None or not price_scale:
          return "mark"                          # absent and zero are indistinguishable in proto3

      px = Decimal(str(px_raw)) / Decimal(str(price_scale))   # de-scale with priceScale, not settlementPriceScale

      if view.get("settlementPreliminary") is not False:
          return "mark"                          # false alone is NOT proof of resolution, but true rules it out

      set_time = instant(view.get("settlementSetTime"))
      expiration = instant(view.get("expirationDate"))
      if set_time is None or expiration is None:
          return "review"                        # a missing field FAILS the condition; it never passes it
      if set_time < expiration:
          return "review"                        # settlementSetTime can precede expirationDate by ~13h on a mark

      if view.get("instrumentState") not in TERMINAL_STATES:
          return "review"
      if px not in (Decimal("0.00"), Decimal("1.00")):
          return "review"                        # binary markets have been seen settling non-binary — hold, do not pay
      return "resolved"


  if __name__ == "__main__":
      state = resolution_state(settlement_view(SYMBOL))
      series = "auto-resolving" if SYMBOL.startswith(AUTO_RESOLVING_PREFIXES) else "manual"
      print(SYMBOL, state, series)
      # "resolved" → mirror our credit. We credit the participant account; you must not credit again.
      # "review"   → queue for a human. Do not auto-pay and do not auto-void.
  ```

  ```typescript TypeScript theme={null}
  const REST_BASE = "https://api.preprod.polymarketexchange.com";
  const SYMBOL = "aec-cfb-clmsn-lsu-2026-09-05"; // from the position change that triggered this check
  const ACCESS_TOKEN = process.env.PMX_ACCESS_TOKEN!; // mint per /connect#authentication; expires_in is 180s

  // Gate on event series and symbol prefix. automaticResolution controls nothing.
  // VERIFY: `aec-atp` is the only symbol prefix we have evidence for. The authoritative
  // list of auto-resolving series and prefixes must come from us — never guess one.
  const AUTO_RESOLVING_PREFIXES = ["aec-atp"];

  // VERIFY: which InstrumentState values are terminal for resolution is not published,
  // and these two spellings are unconfirmed. See the warning above.
  const TERMINAL_STATES = new Set(["INSTRUMENT_STATE_CLOSED", "INSTRUMENT_STATE_TERMINATED"]);

  /**
   * Parses an RFC 3339 timestamp, or null when the field is absent or unparseable.
   * Never compare these as strings: an absent field is an empty string, which
   * compares LOW against every real timestamp and silently passes the gate.
   */
  function instant(value: unknown): number | null {
    if (typeof value !== "string" || value === "") return null;
    const ms = Date.parse(value);
    return Number.isNaN(ms) ? null : ms;
  }

  type ResolutionState = "resolved" | "mark" | "review";

  async function settlementView(symbol: string): Promise<any> {
    // /v1/orderbook/* needs no x-participant-id.
    const response = await fetch(`${REST_BASE}/v1/orderbook/${symbol}`, {
      headers: { authorization: `Bearer ${ACCESS_TOKEN}` },
      signal: AbortSignal.timeout(10_000), // the edge 504s past 30 seconds; fail fast instead
    });
    if (!response.ok) {
      // Never swallow: 401 means a stale token, 504 means >30s at the edge.
      throw new Error(`orderbook read failed: ${response.status} ${await response.text()}`);
    }
    return response.json();
  }

  function resolutionState(view: any): ResolutionState {
    const pxRaw = view.settlementPx; // int64 arrives as a string
    const priceScale = Number(view.priceScale); // 100 and 1000 are both live in production
    if (pxRaw === undefined || pxRaw === null || !priceScale) return "mark";

    const px = Number(pxRaw) / priceScale; // de-scale with priceScale, not settlementPriceScale

    if (view.settlementPreliminary !== false) return "mark"; // false alone is NOT proof of resolution

    const setTime = instant(view.settlementSetTime);
    const expiration = instant(view.expirationDate);
    // A missing field FAILS the condition; it never passes it.
    if (setTime === null || expiration === null) return "review";
    // settlementSetTime can precede expirationDate by ~13h on a mark.
    if (setTime < expiration) return "review";

    if (!TERMINAL_STATES.has(view.instrumentState)) return "review";
    if (px !== 0 && px !== 1) return "review"; // binary markets have settled non-binary — hold, do not pay
    return "resolved";
  }

  const series = AUTO_RESOLVING_PREFIXES.some((p) => SYMBOL.startsWith(p)) ? "auto-resolving" : "manual";

  settlementView(SYMBOL)
    .then((view) => console.log(SYMBOL, resolutionState(view), series))
    .catch((err) => {
      console.error(err);
      process.exit(1);
    });
  ```

  ```bash curl theme={null}
  # /v1/orderbook/* needs no x-participant-id. --fail-with-body so a 401 or 504 is not silently parsed.
  curl --fail-with-body --max-time 10 \
    -H "authorization: Bearer $PMX_ACCESS_TOKEN" \
    "https://api.preprod.polymarketexchange.com/v1/orderbook/aec-cfb-clmsn-lsu-2026-09-05"
  ```
</CodeGroup>

The fields you branch on, as JSON:

```json theme={null}
{
  "symbol": "aec-cfb-clmsn-lsu-2026-09-05",
  "priceScale": 100,
  "settlementPx": "100",
  "settlementPreliminary": false,
  "settlementSetTime": "2026-09-06T03:12:44Z",
  "expirationDate": "2026-09-06T03:00:00Z",
  "instrumentState": "INSTRUMENT_STATE_CLOSED"
}
```

`settlementPx: "100"` at `priceScale: 100` is \*\*$1.00** — a winning contract. At `priceScale: 1000` the same raw value is $0.10, which is a mark, not a win. That is why the scale is read per instrument and never hard-coded.

<Note>
  **Confirm the response field spellings against a live call in your environment.** REST responses are
  `camelCase` while requests are `snake_case` and webhooks are `snake_case`, and the instrument-state
  field on this response is not documented.
</Note>

### `automaticResolution` controls nothing

<Warning>
  **`automaticResolution` is reserved on the v2 surface and controls nothing.** It reads `false` for
  **all 223 preprod sports, including `atp` and `wta`, which do resolve automatically.**

  Our own settlement guide told a partner to require `automaticResolution: true` before selecting a
  market, which made the gate unpassable as written. **Gate on event series and symbol prefix
  instead.**
</Warning>

**\[VERIFY] — the list of auto-resolving series and prefixes has to come from us, not from a guess.** What is evidenced is that the `atp` and `wta` **series** resolve automatically, and that `aec-atp` is a real **symbol prefix**. No other prefix is evidenced: a plausible-looking sibling such as `aec-wta` is constructed, and a constructed prefix either never matches or matches the wrong markets. The snippets above therefore carry `aec-atp` only.

Keep the list in configuration, not in code, so you can extend it the day your integration lead confirms the authoritative set.

### Preprod will not show you a resolution

**Markets do not resolve in preprod.** Instruments reach `INSTRUMENT_STATE_EXPIRED` in batches — 363, 455, 544 and 549 in a single session — with none resolving. One partner asked six times over 40 days to test a settlement flow and never could. Test your gate against captured or synthetic messages and treat the first production resolution as the real test, with the review queue switched on.

## Settlement fields

Five fields carry settlement, and three of them mean less than their names suggest.

Winning contracts settle at **$1.00** and losing contracts at **$0.00**.

### The five fields

<ResponseField name="settlement_px" type="int64">
  The settlement price, on the wire as a scaled integer and in JSON as a **string**. De-scale it with
  the instrument's **`priceScale`**: `settlement_px ÷ priceScale`. Do **not** use
  `settlementPriceScale` — it is reserved and reads `0`, so dividing by it is a divide-by-zero or a
  silently wrong number depending on your language.

  A non-binary value on a non-resolved instrument is a **mark**, not an outcome.
</ResponseField>

<ResponseField name="settlement_preliminary" type="bool">
  **Not a resolved flag.** `false` does not mean "resolved to an outcome" — marks carry `false` too,
  and there is no positive `resolved` indicator anywhere on the API. Gating a payout on this field
  alone pays out on marks. Use the full gate on
  [Detect that a market resolved](#detect-that-a-market-resolved).
</ResponseField>

<ResponseField name="settlement_price_calculation_method" type="enum">
  The calculation method. `CALCULATION_METHOD_VALUE` appears on the wire with **no documented
  meaning**.
</ResponseField>

<ResponseField name="settlement_price_calculation_text" type="string">
  **Not guaranteed present on a settled instrument.** A partner who gated settlement on this field
  had a market stuck suspended in their system. Log it, show it, never branch on its presence.
</ResponseField>

<ResponseField name="settlement_set_time" type="timestamp">
  When the settlement value was set. **It can precede `expirationDate` by \~13 hours on a mark**, so a
  populated `settlement_set_time` is not evidence of resolution. In the gate it is a necessary
  condition (`settlement_set_time` at or after `expirationDate`), never a sufficient one.
</ResponseField>

Where they live: the **market data stream** or **`GET /v1/orderbook/{symbol}`**. `CreateInstrumentStateChangeSubscription` returns the updated `Instrument` and does **not** include them.

### De-scaling, worked

```
settlementPx  = "100"   (int64 fields serialize as strings)
priceScale    = 100     (read off the instrument — 100 and 1000 are both live in production)

settlement    = 100 ÷ 100 = $1.00   → a winning contract
```

The same raw value on a `priceScale: 1000` instrument is `$0.10` — a mark, not a win. Two of 121 open ATP instruments publish `priceScale: 1000`, so a hard-coded divisor is right everywhere else and wrong by 10× there.

proto3 does not put a scalar at its default value on the wire, so an **absent field and a zero are indistinguishable**. Treat a missing `settlement_px` as "no value", never as `$0.00` — a losing contract and an unpopulated field look identical otherwise.

### Fields that appear on the wire with no documented meaning

| Value                      | Status                                                        |
| -------------------------- | ------------------------------------------------------------- |
| `EVENT_TIER_1`             | Appears on the wire. **No documented meaning.** **\[VERIFY]** |
| `EVENT_TIER_2`             | Appears on the wire. **No documented meaning.** **\[VERIFY]** |
| `EVENT_TIER_3`             | Appears on the wire. **No documented meaning.** **\[VERIFY]** |
| `EVENT_TIER_4`             | Appears on the wire. **No documented meaning.** **\[VERIFY]** |
| `CALCULATION_METHOD_VALUE` | Appears on the wire. **No documented meaning.** **\[VERIFY]** |

<Warning>
  **Not yet published — do not branch on any of the five values above.** They are real values you
  will see, with no semantics we can stand behind. Log them, and ask your integration lead before you
  give any of them meaning in your code.
</Warning>

`settlementPriceScale` is in the same category with one difference: we know what it does, which is nothing. It is **reserved and reads 0**.

### Fees

**There is no `settlement_fee` on the partner API.** It was cited to a partner in error and retracted. The only fees are the **trading fees** in the published Fee Schedule.

`Fee = Θ × C × p × (1 − p)`, rounded to the cent with banker's rounding, at the **taker** coefficient — an ISV is structurally always the taker on `CreateVendorOrder` and can never earn the maker rebate. The coefficient changed on 2026-09-14 and there is no fee-schedule endpoint, so partners hand-edit it. The formula, the current coefficient and the scale handling are on [Money on the wire](/instruments#money-on-the-wire).

`SETTLEMENT_FEE` exists as a **suppressed internal `LedgerEntryType`**. Requesting it returns `Aborted` / 409. Its existence in the enum is not a fee you pay — see [Balance ledger](/streams#balance-ledger).

### Gross or net: unanswered

<Warning>
  **Whether the `RESOLUTION` ledger entry is gross with `COMMISSION` deducted separately, or net, is
  unanswered.**

  Do not infer it from executions: an execution folds commission into a **single net
  `ORDER_EXECUTION` entry**, and there is no basis for assuming settlement behaves the same way.

  Build your reconciliation so that either answer is a configuration change, and confirm the answer
  with your integration lead before you publish a settled-amount figure to a user.
</Warning>

## Cancelled and voided markets

**Cancellation and void semantics are not settled, and we will not give you a rule that might be wrong.** Hold these positions for review instead of paying them.

### What has been observed

<Warning>
  **Cancelled events have settled inconsistently — voided at 0.5, fractional, or a 404 — within a
  single event.** There is an ITF-tennis \$0.50 exception. Sources disagree on which behaviour is
  correct, so no value on this page is a rule.
</Warning>

<Warning>
  **Binary markets have been observed settling to non-binary values** (`-1.5 → 0.28`,
  `+1.5 → 0.77`). This is unexplained, and it contradicts the rule that winning contracts settle at
  $1.00 and losing at $0.00.
</Warning>

Both items are unresolved. Until they are resolved, treat any settlement that is not exactly $1.00 or $0.00 as a case for a human.

### What to do in the meantime

<Steps>
  <Step title="Do not auto-pay on a non-binary settlement">
    A fractional value is either a mark or an unexplained settlement, and you cannot tell which from
    the API. Both are reasons not to move money.
  </Step>

  <Step title="Hold the position for review">
    Route it to a queue with the symbol, the raw `settlementPx`, the instrument's `priceScale`,
    `settlementPreliminary` and `settlementSetTime` attached. Those five values are what anyone will
    ask you for.
  </Step>

  <Step title="Reconcile manually against the ledger">
    The `RESOLUTION` ledger entry is what actually happened to the account. Reconcile against it
    rather than against your own expectation of the outcome — and note that whether that entry is
    gross or net of commission is itself unresolved, see [Gross or net](#gross-or-net-unanswered).
  </Step>

  <Step title="Do not originate a credit or a reversal">
    We credit the participant's clearing account. If you pay or claw back from your own ledger while
    we are still settling the market, the two sides diverge — see
    [Who credits the user](#who-credits-the-user).
  </Step>

  <Step title="Escalate the first one you see">
    Post the case in your shared Slack channel, tagged, with the environment and the symbol. These
    cases are how the semantics get resolved.
  </Step>
</Steps>

A 404 on a cancelled market's symbol is one of the observed behaviours, so your handler must survive it: treat a 404 on a symbol you hold a position in as a review case, not as "no such market".

### No correction, bust or refund policy exists

<Warning>
  **Not yet published.**&#x20;
  There is no published correction, bust or refund policy, and no statement of whether institutional
  participants are in scope when retail positions are refunded after an outage. If you need to know
  what happens to your users' positions in either case — and you do, before go-live — get it in
  writing from your integration lead.
</Warning>

## What can go wrong

| Part                 | Symptom                                                         | Cause                                                                                                                                                                                                 | What you do                                                                                                                                 |
| -------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Who credits the user | A user is paid twice for one settled position                   | Both sides credited: we credited the participant account and you credited from your own ledger                                                                                                        | Mirror our balance; never originate a settlement credit. Reconcile the double payouts before enabling the path                              |
| Who credits the user | You built a post-settlement sweep and it fails                  | `OdfSweep` was removed on 2026-08-06 with no changelog entry                                                                                                                                          | Delete the step. Settlement proceeds are already buying power in the participant account                                                    |
| Who credits the user | `NOT_FOUND: customer relationship claim failed`                 | The relationship is established server-side by KYC and there is no claim or link RPC. Root causes seen: a shared test SSN across users, an incomplete server-side setup step, a half-provisioned firm | Check for a reused SSN first — one SSN maps to exactly one exchange account platform-wide — then raise it with your integration lead        |
| Who credits the user | `InvalidArgument` naming `amount` on a transfer                 | The published `CreateCashMovement` proto had the wrong field number: `transfer` is field 4 in the real service                                                                                        | Regenerate from a bundle your integration lead confirms. See [Protos and SDKs](/environments#protos-and-sdks)                               |
| Who credits the user | A retry of a rejected transfer does nothing                     | A terminally-rejected `idempotency_key` replays its stored rejection                                                                                                                                  | Use a new key after a terminal reject                                                                                                       |
| Who credits the user | `403 method not permitted` from `CheckoutAPI` or Aeropay        | ISVs are not entitled to those surfaces                                                                                                                                                               | Use `CashMovementService`. Production deposit rails are **wire only** as of Aug 2026                                                        |
| Who credits the user | A user withdraws money that backs an open short                 | An open short carries an obligation of up to \$1.00 per contract, and `balance_reservation` / `margin_requirement` came back **empty** on the ledger entries for one                                  | Hold your own reserve against open shorts. Whether a collateral requirement is enforced and where it is exposed is unresolved **\[VERIFY]** |
| Detect a resolution  | You paid a user on a market that had not resolved               | You gated on `settlementPreliminary: false`, which marks also carry                                                                                                                                   | Require the full gate above, and hold anything that fails it                                                                                |
| Detect a resolution  | No settlement fields ever arrive                                | You are reading `CreateInstrumentStateChangeSubscription`, which does not carry them                                                                                                                  | Use the market data stream or `GET /v1/orderbook/{symbol}`                                                                                  |
| Detect a resolution  | A market you consider settled is stuck suspended in your system | You gated on `settlement_price_calculation_text`, which is not guaranteed present                                                                                                                     | Drop that condition                                                                                                                         |
| Detect a resolution  | Your gate never passes                                          | You required `automaticResolution: true`; it reads `false` everywhere, including `atp` and `wta`                                                                                                      | Gate on event series and symbol prefix                                                                                                      |
| Detect a resolution  | Price is 10× off                                                | `priceScale` is 1000 on some instruments and 100 on most, and `settlementPriceScale` reads 0                                                                                                          | De-scale per instrument with `priceScale`. See [Money on the wire](/instruments#money-on-the-wire)                                          |
| Detect a resolution  | `settlementSetTime` is before `expirationDate`                  | That happens on a mark, by \~13 hours in an observed case                                                                                                                                             | Hold for review                                                                                                                             |
| Detect a resolution  | A binary market settled at 0.28                                 | Observed and unexplained                                                                                                                                                                              | Do not auto-pay. See [Cancelled and voided markets](#cancelled-and-voided-markets)                                                          |
| Detect a resolution  | `504` on the read                                               | Over 30 seconds at the edge                                                                                                                                                                           | Read one symbol at a time rather than batching, and retry with backoff                                                                      |
| Settlement fields    | Settlement price is 10× or 100× off                             | You hard-coded a divisor, or divided by `settlementPriceScale`                                                                                                                                        | De-scale with `priceScale`, read per instrument                                                                                             |
| Settlement fields    | Divide-by-zero on settlement                                    | `settlementPriceScale` is reserved and reads `0`                                                                                                                                                      | Never use it                                                                                                                                |
| Settlement fields    | A losing contract and a missing value look the same             | proto3 omits scalars at their default value                                                                                                                                                           | Check field presence explicitly; treat absent as "no value"                                                                                 |
| Settlement fields    | A market is stuck suspended in your system                      | You gated on `settlement_price_calculation_text`, which is not guaranteed present                                                                                                                     | Remove that condition                                                                                                                       |
| Settlement fields    | Your settled amount does not match the ledger                   | Gross-versus-net on the `RESOLUTION` entry is unresolved, and executions are net                                                                                                                      | Reconcile against the ledger entry, not your own arithmetic, and flag the discrepancy                                                       |
| Settlement fields    | You budgeted for a settlement fee                               | There is no `settlement_fee`                                                                                                                                                                          | Budget the trading fees only                                                                                                                |
| Settlement fields    | `Aborted` / `409` requesting `SETTLEMENT_FEE` from the ledger   | It is one of the twelve suppressed internal entry types                                                                                                                                               | Request only allowlisted types                                                                                                              |
| Settlement fields    | `EVENT_TIER_*` drives a branch that behaves oddly               | Those values have no documented meaning                                                                                                                                                               | Stop branching on them                                                                                                                      |
| Cancellations        | Two instruments in one cancelled event settled differently      | Observed behaviour: voided at 0.5, fractional, or 404 within a single event                                                                                                                           | Hold both for review; do not derive one from the other                                                                                      |
| Cancellations        | A binary market settled at 0.28                                 | Observed and unexplained                                                                                                                                                                              | Hold for review. Do not auto-pay and do not auto-void                                                                                       |
| Cancellations        | A `404` on a symbol you hold                                    | One of the observed cancellation behaviours                                                                                                                                                           | Treat as a review case, not as a missing market                                                                                             |
| Cancellations        | A tennis market settled at \$0.50                               | There is an ITF-tennis \$0.50 exception, whose scope is unconfirmed                                                                                                                                   | Hold for review and escalate; do not generalise the \$0.50 to other series                                                                  |
| Cancellations        | You reversed a user's credit and we had not                     | You originated a reversal from your own ledger                                                                                                                                                        | Mirror our numbers only. See [Who credits the user](#who-credits-the-user)                                                                  |

<Snippet file="support.mdx" />

## Next

[Preprod and testing](/preprod)
