Lifecycle
Settlement
A settlement record is an instruction: this position closed, this is what to credit. It is computed once, on Parity’s side, so that two systems are never doing the same arithmetic.
The settlement register
/api/v1/settlementsAPI keysettlements:read.| Parameter | Notes |
|---|---|
externalUserId | Your own id for the customer. An id that has never traded is 404 unknown_user, not an empty page. |
outcome | won, lost or void. |
settledFrom / settledTo | ISO-8601, inclusive. |
limit | Default 100. Above 500 is clamped to 500; zero, negative or not an integer is 400 invalid_limit. |
cursor | Opaque. Echo nextCursor from the previous page. |
Swipe the table sideways for more columns.
curl -s "$PARITY_BASE/api/v1/settlements?externalUserId=user-4471&outcome=won&limit=100" \
-H "Authorization: Bearer $PARITY_KEY"{
"settlements": [
{
"id": "a41f7d0e-…", // stable. Safe as the idempotency key for a credit.
"positionId": "5e0c2a91-…",
"externalUserId": "user-4471", // YOUR id, never a Parity uuid
"contractId": "ctr_9f31c2…", // the opaque contract this settled against
"side": "YES",
"resolution": "YES", // YES | NO | VOID, as resolved upstream
"outcome": "won", // won | lost | void, for this position
"contracts": 68.6111,
"realisedPnl": 18.61, // THIS settlement's
"lifetimeRealisedPnl": 18.61, // the position's running total
"settledAt": "2026-08-21T18:02:11.004Z",
"operatorMoney": {
"currency": "USD",
"settlementCredit": "68.61", // CREDIT THIS
"voidCredit": "0.00"
},
"cursor": "…"
}
],
"count": 1,
"nextCursor": "…", // NOT null at the tail: a watermark to poll with
"hasMore": false // the terminator. Stop on this
}realisedPnl is this settlement’s, not the position’s lifetime
The two differ whenever a position was partly exited before it resolved. Reporting the lifetime figure under a per-settlement name overstates the period, so both are published and each is named for what it is. Sum realisedPnl for a period; lifetimeRealisedPnl is the position's running total.
Exactly one credit is non-zero
| outcome | settlementCredit | voidCredit |
|---|---|---|
won | $1.00 × contracts, rounded down to the cent, the same rounding as the quoted potentialPayout, so the two agree. | 0.00 |
lost | 0.00 | 0.00. A loss zeroes both, so there is nothing to credit. |
void | 0.00 | An annulled market: everything the player committed, including the platform fee. A split result: the split payout for the player’s contracts, rounded down to the cent, with the fee kept. |
Swipe the table sideways for more columns.
An annulled market refunds the commission as well as the stake. Parity did not provide the market, so it does not keep the fee: quietly retaining it would be a new commercial term introduced by a backend decision. The two fields are separate rather than one signed amount because a refund and a win are different postings in an operator's books.
An ordinary contract settles at exactly $1.00 or exactly $0.00: a market that resolved 99% certain did not resolve. The one exception is a split result, where the source resolves the contract by dividing the payout between both sides, for example fifty cents a contract to each, or seventy-five and twenty-five. Parity reports it as resolution: "VOID" and outcome: "void", but it is not a refund: the trade stood, each contract pays its side's share of a dollar, voidCredit carries that payout, the platform fee is kept, and realisedPnl is the payout less everything the player committed. Credit voidCredit as the instruction says; do not recompute it from the stake.
A position with a live cash out still being confirmed at the venue waits for that sale to be booked before it settles, so the settlement pays only on the contracts the player still holds.
Consuming it safely
- Key your credit on the settlement's
id. It is stable, and it is what makes re-processing a no-op.positionIdworks equally well and is what the webhook keys on, so a partner using both never processes the same fact twice. - Advance your stored cursor only after the batch is applied. Reading does not consume: the same cursor returns the same page, so a consumer that crashes mid-batch re-reads rather than skips.
- The register is scoped to your operator inside the query, not filtered afterwards. Another partner's settlements are not reachable by guessing a cursor.
hasMore: falsemeans caught up. Poll on the order of once a minute; settlements are not high-frequency events.
for await (const s of parity.iterateSettlements({ settledFrom: lastRun })) {
// s.operatorMoney.settlementCredit and .voidCredit are exact Decimal strings.
await ledger.creditOnce(s.id, s.externalUserId, s.operatorMoney);
}The older feed
/api/v1/settlements/positionsAPI keysupersededBy: "/api/v1/settlements".It pages on settledAt, which is not unique: settlements written in one transaction share a timestamp exactly. So a page never ends inside a group of settlements sharing one timestamp, and a group larger than limit is delivered whole, which means count can exceed limit. Poll with the returned cursor and apply records idempotently on positionId. It is left working because it is already a published contract, but new integrations should use the register above: same records, a keyset cursor on a monotonic sequence.
Every record addresses the contract by contractId
Every partner response addresses a contract by its opaque ctr_… id, and so does this feed: each record carries contractId, the same value GET /api/v1/settlements publishes, and the same one you read on the quote, the order and the position. Correlate on settlementId, positionId and contractId.
Why you cannot be paid twice
Three layers, each of which would be sufficient alone:
- The position row is locked for update inside the settlement transaction, so two concurrent sweeps serialise rather than both reading it as open.
- Only a position still marked open is considered.
- The settlement insert carries a unique index on
positionId. If it inserts nothing, no credit is computed, no status is written and no event is emitted.
The third is the one that holds if the others are ever wrong, because it is a database constraint rather than a branch in application code.
Voids and amendments
Upstream sources sometimes change a published result. Parity handles that by refusing to pretend it did not happen, and by refusing to act unilaterally.
| Situation | What Parity does |
|---|---|
| A contract resolves normally | Positions settle against that contract’s own resolution, and no other. A grouped listing elsewhere settles independently and is permitted to disagree. |
| A contract closes with no published result | It is reported as determined, never guessed. Nothing settles. |
| A resolution changes after positions settled | The contract moves to needs_review, which stops anything further from settling against either value, and a market.resolution_changed event names the affected positions. |
Swipe the table sideways for more columns.
An amendment is a notification, not an instruction
Parity never reverses a settlement, adjusts a figure or rewrites a settlement row. Money that left your ledger on the strength of a published outcome is not clawed back by a background job: the source may be correcting a mistake, or making one. You receive the exact list of affected positions and the decision is yours.
Proving the period balances
The register answers “what settled”. To check that every money fact of a period agrees (trial balance, cash statement, venue agreement, and the individual findings when it does not), read Reconciliation. Its feed carries position.settled alongside every fill, fee and collateral movement, totally ordered and resumable, which is the stream to build a nightly close on.
Settlement data is always authenticated
Settlement is published only to the account it belongs to, because a feed of every resolved contract is a bulk export of the catalogue by another name. If you run your own book against Parity's resolutions, read GET /api/v1/settlements, which is keyset-paginated and scoped to your own account. If Parity recorded the positions, use the register above. Working out who held what from a market outcome is precisely the arithmetic that must not be performed in two places.

