# info.description
> **✨ LATEST VERSION**
> 
> This is the **latest API version (v2.0)**. 
> 
> For reference, the deprecated v1.0 documentation is available at: [https://api.futuur.com/v1.0/docs](https://api.futuur.com/v1.0/docs)

> **🤖 AI Agents Integration**
> 
> For AI agents and automated integrations, access the OpenAPI schema directly at:
> 
> **[https://api.futuur.com/docs/schema/](https://api.futuur.com/docs/schema/)**
> 
> This endpoint provides the complete API specification in JSON format, perfect for code generation, API clients, and AI-powered integrations.


# Introduction

## API Structure

### Events and Markets

The Futuur API is built around **Events** (previously referred to as Questions in older API versions) and **Markets** (previously referred to as Outcomes in older API versions).

- **Events** represent a question or prediction that can be bet on (e.g., "Who will win the 2024 US Presidential Election?")
- **Markets** are the possible outcomes for an event (e.g., "Candidate A", "Candidate B", "Other")

Each event contains multiple markets, and the relationship between markets is defined by **markets correlation**:

- **Linked prices**: Markets are correlated. The sum of all market prices equals 1, and placing a bet on one market automatically affects the prices of other markets in the same event. This is the default correlation type.

- **Independent**: Markets operate independently. Each market's price is calculated separately, and betting on one market does not affect others.

The correlation type determines how prices are calculated and how bets impact market dynamics within an event.

### Limit Orders

Bets are placed through **Limit Orders**, which allow you to specify how you want to participate in a market. Orders can be created in two ways:

- **By Market** (Market Order): Place an order without specifying a price (`price=None`). The order will be executed immediately at the best available price in the order book. You're willing to accept whatever price is currently available for matching your bid (buy) or ask (sell) order.

- **By Price** (Limit Order): Place an order with a specific price you're willing to pay (for buy orders) or receive (for sell orders). The order will be executed when the market price reaches your specified price, or remain in the order book until a matching order is found.

Limit orders provide flexibility in how you enter and exit positions, allowing you to set precise price targets or execute immediately at current market prices.

## Rate Limiting
To ensure the quality and accessibility of our API, we enforce certain rate limits per user.
The limit rates are the following per user:

* **2000 requests / minute**
* **30000 requests / day**

Please be aware that exceeding these limits may result in your access to the API being temporarily suspended.

For more detailed information about the various available endpoints, the expected input parameters, the response
format, error codes to anticipate, and more, please refer to our detailed API documentation or contact our
support team at support@futuur.com.

P.S.: Any attempt to submit the same post request within less than 1 second will result in an 'wager_action_duplicated' error code.

# paths./v2.0/events/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/events/.get.parameters[].description
The initial index from which to return the results.

# paths./v2.0/events/.get.parameters[].description
Which field to use when ordering the results.

# paths./v2.0/events/{id}/.get.parameters[].description
A unique integer value identifying this event.

# paths./v2.0/events/{id}/actions/.get.description
List all event's Actions.

Real money actions have users information anonymized.

# paths./v2.0/events/{id}/actions/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/events/{id}/actions/.get.parameters[].description
A unique integer value identifying this event.

# paths./v2.0/events/{id}/get_tax/.get.parameters[].description
A unique integer value identifying this event.

# paths./v2.0/events/{id}/price_history/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/events/{id}/price_history/.get.parameters[].description
A unique integer value identifying this event.

# paths./v2.0/events/{id}/price_history/.get.parameters[].description
* `day` - Day
* `week` - Week
* `month` - Month
* `year` - Year
* `all_time` - All Time

# paths./v2.0/events/{id}/related_events/.get.parameters[].description
A unique integer value identifying this event.

# paths./v2.0/events/{id}/wagers/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/events/{id}/wagers/.get.parameters[].description
A unique integer value identifying this event.

# paths./v2.0/markets/{id}/book/.get.description
Retrieve the aggregated order book for a market, grouped by price level. The response includes both `ask` and `bid` sides with cumulative totals.

# paths./v2.0/markets/{id}/book/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/markets/{id}/book/.get.parameters[].description
A unique integer value identifying this market.

# paths./v2.0/me/.get.description
Return information about the authenticated user.

# paths./v2.0/me/balances/.get.description
Return ledger account balances for the authenticated user, one object per currency. Use the optional `currency` query parameter to return only that currency.

# paths./v2.0/me/balances/.get.parameters[].description
If set, only the balance for this currency is returned (matches stored currency code, case-insensitive).

# paths./v2.0/me/ranking/.get.description
Return the ranking of the authenticated user.

# paths./v2.0/me/safe/.post.description
Registers Safe smart account address for authenticated user

# paths./v2.0/orders/.get.description
This mixing let you define throttles_classes per actions by passing a dict to throttle_action_classes attribute.
The throttle_classes on throttle_action_classes will be added to the default's ViewSet throttle_classes.
eg.:
class MyViewSet(ModelViewSet):
    throttle_action_classes = {
        'create': [CustomThrottleClass],
        'retrieve': [AnonThrottleClass]
    }

# paths./v2.0/orders/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/orders/.get.parameters[].description
The initial index from which to return the results.

# paths./v2.0/orders/.get.parameters[].description
Which field to use when ordering the results.

# paths./v2.0/orders/.get.parameters[].description
* `open` - open
* `partial_filled` - partial filled
* `filled` - filled
* `canceled` - canceled
* `processing` - processing

# paths./v2.0/orders/.post.description
Create a new limit order.

Request body (subset):
  - market (pk, required)
  - side: "bid" | "ask" (required)
  - position: "long" | "short" (required, market-dependent)
  - price: decimal string | null (null = market order)
  - shares: decimal string (required)
  - amount: decimal string (optional echo for cost validation)
  - currency: e.g. "OOM", "USDC" (required)
  - time_in_force: "gtc" | "ioc" (default "gtc"). "ioc" fills immediately at
    the given price and cancels any unfilled remainder; it requires a price.
  - expired_at: ISO datetime (optional)
  - cancel_conflicting_orders: bool (default False)

Returns:
  - 201 Created with LimitOrderSerializer data.

Raises:
  - RealMoneyForbiddenCountry, RealMoneyBetNotAllowed
  - EmailNotConfirmed, UserBlockedBets
  - InvalidHMACKey, UserNotEnoughBalance, InvalidShares
  - OutcomeDisabled, MarketClosed / OrderBookMarketClosed
  - OrderBookConflictingOrders, OrderBookInvalidAmount

# paths./v2.0/orders/.post.parameters[].description
Optional. Unique value chosen by the client for a logical operation. If you repeat the same POST with the same key and the same JSON body after a successful response, the API returns the stored response and does not create another order. A different body (e.g. different price) uses a different fingerprint, so requoting is not blocked.

# paths./v2.0/orders/{id}/cancel/.patch.parameters[].description
A unique integer value identifying this limit order.

# paths./v2.0/orders/batch/.post.description
Create multiple limit orders in a single request.

Request body:
  - orders: list[1..20] of:
      - event (int, optional consistency check)
      - market (outcome id, required)
      - side: "bid" | "ask"
      - position: "long" | "short"
      - price: decimal string | null
      - shares: decimal string
      - amount: decimal string (optional)
      - currency: e.g. "OOM", "USDC"
      - time_in_force: "gtc" | "ioc" (default "gtc"). "ioc" fills immediately
        at the given price and cancels any unfilled remainder; requires a price.
      - expired_at: ISO datetime (optional)
      - cancel_conflicting_orders: bool (optional)

Returns:
  - 200 OK
  - {
      "results": [
        {"index": 0, "success": true, "order": {...}},
        {"index": 1, "success": false, "errors": "..."}
      ],
      "cancelled_existing_order_ids": [1, 2, 3]
    }

# paths./v2.0/orders/batch/.post.parameters[].description
Optional. Unique value chosen by the client for a logical operation. If you repeat the same POST with the same key and the same JSON body after a successful response, the API returns the stored response and does not create another order. A different body (e.g. different price) uses a different fingerprint, so requoting is not blocked.

# paths./v2.0/orders/batch-cancel/.post.description
Batch Cancel Orders.

Request body:
  - order_ids: list[int] (required)

Returns:
  - 200 OK with one result item per requested id:
    [
      {"index": 0, "order_id": 100, "success": true},
      {"index": 1, "order_id": 101, "success": false, "error": "Order is being processed."}
    ]

# paths./v2.0/orders/batch-cancel/.post.parameters[].description
Which field to use when ordering the results.

# paths./v2.0/orders/batch-update/.post.description
Batch update open limit orders.

Each update cancels the existing open order and creates a replacement
with the updated fields, so all order-book validations run normally.

Optional per-item field:
  - time_in_force: "gtc" | "ioc". When omitted the replacement keeps the
    existing order's value. "ioc" fills the replacement immediately at its
    price and cancels any unfilled remainder instead of resting on the book.

# paths./v2.0/orders/batch-update/.post.parameters[].description
Optional. Unique value chosen by the client for a logical operation. If you repeat the same POST with the same key and the same JSON body after a successful response, the API returns the stored response and does not create another order. A different body (e.g. different price) uses a different fingerprint, so requoting is not blocked.

# paths./v2.0/orders/batch-update/.post.parameters[].description
Which field to use when ordering the results.

# paths./v2.0/orders/cancel-all/.post.description
Cancel all active limit orders for the authenticated user.

Request body (optional filters):
  - event: int (question id)
  - market: int (outcome id)

Returns:
  - 200 OK
  - {
      "canceled_order_ids": [100, 101],
      "processing_order_ids": [102]
    }

# paths./v2.0/wagers/.get.parameters[].description
Filter by active wagers (wagers with status purchased)

# paths./v2.0/wagers/.get.parameters[].description
* `play_money` - Play money
* `real_money` - Real money

# paths./v2.0/wagers/.get.parameters[].description
The initial index from which to return the results.

# paths./v2.0/wagers/.get.parameters[].description
Filter by not active wagers (wagers with status sold, won, lost, disabled)

# paths./v2.0/wagers/{id}/.get.parameters[].description
A unique integer value identifying this wager.

# components.schemas.AddLimitOrder.properties.price.description
Limit price per share the user is willing to pay or receive.

# components.schemas.AddLimitOrder.properties.currency.description
* `USDC` - USDC
* `USDT` - USDT
* `USD` - USD
* `OOM` - OOM

# components.schemas.AddLimitOrder.properties.position.description
long: bets in favor of the market
short: bets against the market. * `long` - long * `short` - short

# components.schemas.AddLimitOrder.properties.side.description
bid: purchase
ask: sell. * `bid` - Bid * `ask` - Ask

# components.schemas.AddLimitOrder.properties.time_in_force.description
gtc (default): the order rests on the book until it is fully filled or cancelled.
ioc: the order fills immediately at the given price and any unfilled remainder is cancelled. Requires a price (a priceless order is a market order). * `gtc` - Good Till Cancelled * `ioc` - Immediate Or Cancel

# components.schemas.EventBase.properties.status.description
* `open` - open
* `stopped` - stopped
* `resolved` - resolved
* `cancelled` - cancelled
* `paused` - paused
* `reversed` - reversed

# components.schemas.EventBase.properties.order_book_enabled.description
If enabled, Question will use Order Book to place bet instead of AMM(LsLmsr).

# components.schemas.EventBase.properties.resolution_mode.description
Exclusive: Only one market can resolve to LONG.
Non Exclusive: Multiple markets can resolve to LONG. Markets have independent rules. * `exclusive` - Exclusive * `non_exclusive` - Non Exclusive

# components.schemas.EventDetail.properties.status.description
* `open` - open
* `stopped` - stopped
* `resolved` - resolved
* `cancelled` - cancelled
* `paused` - paused
* `reversed` - reversed

# components.schemas.EventDetail.properties.available_currencies.items.description
* `BCH` - BCH
* `BNB` - BNB
* `BRL` - BRL R$
* `BTC` - BTC ₿
* `BUSD` - BUSD
* `DOGE` - DOGE
* `ETH` - ETH
* `EUR` - EUR €
* `LTC` - LTC Ł
* `LTCT` - LTCT (test coin)
* `MXN` - MXN MX$
* `OOM` - OOM ø
* `SOL` - SOL
* `TRX` - TRX
* `USD` - USD $
* `USDC` - USDC
* `USDT` - USDT
* `XMR` - XMR

# components.schemas.EventDetail.properties.markets_correlation.description
This information helps Market Makers to define its prices policy.
This is purely informative and do not change the markets behavior.

Linked: Indicate that markets are correlated and influence each other.
Independent: Each market is independent from each other. * `linked` - Linked * `independent` - Independent

# components.schemas.EventDetail.properties.canonical_currency.description
All bets in real currency will be converted to this currency.

* `BCH` - BCH
* `BNB` - BNB
* `BRL` - BRL R$
* `BTC` - BTC ₿
* `BUSD` - BUSD
* `DOGE` - DOGE
* `ETH` - ETH
* `EUR` - EUR €
* `LTC` - LTC Ł
* `LTCT` - LTCT (test coin)
* `MXN` - MXN MX$
* `OOM` - OOM ø
* `SOL` - SOL
* `TRX` - TRX
* `USD` - USD $
* `USDC` - USDC
* `USDT` - USDT
* `XMR` - XMR

# components.schemas.EventDetail.properties.order_book_enabled.description
If enabled, Question will use Order Book to place bet instead of AMM(LsLmsr).

# components.schemas.EventDetail.properties.tax_play_money.description
[0, 1] - Tax for play money. Applied on AMM(LsLmsr).<br>Leave blank to inherit from the category.

# components.schemas.EventDetail.properties.tax_real_money.description
[0, 1] - Tax for real money. Applied on AMM(LsLmsr).<br>Leave blank to inherit from the category.

# components.schemas.EventDetail.properties.resolution_mode.description
Exclusive: Only one market can resolve to LONG.
Non Exclusive: Multiple markets can resolve to LONG. Markets have independent rules. * `exclusive` - Exclusive * `non_exclusive` - Non Exclusive

# components.schemas.EventDetail.properties.comment_count.description
Total number of comments created for this market.

# components.schemas.EventList.properties.status.description
* `open` - open
* `stopped` - stopped
* `resolved` - resolved
* `cancelled` - cancelled
* `paused` - paused
* `reversed` - reversed

# components.schemas.EventList.properties.available_currencies.items.description
* `BCH` - BCH
* `BNB` - BNB
* `BRL` - BRL R$
* `BTC` - BTC ₿
* `BUSD` - BUSD
* `DOGE` - DOGE
* `ETH` - ETH
* `EUR` - EUR €
* `LTC` - LTC Ł
* `LTCT` - LTCT (test coin)
* `MXN` - MXN MX$
* `OOM` - OOM ø
* `SOL` - SOL
* `TRX` - TRX
* `USD` - USD $
* `USDC` - USDC
* `USDT` - USDT
* `XMR` - XMR

# components.schemas.EventList.properties.markets_correlation.description
This information helps Market Makers to define its prices policy.
This is purely informative and do not change the markets behavior.

Linked: Indicate that markets are correlated and influence each other.
Independent: Each market is independent from each other. * `linked` - Linked * `independent` - Independent

# components.schemas.EventList.properties.canonical_currency.description
All bets in real currency will be converted to this currency.

* `BCH` - BCH
* `BNB` - BNB
* `BRL` - BRL R$
* `BTC` - BTC ₿
* `BUSD` - BUSD
* `DOGE` - DOGE
* `ETH` - ETH
* `EUR` - EUR €
* `LTC` - LTC Ł
* `LTCT` - LTCT (test coin)
* `MXN` - MXN MX$
* `OOM` - OOM ø
* `SOL` - SOL
* `TRX` - TRX
* `USD` - USD $
* `USDC` - USDC
* `USDT` - USDT
* `XMR` - XMR

# components.schemas.EventList.properties.order_book_enabled.description
If enabled, Question will use Order Book to place bet instead of AMM(LsLmsr).

# components.schemas.EventList.properties.tax_play_money.description
[0, 1] - Tax for play money. Applied on AMM(LsLmsr).<br>Leave blank to inherit from the category.

# components.schemas.EventList.properties.tax_real_money.description
[0, 1] - Tax for real money. Applied on AMM(LsLmsr).<br>Leave blank to inherit from the category.

# components.schemas.EventList.properties.resolution_mode.description
Exclusive: Only one market can resolve to LONG.
Non Exclusive: Multiple markets can resolve to LONG. Markets have independent rules. * `exclusive` - Exclusive * `non_exclusive` - Non Exclusive

# components.schemas.EventList.properties.comment_count.description
Total number of comments created for this market.

# components.schemas.LimitOrder.properties.price.description
Limit price per share the user is willing to pay or receive.

# components.schemas.LimitOrder.properties.side.description
bid: purchase
ask: sell. * `bid` - Bid * `ask` - Ask

# components.schemas.LimitOrder.properties.position.description
long: bets in favor of the market
short: bets against the market. * `long` - long * `short` - short

# components.schemas.LimitOrder.properties.expired_at.description
Date and time when the order will expire.

# components.schemas.LimitOrder.properties.status.description
Current status of the order.

* `open` - open
* `partial_filled` - partial filled
* `filled` - filled
* `canceled` - canceled
* `processing` - processing

# components.schemas.LimitOrder.properties.created.description
Date and time when the order was created.

# components.schemas.MarketList.description
PERFORMANCE TIP: Use select_related('entity') on Market queryset.

# components.schemas.MarketList.properties.title_verbose.description
Detailed title for the market.<br>Leave blank to auto generate from the event title.

# components.schemas.MarketList.properties.status.description
* `active` - active
* `resolved` - resolved
* `paused` - paused
* `stopped` - stopped
* `cancelled` - cancelled
* `reversed` - reversed

# components.schemas.MarketList.properties.position_labels.description
Long/Short

* `yesno` - Yes/No
* `updown` - Up/Down
* `custom` - Custom

# components.schemas.PatchedLimitOrder.properties.price.description
Limit price per share the user is willing to pay or receive.

# components.schemas.PatchedLimitOrder.properties.side.description
bid: purchase
ask: sell. * `bid` - Bid * `ask` - Ask

# components.schemas.PatchedLimitOrder.properties.position.description
long: bets in favor of the market
short: bets against the market. * `long` - long * `short` - short

# components.schemas.PatchedLimitOrder.properties.expired_at.description
Date and time when the order will expire.

# components.schemas.PatchedLimitOrder.properties.status.description
Current status of the order.

* `open` - open
* `partial_filled` - partial filled
* `filled` - filled
* `canceled` - canceled
* `processing` - processing

# components.schemas.PatchedLimitOrder.properties.created.description
Date and time when the order was created.

# components.schemas.PriceHistory.properties.currency_mode.description
* `play_money` - Play money
* `real_money` - Real money

# components.schemas.PriceHistory.properties.time_interval.description
* `day` - Day
* `week` - Week
* `month` - Month
* `year` - Year
* `all_time` - All Time

# components.schemas.SafeAddressRequest.description
Serializer for Safe address registration.

# components.schemas.UserPrivate.properties.wallet.description
A dictionary containing all the currencies available in your wallet.

# components.schemas.UserPrivate.properties.active_country.description
Country set from last IP user has accessed

# components.schemas.UserPrivate.properties.kyc_status.description
* `no_need` - no need
* `requested` - requested
* `initialized` - initialized
* `refused` - refused
* `approved` - approved
* `by_passed` - by passed

# components.schemas.UserPrivate.properties.pre_kyc_completed.description
This field is automatically filled when user completes the pre-KYC process.

# components.schemas.UserPrivate.properties.block_bets.description
If checked, user won't be able to bet. This flag can be automatically override by the SumSub integration.

# components.schemas.UserPrivate.properties.block_deposits.description
If checked, user won't be able to fetch a new address for deposit. This flag can be automatically override by the SumSub integration.

# components.schemas.UserPrivate.properties.residence_country.description
* `AF` - Afghanistan
* `AX` - Aland Islands
* `AL` - Albania
* `DZ` - Algeria
* `AS` - American Samoa
* `AD` - Andorra
* `AO` - Angola
* `AI` - Anguilla
* `AQ` - Antarctica
* `AG` - Antigua and Barbuda
* `AR` - Argentina
* `AM` - Armenia
* `AW` - Aruba
* `AU` - Australia
* `AT` - Austria
* `AZ` - Azerbaijan
* `BS` - Bahamas
* `BH` - Bahrain
* `BD` - Bangladesh
* `BB` - Barbados
* `BY` - Belarus
* `BE` - Belgium
* `BZ` - Belize
* `BJ` - Benin
* `BM` - Bermuda
* `BT` - Bhutan
* `BO` - Bolivia
* `BQ` - Bonaire, Saint Eustatius and Saba 
* `BA` - Bosnia and Herzegovina
* `BW` - Botswana
* `BV` - Bouvet Island
* `BR` - Brazil
* `IO` - British Indian Ocean Territory
* `VG` - British Virgin Islands
* `BN` - Brunei
* `BG` - Bulgaria
* `BF` - Burkina Faso
* `BI` - Burundi
* `KH` - Cambodia
* `CM` - Cameroon
* `CA` - Canada
* `CV` - Cape Verde
* `KY` - Cayman Islands
* `CF` - Central African Republic
* `TD` - Chad
* `CL` - Chile
* `CN` - China
* `CX` - Christmas Island
* `CC` - Cocos Islands
* `CO` - Colombia
* `KM` - Comoros
* `CK` - Cook Islands
* `CR` - Costa Rica
* `HR` - Croatia
* `CU` - Cuba
* `CW` - Curacao
* `CY` - Cyprus
* `CZ` - Czech Republic
* `CD` - Democratic Republic of the Congo
* `DK` - Denmark
* `DJ` - Djibouti
* `DM` - Dominica
* `DO` - Dominican Republic
* `TL` - East Timor
* `EC` - Ecuador
* `EG` - Egypt
* `SV` - El Salvador
* `GQ` - Equatorial Guinea
* `ER` - Eritrea
* `EE` - Estonia
* `ET` - Ethiopia
* `FK` - Falkland Islands
* `FO` - Faroe Islands
* `FJ` - Fiji
* `FI` - Finland
* `FR` - France
* `GF` - French Guiana
* `PF` - French Polynesia
* `TF` - French Southern Territories
* `GA` - Gabon
* `GM` - Gambia
* `GE` - Georgia
* `DE` - Germany
* `GH` - Ghana
* `GI` - Gibraltar
* `GR` - Greece
* `GL` - Greenland
* `GD` - Grenada
* `GP` - Guadeloupe
* `GU` - Guam
* `GT` - Guatemala
* `GG` - Guernsey
* `GN` - Guinea
* `GW` - Guinea-Bissau
* `GY` - Guyana
* `HT` - Haiti
* `HM` - Heard Island and McDonald Islands
* `HN` - Honduras
* `HK` - Hong Kong
* `HU` - Hungary
* `IS` - Iceland
* `IN` - India
* `ID` - Indonesia
* `IR` - Iran
* `IQ` - Iraq
* `IE` - Ireland
* `IM` - Isle of Man
* `IL` - Israel
* `IT` - Italy
* `CI` - Ivory Coast
* `JM` - Jamaica
* `JP` - Japan
* `JE` - Jersey
* `JO` - Jordan
* `KZ` - Kazakhstan
* `KE` - Kenya
* `KI` - Kiribati
* `XK` - Kosovo
* `KW` - Kuwait
* `KG` - Kyrgyzstan
* `LA` - Laos
* `LV` - Latvia
* `LB` - Lebanon
* `LS` - Lesotho
* `LR` - Liberia
* `LY` - Libya
* `LI` - Liechtenstein
* `LT` - Lithuania
* `LU` - Luxembourg
* `MO` - Macao
* `MK` - Macedonia
* `MG` - Madagascar
* `MW` - Malawi
* `MY` - Malaysia
* `MV` - Maldives
* `ML` - Mali
* `MT` - Malta
* `MH` - Marshall Islands
* `MQ` - Martinique
* `MR` - Mauritania
* `MU` - Mauritius
* `YT` - Mayotte
* `MX` - Mexico
* `FM` - Micronesia
* `MD` - Moldova
* `MC` - Monaco
* `MN` - Mongolia
* `ME` - Montenegro
* `MS` - Montserrat
* `MA` - Morocco
* `MZ` - Mozambique
* `MM` - Myanmar
* `NA` - Namibia
* `NR` - Nauru
* `NP` - Nepal
* `NL` - Netherlands
* `NC` - New Caledonia
* `NZ` - New Zealand
* `NI` - Nicaragua
* `NE` - Niger
* `NG` - Nigeria
* `NU` - Niue
* `NF` - Norfolk Island
* `KP` - North Korea
* `MP` - Northern Mariana Islands
* `NO` - Norway
* `OM` - Oman
* `PK` - Pakistan
* `PW` - Palau
* `PS` - Palestinian Territory
* `PA` - Panama
* `PG` - Papua New Guinea
* `PY` - Paraguay
* `PE` - Peru
* `PH` - Philippines
* `PN` - Pitcairn
* `PL` - Poland
* `PT` - Portugal
* `PR` - Puerto Rico
* `QA` - Qatar
* `CG` - Republic of the Congo
* `RE` - Reunion
* `RO` - Romania
* `RU` - Russia
* `RW` - Rwanda
* `BL` - Saint Barthelemy
* `SH` - Saint Helena
* `KN` - Saint Kitts and Nevis
* `LC` - Saint Lucia
* `MF` - Saint Martin
* `PM` - Saint Pierre and Miquelon
* `VC` - Saint Vincent and the Grenadines
* `WS` - Samoa
* `SM` - San Marino
* `ST` - Sao Tome and Principe
* `SA` - Saudi Arabia
* `SN` - Senegal
* `RS` - Serbia
* `SC` - Seychelles
* `SL` - Sierra Leone
* `SG` - Singapore
* `SX` - Sint Maarten
* `SK` - Slovakia
* `SI` - Slovenia
* `SB` - Solomon Islands
* `SO` - Somalia
* `ZA` - South Africa
* `GS` - South Georgia and the South Sandwich Islands
* `KR` - South Korea
* `SS` - South Sudan
* `ES` - Spain
* `LK` - Sri Lanka
* `SD` - Sudan
* `SR` - Suriname
* `SJ` - Svalbard and Jan Mayen
* `SZ` - Swaziland
* `SE` - Sweden
* `CH` - Switzerland
* `SY` - Syria
* `TW` - Taiwan
* `TJ` - Tajikistan
* `TZ` - Tanzania
* `TH` - Thailand
* `TG` - Togo
* `TK` - Tokelau
* `TO` - Tonga
* `TT` - Trinidad and Tobago
* `TN` - Tunisia
* `TR` - Turkey
* `TM` - Turkmenistan
* `TC` - Turks and Caicos Islands
* `TV` - Tuvalu
* `VI` - U.S. Virgin Islands
* `UG` - Uganda
* `UA` - Ukraine
* `AE` - United Arab Emirates
* `GB` - United Kingdom
* `US` - United States
* `UM` - United States Minor Outlying Islands
* `UY` - Uruguay
* `UZ` - Uzbekistan
* `VU` - Vanuatu
* `VA` - Vatican
* `VE` - Venezuela
* `VN` - Vietnam
* `WF` - Wallis and Futuna
* `EH` - Western Sahara
* `YE` - Yemen
* `ZM` - Zambia
* `ZW` - Zimbabwe

# components.schemas.UserPrivate.properties.public_api_status.description
* `blocked` - blocked
* `no_access` - no access
* `requested` - requested
* `active` - granted

# components.schemas.UserPrivate.properties.currencies_with_active_orders.description
List of currencies where the user has at least one active order.

# components.schemas.UserPrivate.properties.is_wallet_user.description
True if the user signed up via wallet (Privy)

# components.schemas.UserPrivateAccountBalance.description
One double-entry account row: balance amount in a single currency.

# components.schemas.WagerAction.description
Serializer for WagerAction.
PERFORMANCE TIP: Use select_related('outcome', 'outcome__question', 'outcome__entity', 'wager__user', 'wager')
    on WagerAction queryset.

# components.schemas.WagerAction.properties.action.description
* `sale` - sale
* `partial_sale` - partial sale
* `purchase` - purchase
* `win` - win
* `lose` - lose
* `cancel` - cancel
* `reverse` - reverse
* `disable` - disable
* `sale_reverse` - sale reverse

# components.schemas.WagerAction.properties.profit.description
User's profit of this action compared with active purchases (latest purchases after any sell)

# components.schemas.WagerAction.properties.position.description
'Long' for bets in favor of the outcome; 'Short' for bets against the outcome

* `long` - long
* `short` - short

# components.schemas.WagerDetail.properties.status.description
* `lost` - lost
* `purchased` - purchased
* `sold` - sold
* `won` - won
* `cancelled` - cancelled
* `disabled` - disabled

# components.schemas.WagerDetail.properties.position.description
'Long' for bets in favor of the outcome; 'Short' for bets against the outcome

* `long` - long
* `short` - short

# components.schemas.WagerDetail.properties.active_purchases.description
purchases from last outcome bought in that question

# components.schemas.WagerDetail.properties.last_profit.description
Last profit when question is resolved or wager is sold

# components.schemas.WagerDetail.properties.last_action.description
serialized data about last WagerAction on this wager

# components.schemas.WagerDetail.properties.actions_count.description
number of actions triggered by user (purchases, sales)

# components.schemas.WagerDetail.properties.last_sell_amount.description
Last sale amount when wager is sold. This value will always be positive

# components.schemas.WagerDetail.properties.shares_in_canonical.description
consolidated shares in question's canonical currency

# components.schemas.WagerDetail.properties.purchases_amount.description
Cost basis of the open position, based on purchases and adjusted by partial sales.

# components.schemas.WagerList.properties.status.description
* `lost` - lost
* `purchased` - purchased
* `sold` - sold
* `won` - won
* `cancelled` - cancelled
* `disabled` - disabled

# components.schemas.WagerList.properties.position.description
'Long' for bets in favor of the outcome; 'Short' for bets against the outcome

* `long` - long
* `short` - short

# components.schemas.WagerList.properties.active_purchases.description
purchases from last outcome bought in that question

# components.schemas.WagerList.properties.last_profit.description
Last profit when question is resolved or wager is sold

# components.schemas.WagerList.properties.last_action.description
serialized data about last WagerAction on this wager

# components.schemas.WagerList.properties.actions_count.description
number of actions triggered by user (purchases, sales)

# components.schemas.WagerList.properties.last_sell_amount.description
Last sale amount when wager is sold. This value will always be positive

# components.schemas.WagerList.properties.shares_in_canonical.description
consolidated shares in question's canonical currency

# components.schemas.WagerList.properties.purchases_amount.description
Cost basis of the open position, based on purchases and adjusted by partial sales.

# components.securitySchemes.HMAC.description
HMAC Authentication requires three headers:

1. **Key** (header): Your PUBLIC KEY
2. **Timestamp** (header): Current timestamp in Unix format
3. **HMAC** (header): SHA-512 hexdigest hash of the request signed with your PRIVATE KEY

The HMAC signature is created from the full raw headers, POST, and GET data of your request, ordered alphabetically by its keys. For example, if making a request to `markets/?category=5`, you must generate a SHA-512 hash from: `category=5&Key={{your_public_key}}&Timestamp={{current_timestamp}}`

See the Authentication section in the API documentation for detailed examples.

# components.securitySchemes.HMAC.x-code-samples[].source
import hashlib
import hmac
import datetime
from collections import OrderedDict
from urllib.parse import urlencode

PRIVATE_KEY = "your_private_key"
PUBLIC_KEY = "your_public_key"

params = {
    "Key": PUBLIC_KEY,
    "Timestamp": int(datetime.datetime.utcnow().timestamp()),
    "category": 5
}

params_to_sign = OrderedDict(sorted(list(params.items())))
params_to_sign = urlencode(params_to_sign)
encoded_params = params_to_sign.encode("utf-8")
encoded_private_key = PRIVATE_KEY.encode("utf-8")
hmac_signature = hmac.new(encoded_private_key, encoded_params, hashlib.sha512).hexdigest()

headers = {
    "Key": PUBLIC_KEY,
    "Timestamp": str(params["Timestamp"]),
    "HMAC": hmac_signature
}

# tags[].description
### **Authentication**

Most Futuur API endpoints require authentication. Authentication is performed using your API key and is secured by a SHA-512 HMAC.

Each authenticated request must include the following headers:

- **Key**: Your **PUBLIC KEY**
- **Timestamp**: Current timestamp in Unix format
- **HMAC**: SHA-512 hexdigest hash of the request signed with your **PRIVATE KEY**

### **Getting Your Public and Private Keys**

You can generate an API key pair from your Futuur account settings:

1. Open **Account Settings**.
2. Go to the **API** section.
3. Click **Generate new Keys**.

Alternatively, authenticate with your Futuur account and call:

```http
POST /v2.0/api-keys/
```

The response contains:

- **public_key**: use this value in the `Key` header.
- **private_key**: use this value to sign requests and generate the `HMAC` header.

Store your **private_key** securely. When a new key pair is created, previous active keys are disabled.

### **How to Generate the HMAC**

The HMAC signature is created from the full raw headers, POST, and GET data of your request, sorted by key using a **case-sensitive (code-point) sort** — the same ordering Python's built-in `sorted()` produces.

> ⚠️ **The sort is case-sensitive, not alphabetical.** Uppercase letters (`A`–`Z`) sort before lowercase letters (`a`–`z`), so the capitalized `Key` and `Timestamp` fields come **before** lowercase parameters like `category`. Do not use a case-insensitive/locale-aware comparator (e.g. JavaScript's `localeCompare`) — it produces a different order and the server will reject the signature with `401 authentication_failed`.

For example, if making a request to `markets/?category=5`, generate a SHA-512 hash from:

```
Key={{your_public_key}}&Timestamp={{current_timestamp}}&category=5
```

### **Python Example**

```python
import hashlib
import hmac
import datetime
from collections import OrderedDict
from urllib.parse import urlencode

PRIVATE_KEY = 'your_private_key'
PUBLIC_KEY = 'your_public_key'

# Prepare parameters
params = {
    'Key': PUBLIC_KEY,
    'Timestamp': int(datetime.datetime.utcnow().timestamp()),
    'category': 5
}

# Sort and encode parameters
params_sorted = OrderedDict(sorted(params.items()))
params_encoded = urlencode(params_sorted).encode('utf-8')

# Generate HMAC signature
hmac_signature = hmac.new(
    PRIVATE_KEY.encode('utf-8'),
    params_encoded,
    hashlib.sha512
).hexdigest()

# Build headers
headers = {
    'Key': PUBLIC_KEY,
    'Timestamp': str(params['Timestamp']),
    'HMAC': hmac_signature
}
```

# tags[].description
Futuur uses Pusher to deliver real-time data to clients over WebSocket connections.

## Channels and events

This is the complete catalog of realtime channels and the events published on each.

### Channels


| Channel                  | Type    | Auth                            | Purpose                                                                                                   |
| ------------------------ | ------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `event`                  | public  | none                            | Global feed of event/market activity (prices, volumes, new markets, status, comments, live data)          |
| `event-{event_id}`       | public  | none                            | Per-event feed (order-book top-N, BBO, market status)                                                     |
| `private-user-{user_id}` | private | required (`POST /pusher/auth/`) | Per-user private messages (balance, deposits, order updates, positions)                                   |




### Events at a glance


| Event                  | Channel                            | Emitted when                                                         |
| ---------------------- | ---------------------------------- | -------------------------------------------------------------------- |
| `best-prices-change`   | `event`, `event-{event_id}`        | A wagerable market's best bid/ask changes                            |
| `price-change`         | `event`                            | An event's mid-price/volume state changes (order-book: mid only)     |
| `new-market`           | `event`                            | A new market (outcome) is added to an event                          |
| `live-data-update`     | `event`                            | An event's live data (status/resolution/live feed) changes           |
| `market-status-change` | `event`, `event-{event_id}`        | Event/market tradability changes (status, bet window, disabled)      |
| `new-comment`          | `event`                            | A user posts a comment on an event                                   |
| `order-book-update`    | `event-{event_id}`                 | A market's top-of-book (top-N bid/ask) changed                       |
| `balance-change`       | `private-user-{user_id}`           | The user's balance changes                                           |
| `deposit-success`      | `private-user-{user_id}`           | A deposit completes                                                  |
| `order-update`         | `private-user-{user_id}`           | An order status changes (create/fill/cancel/expire)                  |
| `position-update`      | `private-user-{user_id}`           | The user's inventory (shares) on a market changes (coalesced on fills)|

> `order-book-snapshot` is reserved in the backend event catalog but is **not** emitted (Pusher message size limits). Clients that need full depth beyond top-N should use the REST order-book API.




## Client setup (minimal)

Include the Pusher JS client (for other languages see [https://pusher.com/docs/channels/using_channels/client-api-overview/](https://pusher.com/docs/channels/using_channels/client-api-overview/)):

```html
<script src="https://js.pusher.com/8.4.0/pusher.min.js"></script>
```

Initialize Pusher and subscribe to a channel:

```html
<script>
    // Enable pusher logging - don't include this in production
    Pusher.logToConsole = true;

    var pusher = new Pusher("9011f7eac38e825792d5", {
        cluster: "us2",
    });

    var channel = pusher.subscribe("my-channel");
    channel.bind("my-event", function (data) {
        alert(JSON.stringify(data));
    });
</script>
```



## Channel authorization (for private channels)

Pusher only allows a connection to subscribe to a private channel when the client provides an
authorization token signed by your server. When subscribing, the Pusher client library sends a
`POST` request to your authorization endpoint with `socket_id` and `channel_name`.

API bots use Futuur's endpoint (`POST /v2.0/pusher/auth/`) and authenticate with HMAC headers
(`Key`, `Timestamp`, `HMAC`). See the **Authentication** section for key setup.

The string to sign is **every parameter** — the `Key` and `Timestamp` headers plus the
`channel_name` and `socket_id` body fields — URL-encoded and sorted by key, then hashed with
HMAC-SHA512 using your private key. For the example values the signed string is exactly:

```
Key={{your_public_key}}&Timestamp={{current_timestamp}}&channel_name={{channel_name}}&socket_id={{socket_id}}
```

> ⚠️ **The sort is case-sensitive (ASCII / code-point order), not locale/alphabetical.**
> Capitalized `Key` and `Timestamp` therefore sort **before** the lowercase `channel_name`
> and `socket_id`. This matches Python's built-in `sorted()`. A case-insensitive sort — such
> as JavaScript's `String.prototype.localeCompare` or any locale-aware collator — puts
> `channel_name` first and produces a signature the server rejects with
> `401 {"code": "authentication_failed"}`. Sign with a plain byte/code-point comparison
> (in JS: `(a, b) => (a < b ? -1 : a > b ? 1 : 0)`).

> 🔑 **Keep the private key server-side.** The signing (and this auth endpoint call) must run
> on your backend. Never ship the private key to a browser: expose your *own* auth route to
> the Pusher client (see `channelAuthorization.endpoint` below) and sign the request there.

Node.js example (based on [Pusher's authorization endpoint](https://pusher.com/docs/channels/server_api/authorizing-users/)):

```javascript
const crypto = require("crypto");

const API_PUBLIC_KEY = "your_public_key";
const API_PRIVATE_KEY = "your_private_key";
const CHANNEL_AUTH_ENDPOINT = "https://api.futuur.com/v2.0/pusher/auth/";

async function authorizeChannel(socketId, channelName) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const paramsToSign = {
    Key: API_PUBLIC_KEY,
    Timestamp: timestamp,
    channel_name: channelName,
    socket_id: socketId,
  };
  const rawToSign = new URLSearchParams(
    // Case-sensitive (code-point) sort — matches Python's sorted().
    // Do NOT use localeCompare here: it sorts case-insensitively and the
    // server will reject the signature with 401 authentication_failed.
    Object.entries(paramsToSign).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
  ).toString();
  const signature = crypto
    .createHmac("sha512", API_PRIVATE_KEY)
    .update(rawToSign)
    .digest("hex");

  const response = await fetch(CHANNEL_AUTH_ENDPOINT, {
    method: "POST",
    headers: {
      Key: API_PUBLIC_KEY,
      Timestamp: timestamp,
      HMAC: signature,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      socket_id: socketId,
      channel_name: channelName,
    }),
  });

  if (!response.ok) {
    throw new Error("Forbidden");
  }

  return response.json(); // { auth: "..." }
}
```

Point the Pusher client at Futuur's endpoint:

```javascript
const Pusher = require("pusher-js");

const pusher = new Pusher("9011f7eac38e825792d5", {
  cluster: "us2",
  channelAuthorization: {
    customHandler: ({ socketId, channelName }, callback) => {
      authorizeChannel(socketId, channelName)
        .then((authResponse) => callback(null, authResponse))
        .catch((err) => callback(err, null));
    },
  },
});

const channel = pusher.subscribe("private-user-1");
```



## Event channel (public)

Global public feed on channel `event`. Subscribe with `pusher.subscribe("event")`; no authentication required.

### best-prices-change

Emitted when the best bid/ask of a **wagerable** market changes (non-wagerable markets are skipped to stay within Pusher limits).

Published on both the global `event` channel and the per-event `event-{event_id}` channel (same payload), so market-maker bots can subscribe only to events they quote.

- **Data payload**:
  - `market_id`: int
  - `event_id`: int
  - `currency_mode`: string `play_money` or `real_money`
  - `best_ask`: (number or null)
  - `best_bid`: (number or null)
  - `spread`: `best_ask - best_bid` (number or null)

Example subscription:

```html
<script>
    var pusher = new Pusher("9011f7eac38e825792d5", { cluster: "us2" });
    var channel = pusher.subscribe("event");
    channel.bind("best-prices-change", function (data) {
        console.log("Best prices changed:", data);
    });
    // Or, for a single event:
    // pusher.subscribe("event-" + eventId).bind("best-prices-change", ...);
</script>
```



### price-change

Emitted when an event's display price/volume state changes. `currency_mode` controls the ordering of `markets` in the payload. Distinct from `best-prices-change` (BBO/touch only).

- **When**:
  - **Order book** — after `update_prices_by_order_book`, only if at least one market's mid-price (`markets[].price`) actually changed. BBO-only moves still emit `best-prices-change` but not this event.
  - **Legacy LMSR** — from `update_outcome_prices` when the event is open/wagerable.
- **Data payload**:


| Field                     | Type    | Description                                             |
| ------------------------- | ------- | ------------------------------------------------------- |
| `id`                      | number  | Event (question) ID                                     |
| `title`                   | string  | Event title                                             |
| `status`                  | string  | Event status code                                       |
| `currency_mode`           | string  | `'play_money'` or `'real_money'`                        |
| `is_wagerable`            | boolean | Whether the event currently accepts wagers              |
| `wagers_count_play_money` | number  | Wagers count (play money)                               |
| `wagers_count_real_money` | number  | Wagers count (real money)                               |
| `wagers_count_total`      | number  | `wagers_count_play_money + wagers_count_real_money`     |
| `volume_play_money`       | number  | null                                                    |
| `volume_real_money`       | number  | null                                                    |
| `markets`                 | array   | Visible markets, ordered by `currency_mode` (see below) |


Each element of `markets`:


| Field         | Type   | Description                                                                                                    |
| ------------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| `id`          | number | Market (outcome) ID                                                                                            |
| `title`       | string | Market title                                                                                                   |
| `status`      | string | Market status                                                                                                  |
| `price`       | object | **LONG** mid-price of the order book per currency: `{ "OOM": <play_money_price>, "USDC": <real_money_price> }` |
| `long_label`  | string | Label for the long (yes) side (e.g. `"Yes"`, `"Up"`)                                                           |
| `short_label` | string | Label for the short (no) side (e.g. `"No"`, `"Down"`)                                                          |
| `resolution`  | string | `'long'`, `'short'`, or null when unresolved                                                                   |




### new-market

Emitted when a new market is added to an event.

- **Data payload**:


| Field                        | Type   | Description                                           |
| ---------------------------- | ------ | ----------------------------------------------------- |
| `id`                         | number | Market (outcome) ID                                   |
| `title`                      | string | Market title                                          |
| `title_verbose`              | string | Verbose market title                                  |
| `event`                      | object | `{ id, title, status, category }` of the parent event |
| `status`                     | string | Market status                                         |
| `suggested_price_play_money` | number | null                                                  |
| `suggested_price_real_money` | number | null                                                  |
| `bet_start_date`             | string | null                                                  |
| `bet_end_date`               | string | null                                                  |




### live-data-update

Emitted when an event's live data changes (e.g. live sports feed updates, status/resolution changes).

- **Data payload**:


| Field              | Type    | Description                         |
| ------------------ | ------- | ----------------------------------- |
| `id`               | number  | Event (question) ID                 |
| `status`           | string  | Event status                        |
| `resolution`       | string  | null                                |
| `resolve_date`     | string  | null                                |
| `event_start_date` | string  | null                                |
| `event_end_date`   | string  | null                                |
| `event_extra_data` | object  | null                                |
| `is_live`          | boolean | Whether the event is currently live |




### market-status-change

Emitted when tradability-relevant fields change on an event or market. Published on both `event` and `event-{event_id}`. Quoting bots should start/stop liquidity on this signal (do not rely on BBO alone).

Triggered by:

- Event: `status`, `bet_start_date`, `bet_end_date`
- Market: `status`, `bet_start_date`, `bet_end_date`, `disabled`

- **Data payload**:


| Field              | Type           | Description                                                                     |
| ------------------ | -------------- | ------------------------------------------------------------------------------- |
| `event_id`         | number         | Event (question) ID                                                             |
| `market_id`        | number \| null | Market (outcome) ID when the change is market-scoped; `null` for event-level    |
| `status`           | string         | Current status of the event or market                                           |
| `previous_status`  | string \| null | Previous status when status itself changed                                      |
| `is_wagerable`     | boolean        | Whether the event/market currently accepts wagers                               |
| `bet_start_date`   | string \| null | Betting start (ISO 8601)                                                        |
| `bet_end_date`     | string \| null | Betting end (ISO 8601)                                                          |
| `disabled`         | boolean        | _(Optional)_ Present on market-scoped payloads                                  |
| `timestamp`        | string         | ISO 8601 timestamp                                                              |




### new-comment

Emitted when a user posts a comment on an event. Sent asynchronously with a short (~5s) delay so the comment is readable from replicas by the time clients refetch.

- **Data payload**:


| Field        | Type   | Description                                                          |
| ------------ | ------ | -------------------------------------------------------------------- |
| `id`         | number | Comment ID                                                           |
| `content`    | string | Comment text                                                         |
| `created_on` | string | ISO 8601 timestamp                                                   |
| `user`       | object | `{ id, username, picture, win_rate, wagers_count, net_earnings }`    |
| `question`   | object | `{ id, title, slug, status, resolve_date, bet_end_date, is_binary }` |




## Per-event channel (public)

Per-event channel keyed by the event (question) id. Channel name pattern: `event-{event_id}` (e.g. `event-1234`). Subscribe with `pusher.subscribe("event-" + eventId)`; no authentication required.

Also receives mirrored `best-prices-change` and `market-status-change` (see above).

### order-book-update

Emitted once per affected **market** when that market's order book changes (order created, matched, or canceled). Payload is the absolute **top-N** (default 5) bid/ask levels — enough for quoting around the touch without exceeding Pusher size limits.

Subscribe once per event; you will receive one `order-book-update` per market that changed (filter on `market_id` as needed).

Full-book `order-book-snapshot` pushes are **not** used. For depth beyond top-N, call the REST order-book API.

- **Data payload**:


| Field           | Type   | Description                                              |
| --------------- | ------ | -------------------------------------------------------- |
| `market_id`     | number | Market (outcome) ID                                      |
| `event_id`      | number | Event (question) ID                                      |
| `currency`      | string | Concrete currency code (e.g. `USDC`, `OOM`)              |
| `currency_mode` | string | `'play_money'` or `'real_money'`                         |
| `top_n`         | number | Number of levels per side included                       |
| `ask`           | array  | Top asks: `{ price, total_shares, total_amount }`        |
| `bid`           | array  | Top bids: `{ price, total_shares, total_amount }`        |
| `timestamp`     | string | ISO 8601 timestamp                                       |


Example:

```html
<script>
    var pusher = new Pusher("9011f7eac38e825792d5", { cluster: "us2" });
    var channel = pusher.subscribe("event-" + eventId);
    channel.bind("order-book-update", function (data) {
        // data.market_id, data.bid, data.ask — top of book for that market
        console.log("Book update:", data.market_id, data.bid, data.ask);
    });
</script>
```



## Private user channel (private)

Per-user private channel. Channel name pattern: `private-user-{user_id}`. Requires authorization via `POST /pusher/auth/` (see above); a user may only subscribe to their own channel. Subscribe with `pusher.subscribe("private-user-" + userId)`.

Get your `user_id` from the `id` field of an authenticated `GET /v2.0/me/` request (the key pair you sign with determines the user, so this is the only id you are allowed to subscribe to).

### balance-change

Emitted whenever the user's balance changes (wager, deposit, credit, withdrawal, promo code, currency exchange). Carries no amount — clients should refetch the balance.

- **Data payload**:


| Field       | Type   | Description                                            |
| ----------- | ------ | ------------------------------------------------------ |
| `timestamp` | number | Unix epoch seconds (float) when the change was emitted |




### deposit-success

Emitted when a deposit completes.

- **Data payload**:


| Field      | Type   | Description      |
| ---------- | ------ | ---------------- |
| `amount`   | number | Deposited amount |
| `currency` | string | Currency code    |




### order-update

Real-time order status change events for low-latency hedge and quoting reactions (<100ms from create/fill/cancel to push). Covers create acks and all status transitions so a client (e.g. the Liquid Loop or an external market maker) can keep its internal order model and hedge map in sync (no stale open orders). Sent synchronously.

- **When**:
  - Order created (`pending` while PROCESSING, or `open` when resting)
  - Status changes — filled, partial_filled, canceled, or expired
  - Transition to `open` (e.g. PROCESSING → OPEN after a match attempt)
- **Data payload**:


| Field                 | Type    | Description                                                  |
| --------------------- | ------- | ------------------------------------------------------------ |
| `order_id`            | number  | Order ID                                                     |
| `market_id`           | number  | Market (outcome) ID                                          |
| `event_id`            | number  | Event (question) ID (for hedge lookup)                       |
| `status`              | string  | `'pending'`                                                  |
| `previous_status`     | string  | null                                                         |
| `side`                | string  | `'bid'`                                                      |
| `position`            | string  | `'l'` (long)                                                 |
| `price`               | number  | null                                                         |
| `shares_requested`    | number  | Requested quantity                                           |
| `shares_filled`       | number  | Total quantity filled so far                                 |
| `shares_filled_delta` | number  | *(Optional)* Filled in this update (fill events only)        |
| `avg_fill_price`      | number  | null                                                         |
| `currency`            | string  | Currency code (e.g. USDC)                                    |
| `timestamp`           | string  | ISO 8601 with milliseconds                                   |
| `cancel_reason`       | string  | *(Optional)* `'user_requested'`                              |
| `expired_at`          | string  | *(Optional)* Expiry timestamp (ISO 8601)                     |
| `is_system_initiated` | boolean | *(Optional)* `true` if the cancellation was system-initiated |




### Example: subscribe to private order updates

```html
<script>
    var pusher = new Pusher("9011f7eac38e825792d5", { cluster: "us2" });
    var privateChannel = pusher.subscribe("private-user-" + userId);
    privateChannel.bind("order-update", function (data) {
        console.log("Order update:", data);
        if (data.status === "pending" || data.status === "open") {
            // Track resting / in-flight order in local open-orders model
        }
        if (data.status === "filled" || data.status === "partial_filled") {
            // Trigger hedge (e.g. Liquid Loop)
        }
        if (data.status === "canceled" || data.status === "expired") {
            // Remove from local open-orders model and hedge map
        }
    });
</script>
```

### position-update

Emitted when a user's inventory (wager shares) on a market changes. Useful for market-maker inventory reconcile after fills without rebuilding from REST on every tick. Payload always reflects **current consolidated inventory** for that market/position/currency. Skipped for internal AMM users.

- **When**:
  - **Order-book fills** — coalesced. A bet that walks N book levels (N `MatchOrder`s) does **not** emit N messages. After the settlement batch (`process_orders`), the server emits **one** `position-update` per touched wager (taker and each non-AMM maker) with the final share count. A single-match recovery path emits once after that match settles.
  - **Legacy LMSR** purchase / sell / resolve paths — once when aggregated shares change (those paths already run a single aggregation).
- **Not** emitted on every intermediate fill slice. Clients should treat each message as the latest absolute inventory, not as a per-fill delta.
- **Data payload**:


| Field              | Type           | Description                                      |
| ------------------ | -------------- | ------------------------------------------------ |
| `market_id`        | number         | Market (outcome) ID                              |
| `event_id`         | number         | Event (question) ID                              |
| `position`         | string         | `'l'` (long) \| `'s'` (short)                    |
| `currency`         | string         | Currency code                                    |
| `shares`           | number         | Current consolidated shares                      |
| `purchases_amount` | number \| null | Cost basis of active purchases                   |
| `avg_price`        | number \| null | `purchases_amount / shares` when shares > 0      |
| `timestamp`        | string         | ISO 8601 timestamp                               |


Authentication: see **Channel authorization** above.