# API Public reference for integrating Pascal's REST and WebSocket APIs. All write requests are signed. Read requests are unauthenticated. The Write and Read APIs use distinct base URLs; see [Base URLs](#base-urls). ## General Information ### Base URLs Use the production URLs for live trading: | Service | URL | |---|---| | Web | `https://app.pascal.trade` | | Write API | `https://trade.pascal.trade` | | Order Entry WebSocket | `wss://trade.pascal.trade/ws` | | REST Read API | `https://data.pascal.trade` | | Market Data WebSocket | `wss://data.pascal.trade/ws` | ### Geography The Pascal matching engine is located in AWS ap-northeast-1. ### Rate Limits Rate limits may change. Clients must handle HTTP 429 responses. REST order endpoints accept up to 100 requests per second. You can batch up to 50 orders per request. Some read endpoints have their own lower limits. A client that exceeds one receives HTTP 429 with error code `RATE_LIMITED`. ### Authentication and Request Authorization #### Signing Keys Each Solana wallet address owns one Pascal account. Pascal separates account ownership from trading authorization, so certain operations (moving funds, approving trading keys) require wallet signatures, while order placement and cancellation are signed by separate trading keys. This allows accounts to use higher security custody, like hardware or MPC wallets, without prohibiting the use of hot keys for trading. | Key | Use | Used by | |---|---|---| | Wallet key | Custody, withdrawals, and trading-key lifecycle | Create/revoke trading keys, withdraw collateral, register deposit addresses | | Trading key | Order placement and cancellation | Place and cancel orders | In every signed request, `owner` is the wallet public key, also used as the Solana address, and `signer` is the key that produced the Ed25519 signature. Wallet-signed requests require `signer == owner`. #### Request Authorization Every signed request carries a `RequestAuth` object with replay and staleness protection fields `client_ts_ms` and `recv_window_ms`: ##### RequestAuth Request authentication metadata used to validate client requests. | Field | Type | Description | |---|---|---| | `client_ts_ms` | u64 string | Client timestamp in milliseconds since the Unix epoch. Used with `recv_window_ms` for replay and staleness checks. | | `recv_window_ms` | u32 string | Maximum age of the request in milliseconds. The server accepts the request when `server_time_ms - client_ts_ms <= recv_window_ms`. | | `owner` | base58 string | Account owner's wallet public key (Solana address). | | `signer` | base58 string | Public key that signed this request. Wallet-signed requests require `signer == owner`; trading-key requests use a registered trading key. | | `signature` | base58 string | Ed25519 signature over the signed payload bytes for this request. | `recv_window_ms` may be at most 60,000ms (1 minute). `client_ts_ms` also decides whether a [mass cancel](#mass-cancel-orders) covers an order. See [Request Signing](request-signing.txt#request-signing) for constructing the exact payloads to sign over. > [info!] The easiest way to start signing requests is with [github.com/pascal-research-inc/pascal-python-quickstart](https://github.com/Pascal-Research-Inc/pascal-python-quickstart#quickstart) ### Generate A Trading Key In The Browser For most users the easiest way to get a trading key is through the Pascal web app. The web app handles wallet prompts, permit construction, and key generation. 1. Open the Pascal web app, for example [https://app.pascal.trade](https://app.pascal.trade), and log in with the wallet of your choice. To keep wallet keys off the computer, use a hardware wallet or MPC solution. 2. Click your account image in the upper right, then **Trading Keys > Create Key**. 3. Give the key a name and save the private key somewhere secure. Alternatively, to generate and assign a trading key programmatically, call [Create A Trading Key](#create-a-trading-key) directly with an Ed25519 public key and a wallet-signed `create_trading_key` permit. ### Response Envelopes All REST API responses are wrapped in: | Field | Type | Description | |---|---|---| | `status` | string enum | `success` or `error` | | `data` | object | Endpoint response payload on `success`, otherwise error object. | | `seq` | u64 string | (Optional) Matching engine sequence number. Not present if the request is rejected before reaching the matching engine, for example due to invalid request data. | | `state_time_ms` | u64 string | (Optional) Matching engine timestamp as of the last matching round. Not present if the request is rejected before reaching the matching engine, for example due to invalid request data. | | `server_time_ms` | u64 string | API server timestamp when this result was produced. Requests in a batch request can resolve at different times, so items in one response may carry different timestamps. | | `server_receive_time_ms` | u64 string | (Optional) API server timestamp when the HTTP request is received. Present on write endpoint responses, including rejections and timeouts. Not present on read endpoint responses or WebSocket messages. | Request-validation and rate-limit error responses may omit `server_time_ms`. Error data is an object with fields: | Field | Type | Description | |---|---|---| | `code` | [ApiErrorCode](#apierrorcode) | Stable, machine-readable error code. | | `message` | string | Human-readable error message. May evolve over time. For debugging and display only. | Batch endpoints normally return an array of envelopes, one per submitted request, in the same order as the request batch. Request-level errors, including rate limits, return one error envelope. Batch endpoints return HTTP 200 if *any* request in the batch succeeds. Inspect each item's `status` to determine which requests succeeded or failed. Other endpoints return 200 only on `{"status": "success"}`. **Success envelope** ```json { "status": "success", "data": { "server_time_ms": "1731536000123" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` **Error envelope** ```json { "status": "error", "data": { "code": "INVALID_REQUEST", "message": "count_back must be positive" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` **Batch response envelope** ```json [ { "status": "success", "data": { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" }, { "status": "error", "data": { "code": "ORDER_NOT_FOUND", "message": "client_order_id 43 is not an open order" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] ``` ### Pagination Paginated endpoints accept an optional `limit` and at most one starting point: `before_cursor` or, where supported, `at_or_before_seq`. - `at_or_before_seq` allows starting pagination at a sequence number. Continue paginating using the cursor. - `before_cursor` is the `next_cursor` from the previous page. Results continue from there in the endpoint's order. - `limit` uses min 1, default 100, max 500 unless the endpoint says otherwise. Treat cursor values as opaque. The internal format may change without notice; clients should only round-trip `next_cursor` back into `before_cursor`. Paginated responses wrap the page in the standard [response envelope](#response-envelopes). Its `data` object contains: | Field | Type | Description | |---|---|---| | `items` | array | Result page items. The item schema is shown under each paginated endpoint. | | `next_cursor` | string | (Optional) Cursor for the next page of results. Omitted on the last page. | The envelope [`seq`](#sequence-numbers) and `state_time_ms` indicate the freshness of the historical data. For paginated requests, the pages together cover items up to the first page's `seq`. If the requested pagination boundary is beyond indexed history, the API returns HTTP 503 with `code: "HISTORY_NOT_READY"`. Retry the same boundary after a delay. See the historical [Trades](#trades) query for an example. ### JSON Conventions | Shape | Encoding | |---|---| | Decimal prices, USD amounts, rates, and fees | String with 6 fractional digits unless the field description says otherwise. Price fields stay within 0.000000 to 1.000000. | | Contract sizes, sequence numbers, ids, timestamps, and cursors | Serialized as strings | | Timestamps | Unix milliseconds. | | Solana addresses, public keys, and Ed25519 signatures | Base58 strings. | | Market symbols | Uppercase string identifiers. | ### Sequence Numbers Responses to API calls that read or update exchange state include a `seq` number. The `seq` is a global counter that increments on every exchange state change. It only increases. **Note: it is not a per-connection message counter.** You can compare `seq` values across REST, WebSocket, and historical APIs to tell which state is newer. For example: - Order responses include the `seq` at which the order was placed. - REST account and order book snapshots include the snapshot's `seq` in the response envelope. - WebSocket messages include the `seq` of the state that produced the update. - Historical data queries include the highest `seq` of indexed state. For example, the historical trades response is guaranteed to include every trade where `trade.seq <= envelope.seq`. For example, after placing an order, suppose the order response has `seq = o`. Then a WebSocket book update arrives with `seq = w`: - If `w < o`, the update is from state before your order was placed. - If `w = o`, the update is from the same state transition as your order. - If `w > o`, the update is from state after your order was placed. Individual WebSocket channels can carry non-contiguous `seq` values because multiple state changes occur in each [matching round](general.txt#matching-rounds-and-priority) and not every global state change affects each specific WebSocket channel. ## Write Endpoints ### Place And Replace Orders `POST /api/v1/orders` Places or amends one or more orders. Each order is independently signed and independently succeeds or fails. The maximum batch size is 50. An account may hold at most 50 resting orders in a single market. The limit is per account, not per trading key, and it is counted per market, so resting orders in other markets do not count against it. Only orders that come to rest consume a slot: an order that fills immediately never counts, and immediate-or-cancel orders are never subject to the limit. At the limit, an order that would rest is rejected with `RESTING_ORDER_LIMIT_EXCEEDED`. An order that partly fills keeps its fills, and its unfilled remainder is canceled with update reason `RESTING_ORDER_LIMIT` rather than resting. Because each order in a batch is evaluated on its own, a batch sent near the limit can have some orders accepted and later ones rejected. Cancel a resting order to free a slot, or send an immediate-or-cancel order to trade without resting. **Request Body** Request bodies are JSON arrays. Every element is a JSON object: #### PlaceOrderRequest Client sends this request to the API to place an order. | Field | Type | Description | |---|---|---| | `client_order_id` | u64 string | Client-assigned order identifier. Must be unique across open orders for the same account. Must be `< 2^64 - 1`. | | `replace_client_order_id` | u64 string | (Optional) If present, the order with `replace_client_order_id` will be cancelled and replaced atomically with this new order. If the replacement only reduces remaining size, the order preserves the existing queue position. | | `symbol` | string ([Symbol](#market-symbols)) | Symbol for the market to trade. | | `side` | string enum: `"BID"`, `"ASK"` | Whether to buy (`BID`) or sell (`ASK`). | | `price` | decimal string, 6 d.p. | Limit price in dollars. | | `size` | u64 string | Order size in contracts. | | `tif` | [TimeInForce](#timeinforce) | (Optional) The order's [TimeInForce](#timeinforce) policy. Defaults to `GTC`. | | `post_only` | boolean | (Optional) If true, the order can only match as a maker. If it would cross immediately, the order is rejected. | | `reduce_only` | boolean | (Optional) If true, the order can only reduce exposure and never increase it. | | `allow_missing_replace` | boolean | (Optional) If true, the order is placed even if `replace_client_order_id` is not found. Requires `replace_client_order_id`. Incompatible with `replace_with_open_size`. | | `replace_with_open_size` | boolean | (Optional) If true, the replacement does not alter the open order's size. Set the request `size` equal to the open size you think the order has and sign over it. When this request lands, the matching engine will inspect the size remaining for `replace_client_order_id`, and set the new order's size equal to that. Incompatible with `allow_missing_replace`. | | `expires_ts_ms` | u64 string | (Optional) Expiration timestamp, required for `GTT` orders and forbidden otherwise. | | `builder_info` | [BuilderInfo](#builderinfo) | (Optional) Builder attribution and fee settings, signed into the order permit. A present value must name a registered builder other than the order's own account; omitting the field is the only no-builder form. Rejected with `BUILDER_INVALID` for an unknown or self-named builder, `BUILDER_FEE_RATE_INVALID` for a fee rate above `BUILDER_FEE_RATE_CAP`, and `BUILDER_COLLATERAL_BELOW_MINIMUM` when the builder does not meet the minimum free collateral. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | `tif` (time in force) values: #### TimeInForce Time in force for an order. | Value | Description | |---|---| | `"GTC"` | Good 'til canceled: the order rests on the book until it is fully filled or explicitly canceled. | | `"GTT"` | Good 'til time: like GTC, but the order is automatically canceled once `expires_ts_ms` passes. | | `"IOC"` | Immediate or cancel: fills against resting orders immediately; any unfilled remainder is canceled instead of resting on the book. | `builder_info`, when present, is a JSON object (see [Builder Codes](general.txt#builder-codes)): #### BuilderInfo Builder public key and fee rate for one order, signed into the order permit. | Field | Type | Description | |---|---|---| | `builder_pubkey` | base58 string | Registered builder account credited for this order. | | `builder_fee_rate` | decimal string, 6 d.p. | Builder fee rate. Each fill pays the builder `size × price × (1 − price) × builder_fee_rate`, rounded toward zero. | **JSON Response Payload** Batch responses are JSON arrays of responses. On success, each response's `data` object has these fields: | Field | Type | Description | |---|---|---| | `order` | [OrderMsg](#ordermsg) | The state of the order after placement, including any fills that occurred immediately. | | `fills` | array of [FillMsg](#fillmsg) | Fills that occurred during the placement of the order. | | `update_reason` | string enum: `"USER_CANCEL"`, `"MASS_CANCELLED"`, `"EXPIRED"`, `"UNDERCOLLATERALIZED"`, `"SELF_TRADE"`, `"IOC_REMAINDER_CANCELED"`, `"MARKET_RESOLVED"`, `"POSITION_OVERFLOW"`, `"TRADING_KEY_EXPIRED"`, `"TRADING_KEY_REVOKED"`, `"TRADING_KEY_REPLACED"`, `"NO_REDUCIBLE_POSITION"`, `"REDUCE_ONLY_RESIZE"`, `"RESTING_ORDER_LIMIT"`, `"REPLACED"` | (Optional) Classification of an order update during placement. The order's quantities describe its resulting state. | `order` object schema: #### OrderMsg Order message for API responses. | Field | Type | Description | |---|---|---| | `id` | u64 string | The order's unique identifier, assigned by the exchange. | | `client_order_id` | u64 string | Client ID for the order. | | `replace_client_order_id` | u64 string | (Optional) Present when this order was placed as a replacement. | | `symbol` | string ([Symbol](#market-symbols)) | The symbol of the market being traded. | | `side` | string enum: `"BID"`, `"ASK"` | The side of the order. | | `price` | decimal string, 6 d.p. | The limit price of the order in dollars. | | `size_original` | u64 string | The order size in contracts at placement time. | | `size_remaining` | u64 string | Remaining open quantity, matching FIX `LeavesQty` semantics. The order is closed when this is zero, whether it filled completely, was canceled, expired, or was resolved by the matching engine. Note that an order's size can be reduced by the matching engine due to `reduce_only` or self trade prevention. Use `size_filled` to determine filled size, not `size_remaining`. | | `size_filled` | u64 string | The cumulative size of the order that has been filled. | | `notional_filled` | decimal string, 6 d.p. | Sum of `fill_price * fill_size` for all fills of this order, in USD. Divide by `size_filled` when nonzero to get the average fill price. | | `tif` | [TimeInForce](#timeinforce) | The order's [TimeInForce](#timeinforce). | | `type` | string enum: `"LIMIT"`, `"MARKET"` | Order display type. `LIMIT` is a standard limit order. `MARKET` requires `IOC` and executes only at its specified limit price or better. | | `post_only` | boolean | If true, the order is only allowed to fill as a maker. | | `reduce_only` | boolean | If true, the order is only allowed to reduce existing exposure and never increase it. | | `expires_ts_ms` | u64 string | (Optional) The expiration timestamp of the order in milliseconds since the Unix epoch, omitted if the order is not `GTT`. | | `builder_pubkey` | base58 string | (Optional) Builder named by this order, omitted when the order has no builder. | | `builder_fee_rate` | decimal string, 6 d.p. | (Optional) Builder fee rate signed into this order, omitted when the order has no builder. | | `seq` | u64 string | The sequence number when this order was placed. | | `update_seq` | u64 string | The sequence number when this order last changed. | | `place_ts_ms` | u64 string | The timestamp the order was placed at. | | `update_ts_ms` | u64 string | The timestamp when this order last changed. | Every element in `fills` is a JSON object: #### FillMsg Fill message for API responses. | Field | Type | Description | |---|---|---| | `trade_id` | u64 string | Unique ID for this trade. | | `symbol` | string ([Symbol](#market-symbols)) | Symbol for the market being traded. | | `side` | string enum: `"BID"`, `"ASK"` | Side of the order that was filled. | | `reduce_only` | boolean | (Optional) The filled order's reduce-only flag, independent of whether the fill reduced the position. Omitted for archival data. | | `fill_price` | decimal string, 6 d.p. | Execution price in dollars. | | `fill_size` | u64 string | Filled quantity in contracts. | | `liquidity` | string enum: `"TAKER"`, `"MAKER"` | Whether this fill removed liquidity (`TAKER`) or provided it (`MAKER`). | | `counterparty` | base58 string | Owner wallet of the account on the other side of this fill's trade. | | `order_id` | u64 string | Exchange ID of the order that was filled. | | `client_order_id` | u64 string | Client ID of the order that was filled. | | `fee_usd` | signed decimal string, 6 d.p. | Venue fee charged for this fill, excluding builder fees. Positive values are fees paid; negative values are rebates. | | `collateral_change_usd` | signed decimal string, 6 d.p. | This fill side's collateral change: position collateral change minus the side's fees. Credits the account receives from the same trade (builder fees received or a referral reward) are separate and not included. | | `position_size_prev` | i64 string | Signed position size in this market immediately before the fill. | | `realized_pnl_usd` | signed decimal string, 6 d.p. | Realized PnL attributed to this fill, excluding fees. | | `position_open_seq` | u64 string | (Optional) Opening sequence of the position instance associated with this fill. On a flip, this identifies the position closed by the fill rather than the residual position it opens. | | `position_open_ts_ms` | u64 string | (Optional) Opening timestamp, in milliseconds since the Unix epoch, of the position instance associated with this fill. On a flip, this identifies the position closed by the fill rather than the residual position it opens. | | `builder_pubkey` | base58 string | (Optional) Builder named by the filled order, omitted when the order has no builder. | | `builder_fee_paid_usd` | decimal string, 6 d.p. | (Optional) Builder fee paid to the builder for this fill, included in `collateral_change_usd`; omitted when the order has no builder. | | `seq` | u64 string | Matching engine sequence number when this fill occurred. | | `trade_ts_ms` | u64 string | Timestamp when this fill occurred, in milliseconds since the Unix epoch. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/orders \ -H 'Content-Type: application/json' \ --data @- <<'EOF' [ { "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size": "10", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "2rUctQ9bFbuQaUxbXDGRtGm4LMj3GdbYWAW6pjrznAsLvpdH8Eqyx3LgsFx3YzV9cvMfw1ND5T5nChUQZZJ7jEad" } } ] EOF ``` **Example response** ```json [ { "status": "success", "data": { "order": { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "7", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "fills": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "reduce_only": false, "fill_price": "0.550000", "fill_size": "3", "liquidity": "TAKER", "counterparty": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "order_id": "987654321", "client_order_id": "42", "fee_usd": "0.000742", "collateral_change_usd": "-1.650742", "position_size_prev": "0", "realized_pnl_usd": "0.000000", "seq": "67891", "trade_ts_ms": "1731536000050" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] ``` ### Cancel Orders `POST /api/v1/cancels` Cancels one or more orders by client order id or exchange order id. Each cancel is independently signed and independently succeeds or fails. The maximum batch size is 50. Canceling by client order id and canceling by exchange order id have equivalent performance. **Request Body** Request bodies are JSON arrays. Every element is a JSON object: #### CancelOrderRequest Client sends this request to the API to cancel an order. | Field | Type | Description | |---|---|---| | `client_order_id` | u64 string | (One of) Client ID of the order to cancel. | | `order_id` | u64 string | (One of) Exchange ID of the order to cancel. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | **JSON Response Payload** Batch responses are JSON arrays of write envelopes. On success, each envelope's `data` object is an [OrderMsg](#ordermsg) with the cancelled order's final state. **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/cancels \ -H 'Content-Type: application/json' \ --data @- <<'EOF' [ { "client_order_id": "42", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "591YziUjZzro24mamxgRrSDdYD71NpsDBmU4UGSyrBYbFiWzYaLjR9he1P5DP8djMKMJzHTsZzwRAagvLqgtiXnc" } }, { "client_order_id": "43", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "3wFZKJWKA92W6iPszoKQccQmrVEfd6hU68HY6mqWYKEH8yFKQEFRJs1tKmrGDy1PMd3wWwcdcYSVoTgrW5KSnP3f" } } ] EOF ``` **Example response** ```json [ { "status": "success", "data": { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" }, { "status": "success", "data": { "id": "987654322", "client_order_id": "43", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] ``` ### Mass Cancel Orders `POST /api/v1/mass-cancels` Cancels every resting order an account holds in one market with a single signed request. The maximum batch size is 10. Scope is per account, not per trading key: a mass cancel signed by any authorized trading key cancels the account's resting orders in that market whichever key placed them. Within a [matching round](general.txt#matching-rounds-and-priority), a mass cancel also covers any order in the same market whose `client_ts_ms` is less than or equal to the mass cancel's, including one signed in the same millisecond. Those orders are still placed, so a marketable one can still fill, and whatever is left resting is then cancelled with `MASS_CANCELLED`. An order with a greater `client_ts_ms` is placed normally. Orders from earlier rounds are already resting, so the mass cancel cancels them as usual. **Request Body** Request bodies are JSON arrays. Every element is a JSON object: #### MassCancelRequest Client sends this request to the API to cancel every resting order it holds in one market. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Market whose resting orders are cancelled. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | **JSON Response Payload** Batch responses are JSON arrays of write envelopes. On success, each envelope's `data` object contains an `orders` array of [OrderMsg](#ordermsg) values, one per order that was resting when the mass cancel ran. An order from the same round that the mass cancel covers is reported on its own place response instead, with an `update_reason` of `MASS_CANCELLED`. **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/mass-cancels \ -H 'Content-Type: application/json' \ --data @- <<'EOF' [ { "symbol": "SIM_EVENT_1.MARKET_1", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "oLmkQxx9xLPK4QJnFhZUFEker5k5pbLregsYPgRrhrd9sEBYDuCtitUtFSVfhiJpTFHWAdmGk6zKDLpPtA3iegS" } } ] EOF ``` **Example response** ```json [ { "status": "success", "data": { "orders": [ { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, { "id": "987654322", "client_order_id": "43", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] ``` ### Create A Trading Key `POST /api/v1/trading-keys` Authorizes a trading key for an account. This request is wallet-signed. Trading key names may be at most 16 bytes long, and their expiration timestamps may be no more than 31,536,000,000ms (365 days) in the future. An account may store at most 10 unrevoked trading keys at a time. Expired keys count against this limit until revoked. **Request Body** | Field | Type | Description | |---|---|---| | `trading_key` | base58 string | Ed25519 public key to authorize for trading on this account. | | `name` | string | Human-readable label for this trading key. **This name is public onchain.** | | `expiration_ts_ms` | u64 string | Timestamp when this trading key expires, in milliseconds since the Unix epoch. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | **JSON Response Payload** On success, the response envelope's `data` object has these fields: #### TradingKeyMsg Trading key entry in API responses. | Field | Type | Description | |---|---|---| | `trading_key` | base58 string | Ed25519 public key authorized to sign trading requests for this account. | | `name` | string | Human-readable label for this trading key. **This name is public onchain.** | | `expiration_ts_ms` | u64 string | Timestamp when this trading key expires, in milliseconds since the Unix epoch. | | `open_order_count` | u64 string | Number of this account's currently open orders placed using this trading key. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/trading-keys \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "name": "api-doc-key", "expiration_ts_ms": "1732140800000", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signature": "kyc2KdqPTeEWqoK3pXR2rgPUiK4zXg9QdQffPUWk8eALpFy7uaHBEsASt7RQyVzDeubHVsPqQAHXrt8m8KZ78Ep" } } EOF ``` **Example response** ```json { "status": "success", "data": { "trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "name": "api-doc-key", "expiration_ts_ms": "1732140800000", "open_order_count": "0" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Revoke A Trading Key `POST /api/v1/trading-key-revocations` Revokes a previously authorized trading key. This request is wallet-signed. **Request Body** | Field | Type | Description | |---|---|---| | `trading_key` | base58 string | Ed25519 public key of the trading key to revoke. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | **JSON Response Payload** On success, the response envelope's `data` object has these fields: | Field | Type | Description | |---|---|---| | `trading_key` | base58 string | Ed25519 public key of the revoked trading key. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/trading-key-revocations \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signature": "3muKct6upyRNAr1CPdYcxQGCojpuB4gaEnWvbNwEBJ6rrKaThRZrKY56rpYbFEsGUZqJ4iCTJqYrPuxfGYVzXQX3" } } EOF ``` **Example response** ```json { "status": "success", "data": { "trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Withdraw Collateral `POST /api/v1/withdrawals` Requests withdrawal of collateral to a destination token account. This request is wallet-signed. Clients do not send a fee field. `amount` is the gross amount debited from the account; the exchange sends `amount` minus the current signed withdrawal fee to `destination_token_account`. The current signed withdrawal fee is $0.50. **Destination address** Derive the `destination_token_account` [associated token account (ATA)](https://solana.com/docs/tokens/basics/create-token-account#what-is-an-associated-token-account) with the standard Solana formula: PDA seeds `[wallet, spl_token_program_id, usd_token_mint]` under the Associated Token program. **Request Body** | Field | Type | Description | |---|---|---| | `amount` | decimal string, 6 d.p. | Amount of collateral to withdraw in USD. The exchange debits this amount from the account and sends `amount` minus the withdrawal fee to the destination. | | `destination_authority` | base58 string | Solana wallet address that will receive the withdrawal. This is the address the user enters in a "withdraw to" field, not a token account address. | | `destination_token_account` | base58 string | USD associated token account (ATA) for `destination_authority`. Must equal the ATA derived from `destination_authority` and the deployment's USD token mint. Clients derive this locally before signing because both fields are included in the signed withdrawal permit. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | **JSON Response Payload** On success, the response envelope's `data` object has these fields: | Field | Type | Description | |---|---|---| | `owner` | base58 string | Account owner's wallet public key (Solana address). | | `amount` | decimal string, 6 d.p. | Amount of collateral withdrawn in USD. | | `destination_authority` | base58 string | Solana wallet address that received the withdrawal. | | `destination_token_account` | base58 string | USD associated token account (ATA) for `destination_authority` that received the withdrawal. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/withdrawals \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "amount": "25.000000", "destination_authority": "29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2", "destination_token_account": "3JF3sEqM796hk5WFqA6EtmEwJQ9quALszsfJyvXNQKy3", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signature": "61YYUBk6BTXkfrksMbqDLUNxwo6jhMPqdP7fNnWjNF2FYvMDujTsaUxfCiRxXX72jxy4oLtt6AtLodCvjDi4XpDn" } } EOF ``` **Example response** ```json { "status": "success", "data": { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "amount": "25.000000", "destination_authority": "29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2", "destination_token_account": "3JF3sEqM796hk5WFqA6EtmEwJQ9quALszsfJyvXNQKy3" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Register A Deposit Address `POST /api/v1/deposit-addresses` Registers the account's deposit address for balance scanning. This request is wallet-signed. A new account may include a referrer's `referral_code` to credit the referral. A registration carrying a Referral Code succeeds only when attribution is created. If the code is unknown, revoked, self-referring, or otherwise cannot resolve, the request fails with `REFERRAL_CODE_INVALID`; the account remains unregistered and may retry with a corrected code or without one. The Reward Owner can observe a successfully created Referral through their [Referrals](#referrals) list. > [info!] > Pascal uses USDC as its sole collateral token. **Only send USDC to your deposit address.** **Request Body** | Field | Type | Description | |---|---|---| | `owner` | base58 string | Account owner's wallet public key (Solana address). | | `referral_code` | string | (Optional) Referral code to credit for the referral, exactly as shared by the referrer. When supplied, registration succeeds only if referral attribution is created. An unknown, revoked, self-referring, or otherwise invalid code rejects registration with `REFERRAL_CODE_INVALID`; the account remains unregistered and can retry with a corrected code or without one. | | `auth` | [RequestAuth](#requestauth) | Signed request authentication metadata. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `owner` | base58 string | The account owner. | | `deposit_address` | base58 string | (Optional) The deposit address PDA corresponding to the account owner. Present only after the account has registered a deposit address, and immutable once set. | | `registered` | boolean | Whether the deposit address is registered. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/deposit-addresses \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "referral_code": "k7wq2m4x", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signature": "2GVZuf1birCpfr6DKbu1ALSxRTpVnzFAa6oYqnEsnDUB2TFqeYTsKEZrCxNzATyCZbEaLW7koZJPRh9euhYiLtm4" } } EOF ``` **Example response** ```json { "status": "success", "data": { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "deposit_address": "DtzBRq9hAREJZKj1fuHsgrWrtaeXmbYQdXsmMrd54arG", "registered": true }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Generate A Referral Code `POST /api/v1/referral-codes` Creates the owner's reusable referral code. This request is signed by an active trading key. Each owner can generate one code, and generation requires a deposited account and sufficient weighted volume, unless the owner already has an active code. Use [Referrals](#referrals) for the owner's current code and the `can_generate_referral_code` flag. A new user referred with this code submits it as `referral_code` in [Register A Deposit Address](#register-a-deposit-address), which creates a Referral under the [Referral Program](general.txt#referral-program). **Request Body** | Field | Type | Description | |---|---|---| | `auth` | [RequestAuth](#requestauth) | Trading-key-signed request authentication metadata. | **JSON Response Payload** On success, the response envelope's `data` object has these fields: | Field | Type | Description | |---|---|---| | `generated_referral_code` | string | The owner's reusable referral code. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/referral-codes \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "4UpZqQfCNDbj3NEnkkRUQsW5USTTcv7JekvPWyHsEC2ifcc3R8PwEVd5xVWZuSNacyNHrZg29ZqPeUME3GWGpnCN" } } EOF ``` **Example response** ```json { "status": "success", "data": { "generated_referral_code": "k7wq2m4x" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Claim A Custom Referral Code `POST /api/v1/referral-codes/custom` Claims the owner's one-time Custom Referral Code. This request is signed by an active trading key, requires a deposited account and sufficient weighted volume (unless the owner already has an active code), and succeeds at most once per owner. Use [Referrals](#referrals) for the owner's current code and `can_claim_custom_referral_code`. The code must contain 6–20 lowercase ASCII letters or digits and is not normalized by the server. Four- and five-character values fail with `referral_code_too_short`. Values shorter than four characters or otherwise outside the Referral Code format fail request parsing. Codes that are occupied or fail content policy fail with `referral_code_unavailable`. **Request Body** | Field | Type | Description | |---|---|---| | `referral_code` | string | Exact owner-chosen code: 6–20 lowercase ASCII letters or digits. The exchange does not normalize this value. | | `auth` | [RequestAuth](#requestauth) | Trading-key-signed request authentication metadata. | **JSON Response Payload** On success, the response envelope's `data` object has these fields: | Field | Type | Description | |---|---|---| | `custom_referral_code` | string | The owner's newly claimed Custom Referral Code. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/referral-codes/custom \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "referral_code": "trader1", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "26FzDQPEDR6u7rKhkaEEJL1YSyKpbUCCibQtQxkDfEfEjyAsJu17X5p8TtuW5iXQfCNbs9gjEsFecqPv4yffwsgS" } } EOF ``` **Example response** ```json { "status": "success", "data": { "custom_referral_code": "trader1" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Set A Username `POST /api/v1/usernames` Sets, renames, or clears the authenticated owner's Username. This request is signed by an active trading key and requires a deposited account. Usernames contain 3 to 20 ASCII letters, digits, underscores, or hyphens. Submitted casing is preserved, but uniqueness is case-insensitive. Send an empty string to clear the current Username. An unavailable Username returns `CONFLICT` with the message `That name is taken.` **Request Body** | Field | Type | Description | |---|---|---| | `username` | string | Owner-chosen Username, or an empty string to clear the current Username. | | `auth` | [RequestAuth](#requestauth) | Trading-key-signed request authentication metadata. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `username` | `Username` | (Optional) The now-active Username, or `null` after a clear. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/usernames \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "username": "Trader", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "2aEt4QQeG3iFTD6tAZvDnfKzna7cnVh18eq6cveGho9mdwfDGLoFjs1fsyYieXuZKKGdpFFCH8VgvVQJ2K1BBmjV" } } EOF ``` **Example response** ```json { "status": "success", "data": { "username": "Trader" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Set An Account Bio `POST /api/v1/account-bios` Sets or clears the authenticated owner's Account Bio. This request is signed by an active trading key and requires a deposited account. Send an empty string to clear it. An Account Bio can contain 1 to 80 characters. If the text isn't allowed, the API returns `INVALID_REQUEST` (HTTP 422) with the message `That account bio isn't allowed.` **Request Body** | Field | Type | Description | |---|---|---| | `account_bio` | string | Owner-chosen Account Bio, or an empty string to clear it. | | `auth` | [RequestAuth](#requestauth) | Trading-key-signed request authentication metadata. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `account_bio` | `AccountBio` | (Optional) The Account Bio this request set, or `null` after a clear. | **Example request** ```bash curl -X POST https://trade.pascal.trade/api/v1/account-bios \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "account_bio": "verify-42", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "4p8PmDZ3ifUrPpAPjK6R1TmmfvqMmf6TA6RsrYYsgA1VqLacSbjkfN2LG8LVwwGCobrwSeMHQvSo1zWqWTuC1sNA" } } EOF ``` **Example response** ```json { "status": "success", "data": { "account_bio": "verify-42" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ## Order Entry WebSocket `GET wss://trade.pascal.trade/ws` upgrades to a WebSocket connection for placing and cancelling orders. Submission via WebSocket may be useful to users who want many concurrent orders in flight and also want guaranteed submission ordering. Note: - WebSocket messages sent on one connection enter matching in the order you sent them, so an order can replace or cancel an order whose acknowledgement has not arrived yet (see [Matching Rounds And Priority](general.txt#matching-rounds-and-priority)) - Send up to 128 concurrent messages per connection. At the cap the server stops reading the socket until responses are delivered. - Rate limit of 1,000 requests per second (counting batch items and pings). - Batch and message size limits are the same as for REST: 50 requests per frame, 64 KiB per frame. As on REST, `mass_cancel` frames take at most 10 requests. - Send `{"type":"ping"}` messages to keep the connection open. Connections idle for 60 seconds are closed. - Responses do **not** necessarily arrive in the order requests were sent. Use the `id` field on the request envelope to identify responses. ### Client Requests Send JSON place, cancel and mass cancel messages shaped like ```json { "id": "42", "type": "place|cancel|mass_cancel|ping", "payload": "/* same place, cancel or mass cancel payload as REST */" } ``` The payload matches the [Place And Replace Orders](#place-and-replace-orders) array for `place`, the [Cancel Orders](#cancel-orders) array for `cancel`, or the [Mass Cancel Orders](#mass-cancel-orders) array for `mass_cancel` exactly. `id` is an optional field echoed back on the message's response, to help the client match responses to requests. ```json { "id": "1", "type": "place", "payload": [ { "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size": "10", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "2rUctQ9bFbuQaUxbXDGRtGm4LMj3GdbYWAW6pjrznAsLvpdH8Eqyx3LgsFx3YzV9cvMfw1ND5T5nChUQZZJ7jEad" } } ] } ``` ```json { "id": "2", "type": "cancel", "payload": [ { "client_order_id": "42", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "591YziUjZzro24mamxgRrSDdYD71NpsDBmU4UGSyrBYbFiWzYaLjR9he1P5DP8djMKMJzHTsZzwRAagvLqgtiXnc" } } ] } ``` ```json { "id": "3", "type": "mass_cancel", "payload": [ { "symbol": "SIM_EVENT_1.MARKET_1", "auth": { "client_ts_ms": "1731536000000", "recv_window_ms": "5000", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "signature": "oLmkQxx9xLPK4QJnFhZUFEker5k5pbLregsYPgRrhrd9sEBYDuCtitUtFSVfhiJpTFHWAdmGk6zKDLpPtA3iegS" } } ] } ``` **Ping** keeps the connection alive and verifies the server end-to-end. The server responds with `{"type":"pong"}`. ### Server Responses The server sends one response message per request message, but **not necessarily in request order**. Match request/response messages by the echoed `id`, or match individual payload entries by `client_order_id`. ```json { "id": "1", "type": "place", "payload": [ { "status": "success", "data": { "order": { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "7", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "fills": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "reduce_only": false, "fill_price": "0.550000", "fill_size": "3", "liquidity": "TAKER", "counterparty": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "order_id": "987654321", "client_order_id": "42", "fee_usd": "0.000742", "collateral_change_usd": "-1.650742", "position_size_prev": "0", "realized_pnl_usd": "0.000000", "seq": "67891", "trade_ts_ms": "1731536000050" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] } ``` ```json { "id": "2", "type": "cancel", "payload": [ { "status": "success", "data": { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] } ``` ```json { "id": "3", "type": "mass_cancel", "payload": [ { "status": "success", "data": { "orders": [ { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, { "id": "987654322", "client_order_id": "43", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ] } ``` Requests rejected before entering the write pipeline (over the rate limit, under backpressure, unparseable, or over the batch size limit) get an `error` frame instead, echoing the frame's `id` and the rejected `client_order_ids` (both absent when they could not be parsed). Unparseable frames carry the parse error in the `error` frame's message; repeated ones close the connection (see Close Codes). ```json { "id": "3", "type": "error", "payload": { "data": { "code": "RATE_LIMITED", "message": "rate limited; slow down before retrying" }, "client_order_ids": [ "42" ] } } ``` ### Close Codes | Code | Reason | Meaning | |---|---|---| | 1000 | `idle` | No inbound frames and no pending responses for the idle timeout. | | 1001 | `going_away` | Server restarting; reconnect. | | 1009 | — | Frame exceeded the 64 KiB message size limit. | | 4001 | `slow_consumer` | Client stopped reading responses. | | 4002 | `rate_limited` | Client kept sending after repeated rate-limit errors. | | 4003 | `malformed_frame` | Client kept sending invalid messages after repeated errors. | Clients closed with `malformed_frame` or `rate_limited` are rejected on subsequent upgrades with HTTP 429 for several minutes. ## Market Data Endpoints ### Server Time `GET /api/v1/time` Returns the read API server's current wall-clock time in milliseconds. **JSON Response Payload** | Field | Type | Description | |---|---|---| | `server_time_ms` | u64 string | API server wall-clock time in milliseconds since the Unix epoch. | ```bash curl 'https://data.pascal.trade/api/v1/time' ``` ```json { "status": "success", "data": { "server_time_ms": "1731536000123" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Usernames `GET /api/v1/usernames?owners={owner_1},{owner_2}` Returns active Usernames for up to 100 account owners. Owners without a Username are omitted. The request rejects an empty owner list, duplicate owners, and lists over the limit. **Query Parameters** | Field | Type | Description | |---|---|---| | `owners` | array of base58 string | Comma-delimited list of account owners whose Usernames should be returned. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `usernames` | object mapping base58 string to `Username` | Active Usernames keyed by owner. Requested owners without a Username are omitted. | ```bash curl 'https://data.pascal.trade/api/v1/usernames?owners=GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB,J2xccRtuG43drESLYznHhLhQkLTdfepcKYbiQ9BsJVaf' ``` ```json { "status": "success", "data": { "usernames": { "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB": "Trader" } }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Referral Code `GET /api/v1/referral-codes/{referral_code}` Returns whether `referral_code` currently resolves to a Reward Owner, plus the taker discount terms a referred user receives at registration. Revoked Custom Referral Codes return `exists: false`. See the [Referral Program](general.txt#referral-program) for how both sides of a Referral work. Codes are 4 to 20 characters of lowercase `a-z0-9`; other values are rejected with `invalid_request`. **JSON Response Payload** | Field | Type | Description | |---|---|---| | `exists` | boolean | Whether this Referral Code currently resolves to a Reward Owner. | | `taker_fee_discount_share` | decimal string, 6 d.p. | Fraction of each taker fee discounted for a user referred by this code. | | `fee_savings_limit_usd` | decimal string, 6 d.p. | Maximum taker fee savings for a user referred by this code. The discount stops after cumulative savings cross this limit. | ```bash curl 'https://data.pascal.trade/api/v1/referral-codes/k7wq2m4x' ``` ```json { "status": "success", "data": { "exists": true, "taker_fee_discount_share": "0.050000", "fee_savings_limit_usd": "250.000000" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### List Markets `GET /api/v1/markets` Returns unresolved markets and their current state, ordered by symbol. **JSON Response Payload** Returns a JSON array of market objects. Every element in the array is a JSON object: #### MarketStateMsg Contains the market spec, display attributes, and current state exposed by the Read API. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Human-readable identifier for a market. Globally Unique. Eg "NYCMAYOR_25NOV04.MAMDANI". | | `taker_fee_rate` | decimal string, 6 d.p. | Base fee rate paid by takers. Decimal string, e.g. "0.0010" for 10 bps. For a trade of sz at px, the taker fee is `sz * px * (1 - px) * taker_fee_rate`. | | `maker_rebate_share` | decimal string, 6 d.p. | Share of taker fee rebated to makers. Decimal string, e.g. "0.50" for 50%. For a trade of sz at px, the maker earns `sz * px * (1 - px) * taker_fee_rate * maker_rebate_share`. | | `tick_size_min` | decimal string, 6 d.p. | Minimum tick size. The tick size for a given price `px` is `max(tick_size_min, 10^(floor(log10(px')) - tick_sig_figs + 1))`, where `px' = min(px, 1 - px)`. The intuition is that we want some amount of decimal precision available, but we don't want to count 0 prefixes. For example, 0.0025, 0.025 and 0.25 all have 2 digits of precision. | | `tick_sig_figs` | integer | Number of significant figures in the tick size formula. Must be in `[1, 6]`. | | `display_attributes` | object mapping string to JSON value | Market display metadata for UI rendering. Keys and shape match [MarketDisplayAttributes](#marketdisplayattributes). | | `listing_ts_ms` | u64 string | (Optional) Timestamp when the market was listed, in milliseconds since the Unix epoch. | | `rules_url` | string | (Optional) URL to the market's resolution rules document, if available. | | `stats` | [MarketStatsMsg](#marketstatsmsg) | (Optional) Stats. | | `mark_price` | decimal string, 6 d.p. | (Optional) Median of whichever of (best bid, best ask, last trade) are available. | | `best_bid` | decimal string, 6 d.p. | (Optional) Highest buy price on the order book. Omitted when the book has no bids. | | `best_ask` | decimal string, 6 d.p. | (Optional) Lowest sell price on the order book. Omitted when the book has no asks. | | `open_interest` | u64 string | Legacy open interest, equal to `long_interest`. @deprecated Use `long_interest` or `short_interest` instead. | | `long_interest` | u64 string | (Optional) Current interest held by long positions, in contracts. | | `short_interest` | u64 string | (Optional) Current interest held by short positions, in contracts. | | `resolution` | [MarketResolutionMsg](#marketresolutionmsg) | (Optional) Final market resolution, if this market has resolved. | `display_attributes` object: #### MarketDisplayAttributes Additional market attributes used for display purposes only. | Field | Type | Description | |---|---|---| | `event_description` | string | Human-readable description of the event, has to fit in-line. Unenforced max 50 chars. For example, `2025 NYC Mayoral Election`. | | `market_description` | string | Short label for this market, displayed in the context of `event_description`. For example, `Mamdani` or `Dec 31`. | | `reverse_description` | string | (Optional) Short label for this market to display if the user has reversed the UI. Mirrors `market_description`. For example, if the market is YANKEES_VS_METS.YANKEES the market_description might be "Yankees". In that case the reverse_description would be "Mets". `None` indicates reversing the UI is not available for this market. This is a UI-only field. | | `outcome_labels` | [MarketOutcomeLabels](#marketoutcomelabels) | (Optional) Names for the long and short contracts. Defaults to Yes and No when absent. | | `colors` | [MarketDisplayColors](#marketdisplaycolors) | (Optional) Theme colors for this market's outcomes, present on head-to-head sections such as a moneyline. Use the values as supplied. | | `topic` | [TopicInfo](#topicinfo) | (Optional) Higher-level topic this event belongs to, if any. | | `game_page` | [GamePageDisplayInfo](#gamepagedisplayinfo) | (Optional) Display metadata for sections (game lines) under the same game. When absent, clients should use regular event-based display. | | `market_type` | [MarketType](#markettype) | (Optional) Typed market semantics for display and validation. | | `tags` | array of string | First-letter-capitalized tags for filtering and discovery. | | `expected_event_start_time_ms` | u64 string | (Optional) Expected start time of the event. Indicative, for display only. | | `expected_resolution_time_ms` | u64 string | Expected date for market resolution. Indicative, for display only. | | `sort_order` | [MarketSort](#marketsort) | Default UI sort order | | `scalar` | [RangeDisplayAttributes](#rangedisplayattributes) | (Optional) Scalar-market display metadata. | | `reference` | [ReferenceAttributes](#referenceattributes) | (Optional) Reference that informs this market's resolution criteria. | | `resolves_at_expected_time` | boolean | (Optional) Whether this market resolves only after `expected_resolution_time_ms`, for example on a value published at a fixed time. When `false` or absent, it can resolve as soon as its outcome is known. | #### MarketOutcomeLabels Labels for the long (Yes) and short (No) contracts. | Field | Type | Description | |---|---|---| | `long` | [MarketOutcomeLabel](#marketoutcomelabel) | Long. | | `short` | [MarketOutcomeLabel](#marketoutcomelabel) | Short. | #### MarketOutcomeLabel Display names for one canonical market outcome. | Field | Type | Description | |---|---|---| | `extended` | string | Complete outcome name, up to 14 characters. | | `abbreviated` | string | Complete name for compact controls, up to 4 characters. | `display_attributes.scalar` object: #### RangeDisplayAttributes | Field | Type | Description | |---|---|---| | `upper` | i16 string | Upper bound of the scalar market outcome space, from -999 to 999. | | `lower` | i16 string | Lower bound of the scalar market outcome space, from -999 to 999. | | `scale` | u8 string | How many decimal points to move over in the bounds. For example, 15 with a scale of 1 is 1.5. | | `prefix` | string | String to display before the outcome-space price. EG: "$" | | `suffix` | string | String to display after the outcome-space price. EG: "K", "B", "T" | `display_attributes.topic` object: #### TopicInfo Higher-level category that links related events for display. | Field | Type | Description | |---|---|---| | `id` | string | Stable identifier for grouping and filtering. For display only. | | `description` | string | Human-readable label for this topic. For display only. | `display_attributes.game_page` object: #### GamePageDisplayInfo Display metadata for sections (game lines) under the same game. | Field | Type | Description | |---|---|---| | `game_id` | string | Stable identifier for grouping and filtering, e.g. `fifa-world-cup-fra-irq-2026-06-22`. | | `game_description` | string | Human-readable game title, e.g. `France vs Iraq`. | | `section_id` | string | Stable section (game line) id, e.g. `moneyline`, `spreads`, or `totals`. | | `section_description` | string | Human-readable section (game line) label, e.g. `Spreads`, `Totals`. | | `section_display_order` | integer | Section display order within the game. This orders sections, not rows. | | `row_display_order` | integer | (Optional) Explicit row display order within the section. | | `subject_id` | string | (Optional) Stable subject slug for team/player-specific rows, e.g. `lionel-messi`. | | `subject_description` | string | (Optional) Human-readable subject label, e.g. `Lionel Messi`. | | `value` | string | (Optional) Normalized numeric value for a game line. Spread values are signed from the subject's perspective, e.g. `-1.5`; total values are unsigned, e.g. `2.5`. | `display_attributes.colors` object, present on head-to-head sections such as a moneyline. Use the color for the active theme as supplied. #### MarketDisplayColors Theme colors for a market's outcomes. | Field | Type | Description | |---|---|---| | `market` | [ThemeDisplayColors](#themedisplaycolors) | Colors for the `market_description` outcome. | | `reverse` | [ThemeDisplayColors](#themedisplaycolors) | (Optional) Colors for the `reverse_description` outcome, when it names the other side of a head-to-head comparison. | `display_attributes.colors.market` and `display_attributes.colors.reverse` objects: #### ThemeDisplayColors A display color for each theme, as an opaque `#RRGGBB` value. Use the one for the active theme as supplied. | Field | Type | Description | |---|---|---| | `light` | string | Color to use on Pascal's light-theme surfaces. | | `dark` | string | Color to use on Pascal's dark-theme surfaces. | `display_attributes.market_type` enum: #### MarketType Typed market category and category-specific details for a listed market. Variant fields are flattened into the same object as `kind`. For example, sports markets use `kind: "sports"` with a `sport` value such as `"soccer"`, and company action markets use `kind: "company_action"` with their company action fields alongside it. | Value | |---| | `"sports"` | | `"company_action"` | `display_attributes.market_type.sport` enum for `kind: sports` market types: #### SportsMarketType Sport-specific market type details. | Value | |---| | `"soccer"` | | `"baseball"` | | `"league_of_legends"` | | `"counter_strike"` | | `"dota_2"` | | `"tennis"` | | `"football"` | | `"basketball"` | | `"hockey"` | **`"soccer"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"soccer"` | Discriminator. | | `period` | [SoccerPeriod](#soccerperiod) | Period. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [SoccerMarket](#soccermarket) | Market. | **`"baseball"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"baseball"` | Discriminator. | | `period` | [BaseballPeriod](#baseballperiod) | Period. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [BaseballMarket](#baseballmarket) | Market. | **`"league_of_legends"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"league_of_legends"` | Discriminator. | | `series_format` | [BestOfSeriesFormat](#bestofseriesformat) | Series format. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [LeagueOfLegendsMarket](#leagueoflegendsmarket) | Market. | **`"counter_strike"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"counter_strike"` | Discriminator. | | `series_format` | [BestOfSeriesFormat](#bestofseriesformat) | Series format. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [CounterStrikeMarket](#counterstrikemarket) | Market. | **`"dota_2"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"dota_2"` | Discriminator. | | `series_format` | [BestOfSeriesFormat](#bestofseriesformat) | Series format. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [Dota2Market](#dota2market) | Market. | **`"tennis"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"tennis"` | Discriminator. | | `period` | [TennisPeriod](#tennisperiod) | Period. | | `match_format` | [TennisMatchFormat](#tennismatchformat) | Match format. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [TennisMarket](#tennismarket) | Market. | **`"football"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"football"` | Discriminator. | | `period` | [FootballPeriod](#footballperiod) | Period. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [FootballMarket](#footballmarket) | Market. | **`"basketball"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"basketball"` | Discriminator. | | `period` | [BasketballPeriod](#basketballperiod) | Period. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [BasketballMarket](#basketballmarket) | Market. | **`"hockey"` fields** | Field | Type | Description | |---|---|---| | `sport` | string literal `"hockey"` | Discriminator. | | `period` | [HockeyPeriod](#hockeyperiod) | Period. | | `teams` | [HomeAwayTeams](#homeawayteams) | Teams. | | `market` | [HockeyMarket](#hockeymarket) | Market. | `display_attributes.market_type.period` enum for soccer markets: #### SoccerPeriod Portion of a soccer match that a market settles on. | Value | Description | |---|---| | `"reg_time"` | Regular time plus stoppage time, excluding extra time and penalties. | | `"first_half"` | First half plus stoppage time, excluding second half, extra time, and penalties. | | `"second_half"` | Second half plus stoppage time, excluding first half, extra time, and penalties. | `display_attributes.market_type.period` enum for baseball markets: #### BaseballPeriod Portion of a baseball game that a market settles on. | Value | Description | |---|---| | `"full_game"` | Official full-game result, including extra innings. | `display_attributes.market_type.period` enum for football markets: #### FootballPeriod Portion of a football game that a market settles on. | Value | Description | |---|---| | `"full_game"` | Official full-game result, including any overtime. | `display_attributes.market_type.period` enum for basketball markets: #### BasketballPeriod Portion of a basketball game that a market settles on. | Value | Description | |---|---| | `"full_game"` | Official full-game result, including any overtime. | `display_attributes.market_type.period` enum for hockey markets: #### HockeyPeriod Portion of a hockey game that a market settles on. | Value | Description | |---|---| | `"full_game"` | Official full-game result, including any overtime and shootouts. | `display_attributes.market_type.period` enum for tennis markets: #### TennisPeriod Portion of a tennis match that a market settles on. | Value | Description | |---|---| | `"full_match"` | Official full-match result, including tiebreaks. | `display_attributes.market_type.match_format` enum for tennis markets: #### TennisMatchFormat Number of sets in a completed tennis match. | Value | |---| | `"best_of_three"` | | `"best_of_five"` | `display_attributes.market_type.series_format` enum for best-of esports markets: #### BestOfSeriesFormat Format of a best-of series. | Value | |---| | `"best_of_one"` | | `"best_of_three"` | | `"best_of_five"` | `display_attributes.market_type.teams` object for fixture-scoped team sports: #### HomeAwayTeams Provider-designated home and away competitors for match-scoped market types. For neutral-site, esports, and individual-sport matches, home and away provide canonical fixture ordering and do not imply that either competitor is playing at a home venue. | Field | Type | Description | |---|---|---| | `home_name` | string | Full human-readable home team name used in market labels and rules. | | `home_short_name` | string | (Optional) Concise home team name used in generated fixture and event display labels. When provided, `away_short_name` must also be provided. | | `home_subject_id` | string | Stable subject slug for the home team. | | `away_name` | string | Full human-readable away team name used in market labels and rules. | | `away_short_name` | string | (Optional) Concise away team name used in generated fixture and event display labels. When provided, `home_short_name` must also be provided. | | `away_subject_id` | string | Stable subject slug for the away team. | `display_attributes.market_type.market` enum for soccer markets: #### SoccerMarket Soccer market types. | Value | |---| | `"moneyline"` | | `"spread"` | | `"total_goals"` | | `"both_teams_to_score"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [ThreeWayOutcome](#threewayoutcome) | Outcome. | **`"spread"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"spread"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_goals"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_goals"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for baseball markets: #### BaseballMarket Market types for a baseball game. | Value | |---| | `"moneyline"` | | `"spread"` | | `"total"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"spread"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"spread"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for football markets: #### FootballMarket Football market types. | Value | |---| | `"moneyline"` | | `"spread"` | | `"total_points"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"spread"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"spread"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_points"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_points"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for basketball markets: #### BasketballMarket Basketball market types. | Value | |---| | `"moneyline"` | | `"spread"` | | `"total_points"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"spread"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"spread"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_points"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_points"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for hockey markets: #### HockeyMarket Hockey market types. | Value | |---| | `"moneyline"` | | `"spread"` | | `"total_goals"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"spread"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"spread"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_goals"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_goals"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for League of Legends markets: #### LeagueOfLegendsMarket League of Legends market types. | Value | |---| | `"moneyline"` | | `"map_winner"` | | `"map_handicap"` | | `"total_maps"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"map_winner"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"map_winner"` | Request discriminator. | | `map_number` | integer | Map number. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"map_handicap"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"map_handicap"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_maps"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_maps"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for Counter-Strike markets: #### CounterStrikeMarket Counter-Strike market types. | Value | |---| | `"moneyline"` | | `"map_winner"` | | `"map_handicap"` | | `"total_maps"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"map_winner"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"map_winner"` | Request discriminator. | | `map_number` | integer | Map number. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"map_handicap"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"map_handicap"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_maps"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_maps"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for Dota 2 markets: #### Dota2Market Dota 2 market types. | Value | |---| | `"moneyline"` | | `"map_winner"` | | `"map_handicap"` | | `"total_maps"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"map_winner"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"map_winner"` | Request discriminator. | | `map_number` | integer | Map number. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"map_handicap"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"map_handicap"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_maps"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_maps"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market` enum for tennis markets: #### TennisMarket Tennis market types. | Value | |---| | `"moneyline"` | | `"set_handicap"` | | `"total_sets"` | | `"total_games"` | **`"moneyline"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"moneyline"` | Request discriminator. | | `outcome` | [TeamSide](#teamside) | Outcome. | **`"set_handicap"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"set_handicap"` | Request discriminator. | | `team` | [TeamSide](#teamside) | Team. | | `line` | string | Line. | **`"total_sets"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_sets"` | Request discriminator. | | `line` | string | Line. | **`"total_games"` fields** | Field | Type | Description | |---|---|---| | `type` | string literal `"total_games"` | Request discriminator. | | `line` | string | Line. | `display_attributes.market_type.market.outcome` enum for moneyline market types that can draw: #### ThreeWayOutcome Home, away, or draw outcome for three-way markets. | Value | |---| | `"home"` | | `"away"` | | `"draw"` | `display_attributes.market_type.market.team` enum for team-scoped market types: #### TeamSide | Value | |---| | `"home"` | | `"away"` | `display_attributes.market_type` object for `kind: company_action` market types. The announcement deadline must be 11:59:59 PM ET on its calendar day, and `sort_order` must be `EXPECTED_RESOLUTION_TIME`: #### CompanyActionMarketType A company officially announcing an action before a deadline. | Field | Type | Description | |---|---|---| | `company_name` | string | Display name of the company used in market labels and rules, e.g. `Meta`. | | `action` | string | The action as a noun phrase completing "officially announces ...", e.g. `a partnership with OpenAI`. | | `announcement_deadline_time_ms` | u64 string | Cutoff for a qualifying announcement. | `display_attributes.sort_order` enum: #### MarketSort An enum encoding the default sort order for markets on the UI | Value | |---| | `"PRICE"` | | `"SYMBOL_ASC"` | | `"SYMBOL_DESC"` | | `"MARKET_DESCRIPTION_ASC"` | | `"MARKET_DESCRIPTION_DESC"` | | `"VOLUME"` | | `"EXPECTED_RESOLUTION_TIME"` | `display_attributes.reference` enum: #### ReferenceAttributes | Value | |---| | `"polymarket"` | **`"polymarket"` fields** | Field | Type | Description | |---|---|---| | `kind` | string literal `"polymarket"` | Discriminator. | | `market_slug` | string | Polymarket market slug mirrored by this Pascal market. | | `condition_id` | string | Polymarket condition id for the mirrored CLOB market. | | `market_outcome_token_id` | string | (Optional) Polymarket CLOB token id for the Pascal market side. If this token wins, the Pascal market resolves to 1.000000. | | `reverse_outcome_token_id` | string | (Optional) Polymarket CLOB token id for the other binary outcome. If this token wins, the Pascal market resolves to 0.000000. | `resolution` object: #### MarketResolutionMsg Final resolution metadata for a market. | Field | Type | Description | |---|---|---| | `price` | decimal string, 6 d.p. | Final settlement price in dollars. | | `seq` | u64 string | Matching engine sequence number when the market resolved. | | `market_resolution_ts_ms` | u64 string | Timestamp when the market resolved, in milliseconds since the Unix epoch. | `stats` object: #### MarketStatsMsg | Field | Type | Description | |---|---|---| | `last_24h` | [MarketStatsSnapshotMsg](#marketstatssnapshotmsg) | Rolling 24-hour market statistics snapshot. | | `all_time` | [MarketStatsSnapshotMsg](#marketstatssnapshotmsg) | All-time market statistics snapshot. | `last_24h` and `all_time` use the following shape: #### MarketStatsSnapshotMsg | Field | Type | Description | |---|---|---| | `mark_price_start` | decimal string, 6 d.p. | (Optional) Mark price at the start of the rolling window. | | `taker_buy_volume` | u64 string | Total base-asset volume bought by takers in the window. | | `taker_sell_volume` | u64 string | Total base-asset volume sold by takers in the window. | | `taker_buy_volume_notional` | decimal string, 6 d.p. | Sum of `price * size` for trades where the taker buys in this statistics window, in USD. | | `taker_sell_volume_notional` | decimal string, 6 d.p. | Sum of `price * size` for trades where the taker sells in this statistics window, in USD. | | `taker_buy_trade_count` | u64 string | Total number of buy-side taker trades included in the snapshot window. | | `taker_sell_trade_count` | u64 string | Total number of sell-side taker trades included in the snapshot window. | | `window_end_ts_ms_exclusive` | u64 string | Exclusive upper-bound timestamp for the aggregation window. | For `last_24h`, `window_end_ts_ms_exclusive` is the latest closed minute. Markets with no history receive a zero snapshot at that boundary. Resolution does not freeze the 24-hour window. A resolved market keeps its final price while volume, trades, and earlier prices age out normally. Once the window begins after resolution, `mark_price_start` is the final price. ```bash curl 'https://data.pascal.trade/api/v1/markets' ``` ```json { "status": "success", "data": [ { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2, "display_attributes": { "event_description": "API documentation sample market", "expected_resolution_time_ms": "1735000000000", "market_description": "Yes", "reverse_description": "No", "sort_order": "SYMBOL_ASC", "tags": [ "Docs" ], "topic": { "id": "docs", "description": "Documentation examples" } }, "listing_ts_ms": "1731536000000", "rules_url": "https://sample-rules-url.com/events/SIM_EVENT_1/SIM_EVENT_1.MARKET_1.md", "stats": { "last_24h": { "mark_price_start": "0.520000", "taker_buy_volume": "120", "taker_sell_volume": "90", "taker_buy_volume_notional": "64.800000", "taker_sell_volume_notional": "48.600000", "taker_buy_trade_count": "18", "taker_sell_trade_count": "14", "window_end_ts_ms_exclusive": "1731535980000" }, "all_time": { "taker_buy_volume": "1200", "taker_sell_volume": "900", "taker_buy_volume_notional": "648.000000", "taker_sell_volume_notional": "486.000000", "taker_buy_trade_count": "180", "taker_sell_trade_count": "140", "window_end_ts_ms_exclusive": "1731535980000" } }, "mark_price": "0.550000", "best_bid": "0.540000", "best_ask": "0.560000", "open_interest": "250", "long_interest": "250", "short_interest": "250" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Resolved Markets `GET /api/v1/markets/resolved` Returns markets resolved within the requested `lookback`. **Query Parameters** | Field | Type | Description | |---|---|---| | `lookback` | string | Optional. Accepted values: `4h`, `24h`, `3d`. Defaults to `24h`. | | `order_by` | string | Optional. Accepted values: `resolution_time`, `volume_24h`, `volume_all_time`. Defaults to `resolution_time`. | | `limit` | integer | Optional. Maximum number of markets to return. Must be one of `50`, `100`, or `200`. Defaults to 50. | `order_by` enum: #### ResolvedMarketOrderBy Resolved market ordering field. | Value | Description | |---|---| | `"resolution_time"` | Sort by least distance between market resolution time and current exchange time. | | `"volume_24h"` | Sort by rolling 24-hour contract volume. | | `"volume_all_time"` | Sort by all-time contract volume. | `order_by` behavior: | Value | Description | |---|---| | `resolution_time` | Sort by least distance between resolution time and the current exchange time. For this endpoint, that returns the latest resolutions first. | | `volume_24h` | Sort by rolling 24-hour contract volume. Ties use the `resolution_time` ordering. | | `volume_all_time` | Sort by all-time contract volume. | Resolved markets use the rolling-statistics semantics described under [List Markets](#list-markets). **JSON Response Payload** Returns a JSON array of market objects. Same object shape as `GET /api/v1/markets`. ```bash curl 'https://data.pascal.trade/api/v1/markets/resolved?lookback=24h&order_by=resolution_time&limit=50' ``` ```json { "status": "success", "data": [ { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2, "display_attributes": { "event_description": "API documentation sample market", "expected_resolution_time_ms": "1731536000050", "market_description": "Yes", "reverse_description": "No", "sort_order": "SYMBOL_ASC", "tags": [ "Docs" ], "topic": { "id": "docs", "description": "Documentation examples" } }, "listing_ts_ms": "1731536000000", "rules_url": "https://sample-rules-url.com/events/SIM_EVENT_1/SIM_EVENT_1.MARKET_1.md", "stats": { "last_24h": { "mark_price_start": "0.520000", "taker_buy_volume": "120", "taker_sell_volume": "90", "taker_buy_volume_notional": "64.800000", "taker_sell_volume_notional": "48.600000", "taker_buy_trade_count": "18", "taker_sell_trade_count": "14", "window_end_ts_ms_exclusive": "1731535980000" }, "all_time": { "taker_buy_volume": "1200", "taker_sell_volume": "900", "taker_buy_volume_notional": "648.000000", "taker_sell_volume_notional": "486.000000", "taker_buy_trade_count": "180", "taker_sell_trade_count": "140", "window_end_ts_ms_exclusive": "1731535980000" } }, "mark_price": "1.000000", "open_interest": "0", "long_interest": "0", "short_interest": "0", "resolution": { "price": "1.000000", "seq": "67891", "market_resolution_ts_ms": "1731536000050" } } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` #### Market Symbols Symbols are uppercase ASCII identifiers up to 32 bytes long, of the form `EVENT.MARKET`. The event prefix groups markets that share a resolution event; the market suffix identifies the specific outcome. For example, `NYCMAYOR_25NOV04.MAMDANI` denotes the "Mamdani" outcome of the 2025-11-04 New York City mayoral election event. A second outcome on the same event might be `NYCMAYOR_25NOV04.ADAMS`. Symbols are immutable and globally unique. ### Market Search `POST /api/v1/markets/search` Returns markets matching any requested symbol, event, or game. Symbol, event, and game filters are exact-match filters combined with union semantics. At least one filter is required. Search results include resolved markets. Requests may include at most 500 symbols, at most 50 event codes, and at most 50 games. Markets are ordered by symbol. Resolved markets use the rolling-statistics semantics described under [List Markets](#list-markets). **JSON Request Body** | Field | Type | Description | |---|---|---| | `symbols` | array of string ([Symbol](#market-symbols)) | (Optional) Exact symbols to match. Maximum 500 symbols. | | `events` | array of string | (Optional) Exact event codes to match. Maximum 50 event codes. | | `games` | array of string | (Optional) Exact game ids to match. Maximum 50 games. | **JSON Response Payload** Returns a JSON array of market objects. Same object shape as `GET /api/v1/markets`. ```bash curl -X POST https://data.pascal.trade/api/v1/markets/search \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "symbols": [ "SIM_EVENT_1.MARKET_1" ], "events": [ "SIM_EVENT_1" ], "games": [ "fifa-world-cup-fra-irq-2026-06-22" ] } EOF ``` ```json { "status": "success", "data": [ { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2, "display_attributes": { "event_description": "API documentation sample market", "expected_resolution_time_ms": "1735000000000", "market_description": "Yes", "reverse_description": "No", "sort_order": "SYMBOL_ASC", "tags": [ "Docs" ], "topic": { "id": "docs", "description": "Documentation examples" } }, "listing_ts_ms": "1731536000000", "rules_url": "https://sample-rules-url.com/events/SIM_EVENT_1/SIM_EVENT_1.MARKET_1.md", "stats": { "last_24h": { "mark_price_start": "0.520000", "taker_buy_volume": "120", "taker_sell_volume": "90", "taker_buy_volume_notional": "64.800000", "taker_sell_volume_notional": "48.600000", "taker_buy_trade_count": "18", "taker_sell_trade_count": "14", "window_end_ts_ms_exclusive": "1731535980000" }, "all_time": { "taker_buy_volume": "1200", "taker_sell_volume": "900", "taker_buy_volume_notional": "648.000000", "taker_sell_volume_notional": "486.000000", "taker_buy_trade_count": "180", "taker_sell_trade_count": "140", "window_end_ts_ms_exclusive": "1731535980000" } }, "mark_price": "0.550000", "best_bid": "0.540000", "best_ask": "0.560000", "open_interest": "250", "long_interest": "250", "short_interest": "250" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Market Text Search `POST /api/v1/markets/text-search` Returns markets whose symbol, event description, market description, or tag contains the requested text. Matching is case-insensitive substring matching. If `tags` is provided, every returned market must contain all requested tags exactly. Requests must include a query between 3 and 64 characters after trimming. Missing or type-invalid fields fail JSON request parsing; blank, too-short, or too-long trimmed queries return `INVALID_REQUEST`. Results are ordered by `order_by`. `limit` defaults to 50 and may not exceed 100. Limited to 60 requests per minute per IP. Resolved markets use the rolling-statistics semantics described under [List Markets](#list-markets). **JSON Request Body** | Field | Type | Description | |---|---|---| | `query` | string | Case-insensitive substring query. Trimmed length must be 3 to 64 Unicode scalar chars. | | `tags` | array of string | (Optional) Exact display-attribute tags all returned markets must contain. Maximum 5 tags. | | `status` | [MarketStatusFilter](#marketstatusfilter) | (Optional) Resolution status filter. | | `order_by` | [MarketSearchOrderBy](#marketsearchorderby) | (Optional) Market field used for result ordering. | | `limit` | integer | (Optional) Maximum number of markets to return. Defaults to 50. Maximum 100. | `status` enum: #### MarketStatusFilter Market lifecycle status filter. | Value | Description | |---|---| | `"live"` | Include unresolved markets only. | | `"resolved"` | Include resolved markets only. | | `"all"` | Include both unresolved and resolved markets. | `order_by` enum: #### MarketSearchOrderBy Market text-search ordering field. | Value | Description | |---|---| | `"volume_24h"` | Sort by rolling 24-hour contract volume. | | `"volume_all_time"` | Sort by all-time contract volume. | `order_by` behavior: | Value | Description | |---|---| | `volume_24h` | Sort by rolling 24-hour contract volume descending, then by symbol ascending. | | `volume_all_time` | Sort by all-time contract volume descending, then by symbol ascending. | **JSON Response Payload** Returns a JSON array of market objects. Same object shape as `GET /api/v1/markets`. ```bash curl -X POST https://data.pascal.trade/api/v1/markets/text-search \ -H 'Content-Type: application/json' \ --data @- <<'EOF' { "query": "api", "tags": [ "Finance" ], "status": "live", "order_by": "volume_24h", "limit": 25 } EOF ``` ```json { "status": "success", "data": [ { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2, "display_attributes": { "event_description": "API documentation sample market", "expected_resolution_time_ms": "1735000000000", "market_description": "Yes", "reverse_description": "No", "sort_order": "SYMBOL_ASC", "tags": [ "Docs" ], "topic": { "id": "docs", "description": "Documentation examples" } }, "listing_ts_ms": "1731536000000", "rules_url": "https://sample-rules-url.com/events/SIM_EVENT_1/SIM_EVENT_1.MARKET_1.md", "stats": { "last_24h": { "mark_price_start": "0.520000", "taker_buy_volume": "120", "taker_sell_volume": "90", "taker_buy_volume_notional": "64.800000", "taker_sell_volume_notional": "48.600000", "taker_buy_trade_count": "18", "taker_sell_trade_count": "14", "window_end_ts_ms_exclusive": "1731535980000" }, "all_time": { "taker_buy_volume": "1200", "taker_sell_volume": "900", "taker_buy_volume_notional": "648.000000", "taker_sell_volume_notional": "486.000000", "taker_buy_trade_count": "180", "taker_sell_trade_count": "140", "window_end_ts_ms_exclusive": "1731535980000" } }, "mark_price": "0.550000", "best_bid": "0.540000", "best_ask": "0.560000", "open_interest": "250", "long_interest": "250", "short_interest": "250" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Order Books `GET /api/v1/books` Returns order books for up to 50 comma-delimited symbols. Single-symbol responses may be up to one second old. For live order books, use the `book` WebSocket channel, which starts with a full snapshot and then streams updates. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | Comma-delimited list of market symbols. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `books` | object mapping string ([Symbol](#market-symbols)) to [OrderBookMsg](#orderbookmsg) | Order books keyed by market symbol. | Each value in `books` is an order book object: #### OrderBookMsg Order book message for API responses. Levels are (price, size) tuples serialized as string arrays: `[["0.50", "100"], ...]` | Field | Type | Description | |---|---|---| | `asks` | array of tuple (decimal string, 6 d.p., u64 string) | Ask levels as `(price, size)` tuples, sorted lowest price first. | | `bids` | array of tuple (decimal string, 6 d.p., u64 string) | Bid levels as `(price, size)` tuples, sorted highest price first. | | `spec` | [MarketSpec](#marketspec) | (Optional) Market specification for this book, included on first fetch or when the market specification changes. | `spec` object: #### MarketSpec Specification for a tradeable market. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Human-readable identifier for a market. Globally Unique. Eg "NYCMAYOR_25NOV04.MAMDANI". | | `taker_fee_rate` | decimal string, 6 d.p. | Base fee rate paid by takers. Decimal string, e.g. "0.0010" for 10 bps. For a trade of sz at px, the taker fee is `sz * px * (1 - px) * taker_fee_rate`. | | `maker_rebate_share` | decimal string, 6 d.p. | Share of taker fee rebated to makers. Decimal string, e.g. "0.50" for 50%. For a trade of sz at px, the maker earns `sz * px * (1 - px) * taker_fee_rate * maker_rebate_share`. | | `tick_size_min` | decimal string, 6 d.p. | Minimum tick size. The tick size for a given price `px` is `max(tick_size_min, 10^(floor(log10(px')) - tick_sig_figs + 1))`, where `px' = min(px, 1 - px)`. The intuition is that we want some amount of decimal precision available, but we don't want to count 0 prefixes. For example, 0.0025, 0.025 and 0.25 all have 2 digits of precision. | | `tick_sig_figs` | integer | Number of significant figures in the tick size formula. Must be in `[1, 6]`. | ```bash curl 'https://data.pascal.trade/api/v1/books?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2' ``` ```json { "status": "success", "data": { "books": { "SIM_EVENT_1.MARKET_1": { "asks": [ [ "0.560000", "12" ], [ "0.570000", "20" ] ], "bids": [ [ "0.540000", "15" ], [ "0.530000", "7" ] ], "spec": { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2 } }, "SIM_EVENT_1.MARKET_2": { "asks": [ [ "0.560000", "12" ], [ "0.570000", "20" ] ], "bids": [ [ "0.540000", "15" ], [ "0.530000", "7" ] ] } } }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Trade Candles `GET /api/v1/trade-candles` Returns OHLCV trade candles for one market and interval. Ranges are end-exclusive. If `start_time_ms` is omitted, `count_back` controls how many candles are returned before `end_time_ms`. `interval` must be one of `1m`, `5m`, `15m`, `1h`, `4h`, `12h`, and `1d`. `count_back` defaults to 100 and must be between 1 and 500. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Symbol of the market to fetch candles for. | | `interval` | string enum: `"1m"`, `"5m"`, `"15m"`, `"1h"`, `"4h"`, `"12h"`, `"1d"` | Candle aggregation interval. | | `end_time_ms` | integer | Exclusive end timestamp for the query range, in milliseconds since the Unix epoch. | | `start_time_ms` | integer | (Optional) Inclusive start timestamp for the query range. If omitted, use `count_back`. | | `count_back` | integer | (Optional) Number of candles to return ending at `end_time_ms` when `start_time_ms` is omitted. | **JSON Response Payload** Returns a JSON array of candle objects. Every element in the array is a JSON object: #### CandleMsg OHLCV candle in TradingView-compatible format. | Field | Type | Description | |---|---|---| | `time` | u64 string | Candle open time in milliseconds since the Unix epoch. | | `open` | decimal string, 6 d.p. | Opening price in dollars. | | `high` | decimal string, 6 d.p. | Highest price in the interval, in dollars. | | `low` | decimal string, 6 d.p. | Lowest price in the interval, in dollars. | | `close` | decimal string, 6 d.p. | Closing price in dollars. | | `volume` | u64 string | Total contracts traded in the interval. | | `taker_buy_volume` | u64 string | Contracts bought by takers in the interval. | | `taker_sell_volume` | u64 string | Contracts sold by takers in the interval. | | `volume_notional` | decimal string, 6 d.p. | Sum of `price * size` for all trades in the candle interval, in USD. Counts each trade once. | | `taker_buy_volume_notional` | decimal string, 6 d.p. | Sum of `price * size` for trades where the taker buys during the candle interval, in USD. | | `taker_sell_volume_notional` | decimal string, 6 d.p. | Sum of `price * size` for trades where the taker sells during the candle interval, in USD. | | `trade_count` | u64 string | Total number of trades in the interval. | | `taker_buy_count` | u64 string | Number of taker buy trades in the interval. | | `taker_sell_count` | u64 string | Number of taker sell trades in the interval. | ```bash curl 'https://data.pascal.trade/api/v1/trade-candles?symbol=SIM_EVENT_1.MARKET_1&interval=1m&end_time_ms=1731536000000&count_back=2' ``` ```json { "status": "success", "data": [ { "time": "1731535960000", "open": "0.520000", "high": "0.570000", "low": "0.510000", "close": "0.550000", "volume": "120", "taker_buy_volume": "70", "taker_sell_volume": "50", "volume_notional": "66.000000", "taker_buy_volume_notional": "38.500000", "taker_sell_volume_notional": "27.500000", "trade_count": "8", "taker_buy_count": "5", "taker_sell_count": "3" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Mark Price Candles `GET /api/v1/mark-price-candles` Returns sparse mark price OHLC candles for one market and interval. Ranges are end-exclusive. If `start_time_ms` is omitted, `count_back` controls how many candles are returned before `end_time_ms`. `interval` must be one of `1m`, `5m`, `15m`, `1h`, `4h`, `12h`, and `1d`. `count_back` defaults to 100 and must be between 1 and 500. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Symbol of the market to fetch mark price candles for. | | `interval` | string enum: `"1m"`, `"5m"`, `"15m"`, `"1h"`, `"4h"`, `"12h"`, `"1d"` | Candle aggregation interval. | | `end_time_ms` | integer | Exclusive end timestamp for the query range, in milliseconds since the Unix epoch. | | `start_time_ms` | integer | (Optional) Inclusive start timestamp for the query range. If omitted, use `count_back`. | | `count_back` | integer | (Optional) Number of candles to return ending at `end_time_ms` when `start_time_ms` is omitted. | **JSON Response Payload** Returns a JSON array of mark price candle objects. Every element in the array is a JSON object: #### MarkPriceCandleMsg Mark price OHLC candle. | Field | Type | Description | |---|---|---| | `time` | u64 string | Candle open time in milliseconds since the Unix epoch. | | `open` | decimal string, 6 d.p. | Opening mark price in dollars. | | `high` | decimal string, 6 d.p. | Highest mark price in the interval, in dollars. | | `low` | decimal string, 6 d.p. | Lowest mark price in the interval, in dollars. | | `close` | decimal string, 6 d.p. | Closing mark price in dollars. | ```bash curl 'https://data.pascal.trade/api/v1/mark-price-candles?symbol=SIM_EVENT_1.MARKET_1&interval=1m&end_time_ms=1731536000000&count_back=2' ``` ```json { "status": "success", "data": [ { "time": "1731535960000", "open": "0.540000", "high": "0.580000", "low": "0.530000", "close": "0.560000" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Batch Mark Price Candles `GET /api/v1/mark-price-candles/batch` Returns sparse mark price OHLC candles for up to 20 symbols sharing one interval and time range. Uses the same range semantics as `GET /api/v1/mark-price-candles`, except `count_back` and explicit range queries are capped at 200 candles per symbol. Unknown symbols are omitted from `items`; returned items preserve request order. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | Comma-delimited list of market symbols to fetch mark price candles for. | | `interval` | string enum: `"1m"`, `"5m"`, `"15m"`, `"1h"`, `"4h"`, `"12h"`, `"1d"` | Candle aggregation interval. | | `end_time_ms` | integer | Exclusive end timestamp for the query range, in milliseconds since the Unix epoch. | | `start_time_ms` | integer | (Optional) Inclusive start timestamp for the query range. If omitted, use `count_back`. | | `count_back` | integer | (Optional) Number of candles to return ending at `end_time_ms` when `start_time_ms` is omitted. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `items` | array of [MarkPriceCandlesBatchItem](#markpricecandlesbatchitem) | Per-symbol candle results. Unknown requested symbols are omitted. | Each value in `items` is a JSON object: #### MarkPriceCandlesBatchItem Mark price candle data for one requested symbol. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Market symbol for this result. | | `candles` | array of [MarkPriceCandleMsg](#markpricecandlemsg) | Mark price candles for this symbol, sorted newest-first. | Each value in `candles` is a mark price candle object using [MarkPriceCandleMsg](#markpricecandlemsg). ```bash curl 'https://data.pascal.trade/api/v1/mark-price-candles/batch?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2&interval=1m&end_time_ms=1731536000000&count_back=2' ``` ```json { "status": "success", "data": { "items": [ { "symbol": "SIM_EVENT_1.MARKET_1", "candles": [ { "time": "1731535960000", "open": "0.540000", "high": "0.580000", "low": "0.530000", "close": "0.560000" } ] }, { "symbol": "SIM_EVENT_1.MARKET_2", "candles": [] } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Trades `GET /api/v1/trades` Returns trade history for up to 120 markets. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | Comma-delimited list of market symbols. | | `before_cursor` | string | (Optional) Opaque cursor returned as `next_cursor` from a prior page. Results are strictly older than this cursor. | | `at_or_before_seq` | u64 string | (Optional) Inclusive sequence number to start pagination from. Use this when you have an event (eg a fill or order) and want to start pagination from that event. After you get the first page, use the `next_cursor` to continue. | | `limit` | integer | (Optional) Maximum number of items to return. | **JSON Response Payload** Returns a paginated JSON object: | Field | Type | Description | |---|---|---| | `items` | array of [PublicTradeMsg](#publictrademsg) | Result page items. | | `next_cursor` | string | (Optional) Cursor for the next page of results. Omitted on the last page. | Every element in `items` is a JSON object: #### PublicTradeMsg Public trade message. This is not the same as the account-specific [FillMsg](#fillmsg), which is a view of *one side* of a trade, from one account's perspective. | Field | Type | Description | |---|---|---| | `trade_id` | u64 string | Unique ID for this trade. | | `symbol` | string ([Symbol](#market-symbols)) | Market symbol this trade belongs to. | | `taker_side` | string enum: `"BID"`, `"ASK"` | Side of the aggressive order that took liquidity. | | `price` | decimal string, 6 d.p. | Trade price in dollars. | | `size` | u64 string | Trade size in contracts. | | `maker` | base58 string | Account owner public key of the resting (maker) order. | | `taker` | base58 string | Account owner public key of the aggressive (taker) order. | | `maker_reduce_only` | boolean | (Optional) The maker order's reduce-only flag. Omitted for archival data. | | `taker_reduce_only` | boolean | (Optional) The taker order's reduce-only flag. Omitted for archival data. | | `maker_position_size_prev` | i64 string | (Optional) Signed maker position immediately before the trade. Omitted on older cached WebSocket trades; historical REST responses provide this field. | | `taker_position_size_prev` | i64 string | (Optional) Signed taker position immediately before the trade. Omitted on older cached WebSocket trades; historical REST responses provide this field. | | `seq` | u64 string | Matching engine sequence number when this trade occurred. | | `trade_ts_ms` | u64 string | Timestamp when this trade occurred, in milliseconds since the Unix epoch. | ```bash curl 'https://data.pascal.trade/api/v1/trades?symbols=SIM_EVENT_1.MARKET_1&limit=50' ``` ```json { "status": "success", "data": { "items": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "taker_side": "BID", "price": "0.550000", "size": "3", "maker": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "taker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "maker_reduce_only": false, "taker_reduce_only": false, "maker_position_size_prev": "10", "taker_position_size_prev": "0", "seq": "67891", "trade_ts_ms": "1731536000050" } ], "next_cursor": "00000000000000067890:0000000000" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` Next-page request using the `data.next_cursor` value above as `before_cursor`: ```bash curl 'https://data.pascal.trade/api/v1/trades?symbols=SIM_EVENT_1.MARKET_1&before_cursor=00000000000000067890%3A0000000000&limit=50' ``` ### Batch Trades `GET /api/v1/trades/batch` Returns the most recent trades for up to 20 markets, newest-first per market. This is the batch equivalent of the first page of `GET /api/v1/trades`. Unknown symbols are omitted from `items`; returned items preserve request order. `limit` defaults to 10 and must be between 1 and 50. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | Comma-delimited list of market symbols to fetch recent trades for. | | `limit` | integer | (Optional) Maximum number of recent trades to return per symbol. Defaults to 10. Maximum 50. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `items` | array of [TradesBatchItem](#tradesbatchitem) | Per-symbol recent trade results. Unknown requested symbols are omitted. | Each value in `items` is a JSON object: #### TradesBatchItem Recent trades for one requested symbol. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Market symbol for this result. | | `trades` | array of [PublicTradeMsg](#publictrademsg) | Recent trades for this symbol, sorted newest-first. | Each value in `trades` is a public trade object using [PublicTradeMsg](#publictrademsg). ```bash curl 'https://data.pascal.trade/api/v1/trades/batch?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2&limit=10' ``` ```json { "status": "success", "data": { "items": [ { "symbol": "SIM_EVENT_1.MARKET_1", "trades": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "taker_side": "BID", "price": "0.550000", "size": "3", "maker": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "taker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "maker_reduce_only": false, "taker_reduce_only": false, "maker_position_size_prev": "10", "taker_position_size_prev": "0", "seq": "67891", "trade_ts_ms": "1731536000050" } ] }, { "symbol": "SIM_EVENT_1.MARKET_2", "trades": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_2", "taker_side": "BID", "price": "0.550000", "size": "3", "maker": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "taker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "maker_reduce_only": false, "taker_reduce_only": false, "maker_position_size_prev": "10", "taker_position_size_prev": "0", "seq": "67891", "trade_ts_ms": "1731536000050" } ] } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Top Holders `GET /api/v1/top-holders` Returns the largest active position holders across up to 120 comma-delimited symbols, split into `longs` (positive position size) and `shorts` (negative position size) per market. `limit` applies to each side independently. Each side is ordered by absolute contract size descending, then owner public-key byte order ascending. Requested symbols for resolved markets are returned with empty `longs` and `shorts` arrays because they no longer have active positions. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | Comma-delimited list of market symbols. | | `limit` | integer | (Optional) Maximum number of holders to return per side per market. Defaults to 10. Maximum 50. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `holders` | object mapping string ([Symbol](#market-symbols)) to [MarketTopHoldersMsg](#markettopholdersmsg) | Largest active holders per side, keyed by market symbol. | Each value in `holders` is a JSON object: #### MarketTopHoldersMsg Largest active holders for one market, split by position side. | Field | Type | Description | |---|---|---| | `longs` | array of [TopHolderMsg](#topholdermsg) | Largest long positions, ordered by contracts descending. | | `shorts` | array of [TopHolderMsg](#topholdermsg) | Largest short positions, ordered by absolute contracts descending. | Every holder row in `longs` and `shorts` is a JSON object. See [Realized and Unrealized PnL](general.txt#realized-and-unrealized-pnl) for the `position` lifecycle, average entry price, and PnL semantics: #### TopHolderMsg Holder row for a market. | Field | Type | Description | |---|---|---| | `owner` | base58 string | Account owner public key. | | `position` | [PositionMsg](#positionmsg) | The holder's current position in this market. | ```bash curl 'https://data.pascal.trade/api/v1/top-holders?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2&limit=20' ``` ```json { "status": "success", "data": { "holders": { "SIM_EVENT_1.MARKET_1": { "longs": [ { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "position": { "symbol": "SIM_EVENT_1.MARKET_1", "size": "3", "cumulative_increasing_contracts": "3", "cumulative_increasing_notional_usd": "1.650000", "cumulative_reducing_contracts": "0", "cumulative_reducing_notional_usd": "0.000000", "cumulative_fees_usd": "0.000742", "realized_pnl_usd": "0.000000", "unrealized_pnl_usd": "0.000000", "mark_price": "0.550000", "average_entry_price": "0.550000", "seq": "67880", "update_seq": "67891", "open_ts_ms": "1731535900000", "update_ts_ms": "1731536000050" } } ], "shorts": [] }, "SIM_EVENT_1.MARKET_2": { "longs": [], "shorts": [ { "owner": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "position": { "symbol": "SIM_EVENT_1.MARKET_2", "size": "-80", "cumulative_increasing_contracts": "80", "cumulative_increasing_notional_usd": "44.000000", "cumulative_reducing_contracts": "0", "cumulative_reducing_notional_usd": "0.000000", "cumulative_fees_usd": "0.019800", "realized_pnl_usd": "0.000000", "unrealized_pnl_usd": "1.600000", "mark_price": "0.530000", "average_entry_price": "0.550000", "seq": "67880", "update_seq": "67891", "open_ts_ms": "1731535900000", "update_ts_ms": "1731536000050" } } ] } } }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ## Account Data Endpoints All account data endpoints use `{owner}` as a path parameter. `owner` is the account owner's Solana address, encoded as the base58 wallet public key that identifies the Pascal account. ### Account State Snapshot `GET /api/v1/accounts/{owner}` Returns open orders, positions, pending deposits/withdrawals, and collateral for one account, with orders and positions optionally filtered to a single market. The same shape is sent over the [account WebSocket channel](#account-channel). Successful responses may be served from cache for up to 1 second, so a snapshot fetched immediately after submitting an order may not reflect it yet. For real-time account state, subscribe to the [account WebSocket channel](#account-channel). **Query Parameters** | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | (Optional) Market symbol to filter by. When set, `orders` and `positions` contain only that market's entries; account-wide fields such as collateral are unaffected. Omit to include all markets. | | `exclude_orders` | boolean | (Optional) Return account state with an empty `orders` array. Defaults to false. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `has_deposited` | boolean | (Optional) True after the owner's first deposit is credited, including after all funds are withdrawn. Present in REST and WebSocket subscription snapshots. WebSocket updates include it when a deposit is credited. | | `username` | `Username` | (Optional) Owner-chosen handle. Present in REST responses and WebSocket subscription snapshots when set; always omitted from incremental WebSocket updates. | | `twitter_account_connection` | [TwitterAccountConnection](#twitteraccountconnection) | (Optional) Twitter account connected to the owner. Present in REST responses and WebSocket subscription snapshots when connected; always omitted from incremental WebSocket updates. | | `collateral_usd` | decimal string, 6 d.p. | (Optional) Current free collateral in USD. The account WebSocket channel omits this field when there has been no collateral change since the last snapshot. | | `rewards` | [AccountRewardsMsg](#accountrewardsmsg) | (Optional) Complete cumulative account reward totals. Present in REST and WebSocket snapshots. The account WebSocket channel omits this field when no reward component changed since the last snapshot. | | `lifetime_stats` | [AccountLifetimeStatsMsg](#accountlifetimestatsmsg) | (Optional) Complete account lifetime trade totals. Omitted if data is not available. The account WebSocket channel also omits this field when no trade has changed the totals since the last snapshot. | | `orders` | array of [OrderMsg](#ordermsg) | Open orders for this account, or all changed orders when used on the account WebSocket channel. | | `positions` | array of [PositionMsg](#positionmsg) | Current open positions for this account, or all changed positions when used on the account WebSocket channel. | | `pending_deposits` | array of [DepositMsg](#depositmsg) | Deposits being processed onchain but not yet credited. | | `pending_withdrawals` | array of [PendingWithdrawalMsg](#pendingwithdrawalmsg) | Active pending withdrawals. | When present, `twitter_account_connection` is a Twitter account connection object: #### TwitterAccountConnection Twitter account connected to one Pascal owner, with the sequence of its latest connect or refresh. | Field | Type | Description | |---|---|---| | `twitter_user_id` | u64 string | X's stable numeric account identifier. Use it, rather than the handle, to identify the X account. | | `twitter_handle` | string | Handle returned by X during the Owner's most recent connection or refresh. It can lag behind a later X-side rename. | | `connect_seq` | u64 string | (Optional) Sequence of the connect or refresh that set this connection, so it changes whenever the connection is refreshed. Omitted for older connections until their next connect or refresh. | When present, `rewards` is an account rewards object: #### AccountRewardsMsg Cumulative reward totals received by an account. | Field | Type | Description | |---|---|---| | `referral_rewards_received_usd` | decimal string, 6 d.p. | Cumulative referral fee share received in USD (this does not include VIP referral rewards). | | `builder_fees_received_usd` | decimal string, 6 d.p. | Cumulative builder fees received. | | `user_rewards_received_usd` | decimal string, 6 d.p. | Cumulative user rewards received. | When present, `lifetime_stats` is an account lifetime stats object: #### AccountLifetimeStatsMsg Complete maker and taker lifetime trade totals for an account. | Field | Type | Description | |---|---|---| | `maker` | [LifetimeStatsMsg](#lifetimestatsmsg) | Maker. | | `taker` | [LifetimeStatsMsg](#lifetimestatsmsg) | Taker. | Each `maker` and `taker` value is a liquidity-side lifetime stats object: #### LifetimeStatsMsg Lifetime trade totals for one account liquidity side. | Field | Type | Description | |---|---|---| | `volume_contracts` | string | Whole contracts traded over the account's lifetime. | | `weighted_volume` | string | (Optional) Lifetime weighted volume in fractional weighted contracts. Omitted until historical weighted volume is complete. | | `volume_notional_usd` | string | Lifetime sum of `price * size` for this account's fills on this liquidity side, in USD. Includes both position increases and reductions. | | `fees_paid_usd` | string | Lifetime venue fees paid in decimal USD; maker rebates are negative. Excludes builder fees paid, transfer fees, and rewards received. | `maker.fees_paid_usd + taker.fees_paid_usd` is the account's lifetime net fees paid to the exchange (this does not include [builder fees](general.txt#builder-codes) for orders submitted via third parties). Every element in `positions` is a JSON object. See [Realized and Unrealized PnL](general.txt#realized-and-unrealized-pnl) for position lifecycle, average entry price, and PnL semantics: #### PositionMsg An account's current position in a market. A position's lifecycle begins when the position becomes non-zero and ends when it touches or crosses zero. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Symbol of the market this position is in. | | `size` | i64 string | Signed position size. Positive for long, negative for short. | | `cumulative_increasing_contracts` | string | Cumulative contracts added during the current position lifecycle, including the opening fill. | | `cumulative_increasing_notional_usd` | string | Total USD value of contracts opened or added during the current position lifecycle, excluding fees. For longs, this is the cumulative entry cost. For shorts, cumulative entry cost is `cumulative_increasing_contracts * $1 - cumulative_increasing_notional_usd`. | | `cumulative_reducing_contracts` | string | Cumulative contracts reduced during the current position lifecycle. | | `cumulative_reducing_notional_usd` | string | Total USD value of contracts reduced during the current position lifecycle, excluding fees. For longs, this is the cumulative collateral returned by reductions. For shorts, cumulative collateral returned is `cumulative_reducing_contracts * $1 - cumulative_reducing_notional_usd`. | | `cumulative_fees_usd` | signed decimal string, 6 d.p. | Signed cumulative fees paid for the current position. Positive values are fees paid; negative values are rebates. | | `realized_pnl_usd` | signed decimal string, 6 d.p. | Cumulative realized PnL for the current position, excluding fees. PnL only realizes on closing trades. | | `unrealized_pnl_usd` | signed decimal string, 6 d.p. | Unrealized PnL for the remaining open position, excluding fees. This is the amount of PnL that would be realized if the remaining position were closed at mark price, using the full-precision entry basis. Reconstructing it from the floored `average_entry_price` can differ. | | `mark_price` | decimal string, 6 d.p. | Most recent mark price for the market. | | `average_entry_price` | decimal string, 6 d.p. | (Optional) Average entry price floored to 6 decimal places. Not included if position size is zero. | | `seq` | u64 string | The sequence number when this position was opened. Resets on touching zero. | | `update_seq` | u64 string | The sequence number when this position last changed. Advances on every fill and resolution. | | `open_ts_ms` | u64 string | When this position was opened. Resets on touching zero. | | `update_ts_ms` | u64 string | When this position last changed. Advances on every fill and resolution. | Every element in `pending_deposits` is a JSON object: #### DepositMsg Deposit lifecycle entry included in account updates. | Field | Type | Description | |---|---|---| | `deposit_id` | u64 string | Unique identifier for this deposit lifecycle. | | `owner` | base58 string | Account owner's wallet public key (Solana address). | | `amount` | decimal string, 6 d.p. | Deposited amount in USD. | | `transfer_status` | string enum: `"VERIFYING"`, `"PROCESSING"`, `"REJECTED"`, `"COMPLETED"` | Generic lifecycle state of this deposit. Deposits use `PROCESSING` or `COMPLETED`. | Every element in `pending_withdrawals` is a JSON object: #### PendingWithdrawalMsg Pending withdrawal lifecycle item exposed by read API snapshots and WebSocket updates. Snapshots contain active `VERIFYING` and `PROCESSING` withdrawals. WebSocket updates may also include terminal `REJECTED` or `COMPLETED` lifecycle updates so clients can remove the withdrawal from pending UI. | Field | Type | Description | |---|---|---| | `owner` | base58 string | Account owner's wallet public key (Solana address). | | `status` | string enum: `"VERIFYING"`, `"PROCESSING"`, `"REJECTED"`, `"COMPLETED"` | Current lifecycle state for this pending withdrawal. | | `amount` | decimal string, 6 d.p. | Amount of collateral being withdrawn in USD. | | `destination_authority` | base58 string | Solana wallet address that will receive the withdrawal. This is the owner/authority address, not the associated token account address. | | `chain_seq` | u64 string | (Optional) Withdrawal transfer chain sequence. Present when `status` is `PROCESSING` or `COMPLETED`. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB' ``` ```json { "status": "success", "data": { "has_deposited": true, "username": "Trader", "twitter_account_connection": { "twitter_user_id": "1234567890", "twitter_handle": "pascaldottrade", "connect_seq": "7" }, "collateral_usd": "100.000000", "rewards": { "referral_rewards_received_usd": "12.500000", "builder_fees_received_usd": "34.375000", "user_rewards_received_usd": "5.000000" }, "lifetime_stats": { "maker": { "volume_contracts": "1250", "weighted_volume": "250.000000", "volume_notional_usd": "625.000000", "fees_paid_usd": "-0.625000" }, "taker": { "volume_contracts": "750", "weighted_volume": "150.000000", "volume_notional_usd": "375.000000", "fees_paid_usd": "1.125000" } }, "orders": [ { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "7", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" } ], "positions": [ { "symbol": "SIM_EVENT_1.MARKET_1", "size": "3", "cumulative_increasing_contracts": "3", "cumulative_increasing_notional_usd": "1.650000", "cumulative_reducing_contracts": "0", "cumulative_reducing_notional_usd": "0.000000", "cumulative_fees_usd": "0.000742", "realized_pnl_usd": "0.000000", "unrealized_pnl_usd": "0.000000", "mark_price": "0.550000", "average_entry_price": "0.550000", "seq": "67880", "update_seq": "67891", "open_ts_ms": "1731535900000", "update_ts_ms": "1731536000050" } ], "pending_deposits": [ { "deposit_id": "7", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "amount": "100.000000", "transfer_status": "PROCESSING" } ], "pending_withdrawals": [ { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "status": "VERIFYING", "amount": "25.000000", "destination_authority": "29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Trading Keys `GET /api/v1/accounts/{owner}/trading-keys` Returns active trading keys for one account. **JSON Response Payload** Response `data` is an array of [TradingKeyMsg](#tradingkeymsg) objects. ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/trading-keys' ``` ```json { "status": "success", "data": [ { "trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1", "name": "api-doc-key", "expiration_ts_ms": "1732140800000", "open_order_count": "2" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Trading Key Accounts `GET /api/v1/trading-keys/{trading_key_pubkey}/accounts` Returns account owner addresses that have `{trading_key_pubkey}` permissioned as an active trading key. One trading key can be assigned to multiple accounts. **JSON Response Payload** Response `data` is an array of account owner addresses, encoded as base58 strings. ```bash curl 'https://data.pascal.trade/api/v1/trading-keys/2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1/accounts' ``` ```json { "status": "success", "data": [ "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB" ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Builder Stats `GET /api/v1/accounts/{owner}/builder-stats` Returns lifetime statistics for fills from orders that named `owner` as builder. Each fill contributes separately, so a trade contributes twice when both orders name `owner`. Accounts never named as builder return zero totals. **JSON Response Payload** #### AccountBuilderStatsMsg Account builder stats response: lifetime totals across fills of orders where this account was the builder. | Field | Type | Description | |---|---|---| | `builder_volume_contracts` | u128 string | Total contracts across attributed fills. | | `builder_volume_notional_usd` | decimal string, 6 d.p. | Lifetime sum of `price * size` for fills of orders naming this account as builder, in USD. | | `builder_fee_base_usd` | decimal string, 6 d.p. | Sum of size × price × (1 − price), rounded per fill. At a constant rate r, fees equal r × this value, up to per-fill rounding. | | `builder_fees_received_usd` | decimal string, 6 d.p. | Builder fees received, credited to free collateral at settlement. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/builder-stats' ``` ```json { "status": "success", "data": { "builder_volume_contracts": "12500", "builder_volume_notional_usd": "6875.000000", "builder_fee_base_usd": "3093.750000", "builder_fees_received_usd": "34.375000" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Account Bio `GET /api/v1/accounts/{owner}/bio` Returns `owner`'s Account Bio, or `null` when none is set. **JSON Response Payload** #### AccountBioMsg Response body for `GET /api/v1/accounts/{owner}/bio`. | Field | Type | Description | |---|---|---| | `account_bio` | `AccountBio` | (Optional) The owner's Account Bio, or `null` when none is set. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/bio' ``` ```json { "status": "success", "data": { "account_bio": "verify-42" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Taker Discount Terms `GET /api/v1/accounts/{owner}/taker-discount-terms` Returns the stored taker discount terms and fee-savings usage for `owner`. `taker_discount_terms` is absent when the owner has no stored terms. Stored terms discount the owner's taker fees by `taker_fee_discount_share` until `fee_savings_usd` crosses `fee_savings_limit_usd`; later taker fills receive no discount. See the [Referral Program](general.txt#referral-program) for the terms applied through referral attribution. **JSON Response Payload** #### AccountTakerDiscountTermsMsg Account taker discount terms response. `taker_discount_terms` is absent when the owner has no stored terms. | Field | Type | Description | |---|---|---| | `taker_discount_terms` | [TakerDiscountTermsMsg](#takerdiscounttermsmsg) | (Optional) The owner's stored terms; absent when the owner has none. | `taker_discount_terms`, when present, is a JSON object: #### TakerDiscountTermsMsg Stored taker discount terms and fee-savings usage for one owner. | Field | Type | Description | |---|---|---| | `taker_fee_discount_share` | decimal string, 6 d.p. | Fraction of the taker fee discounted while the fee-savings cap is not exhausted. | | `created_ts_ms` | u64 string | Timestamp when these terms were created, in milliseconds since the Unix epoch. | | `fee_savings_limit_usd` | decimal string, 6 d.p. | Maximum taker fee savings this owner can receive through these terms. | | `fee_savings_usd` | decimal string, 6 d.p. | Cumulative taker fee savings. The discount stops after this crosses the limit. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/taker-discount-terms' ``` ```json { "status": "success", "data": { "taker_discount_terms": { "taker_fee_discount_share": "0.050000", "created_ts_ms": "1731535900000", "fee_savings_limit_usd": "250.000000", "fee_savings_usd": "12.500000" } }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Referrals `GET /api/v1/accounts/{owner}/referrals` Returns up to the 500 most recently created Referrals for which `{owner}` is the Reward Owner: one entry per referred user, with the reward paid so far and the reward-paid cap. Referrals are included identically regardless of their original attribution mechanism. The response includes the owner's Generated and Custom Referral Codes and whether the Custom Code has been revoked. `can_generate_referral_code` and `can_claim_custom_referral_code` report whether the corresponding signed write requests would currently succeed; a submitted Custom Code may still be unavailable. `referral_reward_terms` is the terms applied to Referrals created for this owner in the future. Each existing Referral keeps the share and cap fixed when it was created. A Referral pays rewards until `reward_paid_usd` crosses `reward_paid_limit_usd`; later fills pay no reward. See the [Referral Program](general.txt#referral-program) for the current terms. **JSON Response Payload** #### OwnerReferralsMsg Owner referrals response. | Field | Type | Description | |---|---|---| | `referral_reward_terms` | [ReferralRewardTermsMsg](#referralrewardtermsmsg) | Terms applied to Referrals created for this owner in the future. Existing Referrals keep the share and limit fixed when they were created. | | `reward_paid_total_usd` | decimal string, 6 d.p. | Total reward paid to this owner across all their Referrals. | | `referral_count_total` | integer | Total number of this owner's Referrals, including any omitted from `referrals` by an endpoint response limit. | | `referrals` | array of [ReferralMsg](#referralmsg) | This owner's Referrals as Reward Owner, one per referred owner, newest first. | | `generated_referral_code` | string | (Optional) This owner's Generated Referral Code, if they have created one. | | `custom_referral_code` | string | (Optional) This owner's Custom Referral Code, including a revoked code retained for support context. | | `custom_referral_code_revoked` | boolean | Whether `custom_referral_code` has been revoked and no longer resolves for attribution. | | `can_generate_referral_code` | boolean | Whether a generate-referral-code request from this owner would currently succeed: Referral Codes are enabled, the owner has deposited and meets the weighted volume requirement, and neither a Generated Code nor an active Custom Code exists. | | `can_claim_custom_referral_code` | boolean | Whether the owner may currently claim a Custom Referral Code. The submitted code may still be unavailable. | `referral_reward_terms` is a JSON object: #### ReferralRewardTermsMsg Reward terms applied to a Reward Owner's future Referrals. | Field | Type | Description | |---|---|---| | `referral_reward_share` | decimal string, 6 d.p. | Fraction of the venue fee from each referred taker fill paid to the Reward Owner. | | `per_referred_owner_reward_paid_limit_usd` | decimal string, 6 d.p. | Maximum reward paid to the Reward Owner for a single Referred Owner. | Every element in `referrals` is a JSON object: #### ReferralMsg One Referral from its Reward Owner's perspective. | Field | Type | Description | |---|---|---| | `seq` | `ReferralSeq` | Monotonic referral creation sequence for stable ordering and future pagination. | | `referred_owner` | base58 string | Owner whose registration created this Referral. | | `created_ts_ms` | u64 string | Timestamp when the Referral was created, in milliseconds since the Unix epoch. | | `referral_reward_share` | decimal string, 6 d.p. | Reward share fixed when the Referral was created. | | `reward_paid_usd` | decimal string, 6 d.p. | Cumulative reward paid to the Reward Owner for this Referral. Rewards stop after this crosses the limit. | | `reward_paid_limit_usd` | decimal string, 6 d.p. | Maximum reward paid to the Reward Owner for this Referral. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/referrals' ``` ```json { "status": "success", "data": { "referral_reward_terms": { "referral_reward_share": "0.100000", "per_referred_owner_reward_paid_limit_usd": "10000.000000" }, "reward_paid_total_usd": "25.000000", "referral_count_total": 1, "referrals": [ { "seq": "42", "referred_owner": "J2xccRtuG43drESLYznHhLhQkLTdfepcKYbiQ9BsJVaf", "created_ts_ms": "1731535900000", "referral_reward_share": "0.100000", "reward_paid_usd": "25.000000", "reward_paid_limit_usd": "10000.000000" } ], "generated_referral_code": "k7wq2m4x", "custom_referral_code": "trader1", "custom_referral_code_revoked": false, "can_generate_referral_code": false, "can_claim_custom_referral_code": false }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Deposit Address `GET /api/v1/accounts/{owner}/deposit-address` Returns the wallet-facing deposit address for one account. **JSON Response Payload** #### DepositAddressMsg Wallet-facing deposit address data for a user owner. | Field | Type | Description | |---|---|---| | `owner` | base58 string | The account owner. | | `deposit_address` | base58 string | (Optional) The deposit address PDA corresponding to the account owner. Present only after the account has registered a deposit address, and immutable once set. | | `registered` | boolean | Whether the deposit address is registered. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/deposit-address' ``` ```json { "status": "success", "data": { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "deposit_address": "DtzBRq9hAREJZKj1fuHsgrWrtaeXmbYQdXsmMrd54arG", "registered": true }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Fill History `GET /api/v1/accounts/{owner}/fills` Returns fill history for one account, optionally filtered by up to 50 symbols. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | (Optional) Comma-delimited list of market symbols to filter by. Empty returns fills for all markets. | | `before_cursor` | string | (Optional) Opaque cursor returned as `next_cursor` from a prior page. Results are strictly older than this cursor. | | `at_or_before_seq` | u64 string | (Optional) Inclusive sequence number to start pagination from. Use this when you have an event (eg a fill or order) and want to start pagination from that event. After you get the first page, use the `next_cursor` to continue. | | `limit` | integer | (Optional) Maximum number of items to return. | **JSON Response Payload** Returns a paginated JSON object: | Field | Type | Description | |---|---|---| | `items` | array of [FillMsg](#fillmsg) | Result page items. | | `next_cursor` | string | (Optional) Cursor for the next page of results. Omitted on the last page. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/fills?symbols=SIM_EVENT_1.MARKET_1&limit=50' ``` ```json { "status": "success", "data": { "items": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "reduce_only": false, "fill_price": "0.550000", "fill_size": "3", "liquidity": "TAKER", "counterparty": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "order_id": "987654321", "client_order_id": "42", "fee_usd": "0.000742", "collateral_change_usd": "-1.650742", "position_size_prev": "0", "realized_pnl_usd": "0.000000", "position_open_seq": "67891", "position_open_ts_ms": "1731536000050", "seq": "67891", "trade_ts_ms": "1731536000050" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` Note on realized PnL: Prior to 2026-08-05 at 17:55:51.976 UTC, Pascal used different logic for attributing PnL across fills. Reducing, increasing, and then reducing a position would lead to different values of realized PnL per fill than the current logic would compute, though the sum of realized PnL across all fills in a position would be unaffected. Ie the updated logic only affects how realized PnL is attributed across trades, not the actual movement of funds. ### Transfer History `GET /api/v1/accounts/{owner}/transfers` Returns deposit, withdrawal, and reward history for one account. Reward transfers have `type: "REWARD"` and a `user_reward_reason`. Fee share income from referrals and builder fees received are reported separately in account `rewards` totals and [PnL history](#pnl-history), not as reward transfers. **Query Parameters** | Field | Type | Description | |---|---|---| | `before_cursor` | string | (Optional) Opaque cursor returned as `next_cursor` from a prior page. Results are strictly older than this cursor. | | `at_or_before_seq` | u64 string | (Optional) Inclusive sequence number to start pagination from. Use this when you have an event (eg a fill or order) and want to start pagination from that event. After you get the first page, use the `next_cursor` to continue. | | `limit` | integer | (Optional) Maximum number of items to return. | **JSON Response Payload** Returns a paginated JSON object: | Field | Type | Description | |---|---|---| | `items` | array of [TransferMsg](#transfermsg) | Result page items. | | `next_cursor` | string | (Optional) Cursor for the next page of results. Omitted on the last page. | Every element in `items` is a JSON object: #### TransferMsg Transfer ledger entry included in account updates. | Field | Type | Description | |---|---|---| | `type` | string enum: `"DEPOSIT"`, `"WITHDRAWAL"`, `"REWARD"` | Kind of balance movement: deposit, withdrawal, or user reward. | | `status` | string enum: `"VERIFYING"`, `"PROCESSING"`, `"REJECTED"`, `"COMPLETED"` | Current user-facing lifecycle state for this transfer. | | `collateral_change_usd` | signed decimal string, 6 d.p. | Signed change to account collateral. For withdrawals, the full negative debit, including the withdrawal fee. | | `fee_usd` | signed decimal string, 6 d.p. | Transfer fee paid by the user. Positive values are fees paid. | | `net_usd` | signed decimal string, 6 d.p. | For withdrawals, the amount sent to the destination after fees, expressed as a negative value. For deposits and rewards, the positive amount credited to account collateral. | | `seq` | u64 string | Sequence number where account collateral was updated. | | `transfer_ts_ms` | u64 string | Timestamp when account collateral was updated. | | `chain_seq` | u64 string | (Optional) Chain sequence used to correlate withdrawal lifecycle updates. Present only for withdrawal transfers. | | `user_reward_reason` | string | (Optional) Product reason for a user reward. Absent for deposits and withdrawals. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/transfers?limit=50' ``` ```json { "status": "success", "data": { "items": [ { "type": "DEPOSIT", "status": "COMPLETED", "collateral_change_usd": "100.000000", "fee_usd": "0.000000", "net_usd": "100.000000", "seq": "67880", "transfer_ts_ms": "1731535900000" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Position Resolution History `GET /api/v1/accounts/{owner}/position-resolutions` Returns historical position-resolution payouts for one account. **Query Parameters** | Field | Type | Description | |---|---|---| | `before_cursor` | string | (Optional) Opaque cursor returned as `next_cursor` from a prior page. Results are strictly older than this cursor. | | `at_or_before_seq` | u64 string | (Optional) Inclusive sequence number to start pagination from. Use this when you have an event (eg a fill or order) and want to start pagination from that event. After you get the first page, use the `next_cursor` to continue. | | `limit` | integer | (Optional) Maximum number of items to return. | **JSON Response Payload** Returns a paginated JSON object: | Field | Type | Description | |---|---|---| | `items` | array of [PositionResolutionMsg](#positionresolutionmsg) | Result page items. | | `next_cursor` | string | (Optional) Cursor for the next page of results. Omitted on the last page. | Every element in `items` is a JSON object: #### PositionResolutionMsg Resolution message for API responses. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Market symbol for the resolved position. | | `resolution` | decimal string, 6 d.p. | Final settlement price in dollars. | | `size` | i64 string | Signed position size settled by this resolution. | | `collateral_change_usd` | decimal string, 6 d.p. | Net collateral change from settling this position. | | `realized_pnl_usd` | signed decimal string, 6 d.p. | Realized PnL from this resolution, excluding fees. | | `position_open_seq` | u64 string | (Optional) Opening sequence of the position settled by this resolution. | | `position_open_ts_ms` | u64 string | (Optional) Opening timestamp, in milliseconds since the Unix epoch, of the position settled by this resolution. | | `seq` | u64 string | Matching engine sequence number when this resolution was applied. | | `position_resolution_ts_ms` | u64 string | Timestamp when this position was resolved, in milliseconds since the Unix epoch. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/position-resolutions?limit=50' ``` ```json { "status": "success", "data": { "items": [ { "symbol": "SIM_EVENT_1.MARKET_1", "resolution": "1.000000", "size": "3", "collateral_change_usd": "3.000000", "realized_pnl_usd": "1.350000", "position_open_seq": "67891", "position_open_ts_ms": "1731536000050", "seq": "67891", "position_resolution_ts_ms": "1731536000050" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Closed Position History `GET /api/v2/accounts/{owner}/position-instances` Returns every closed position for one account: positions that were sold out, flipped, or held until resolution. Each item covers one position from the fill that opened it to the fill or resolution that closed it. Results can be filtered by up to 100 symbols and sorted by close time or realized PnL in either direction. All pages list closed positions as of the first page, so none are skipped or repeated, and positions that close while paging appear in the next request without `before_cursor`. A `before_cursor` must be sent with the same `symbols`, `order_by`, and `sort_order` as the page that returned it. A server that has not yet indexed the first page's history returns HTTP 503 with `code: "HISTORY_NOT_READY"`. **Query Parameters** | Field | Type | Description | |---|---|---| | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | (Optional) Comma-delimited list of market symbols to filter by. Empty returns all markets. | | `order_by` | string enum: `"close_time"`, `"realized_pnl"` | (Optional) Field used to order results. Defaults to `close_time`. | | `sort_order` | string enum: `"asc"`, `"desc"` | (Optional) Direction used to sort results. Defaults to `desc`. | | `before_cursor` | string | (Optional) The `next_cursor` from the previous page. Results continue from there in the requested order. Send the same `symbols`, `order_by`, and `sort_order` as the previous page. | | `limit` | integer | (Optional) Maximum number of position instances to return. Defaults to 1,000. Maximum 1,000. | **JSON Response Payload** Returns a paginated JSON object: | Field | Type | Description | |---|---|---| | `items` | array of [PositionHistoryMsg](#positionhistorymsg) | Result page items. | | `next_cursor` | string | (Optional) Cursor for the next page of results. Omitted on the last page. | Every element in `items` is a JSON object. Realized PnL excludes fees: #### PositionHistoryMsg Historical performance for one position instance or a group of position instances with the same market and side. For a group, cumulative totals sum across the included completed position lifecycles. | Field | Type | Description | |---|---|---| | `symbol` | string ([Symbol](#market-symbols)) | Market symbol. | | `position_side` | string enum: `"LONG"`, `"SHORT"` | Direction of the position instance. | | `position_instance_count` | string | Number of position instances. This is `"1"` for a single position instance. | | `held_until_resolution` | boolean | Whether the position instance was held until market resolution. For a position aggregate, this describes the most recently closed position instance. | | `realized_pnl_usd` | string | Gross realized PnL, excluding fees, in decimal USD. | | `cumulative_increasing_contracts` | string | Cumulative contracts added during the position's lifecycle, including the opening fill. Counts only the opening part of a flip. Later reductions do not subtract from this total. | | `cumulative_increasing_notional_usd` | string | Total USD value of contracts opened or added during the position lifecycle, excluding fees. Contracts are valued at their execution price. For longs, this is the cumulative entry cost. For shorts, cumulative entry cost is `cumulative_increasing_contracts * $1 - cumulative_increasing_notional_usd`. Reductions do not decrease this total. On a flip, only contracts opening the new position count. | | `cumulative_reducing_notional_usd` | string | Total USD value of contracts closed during the position lifecycle, excluding fees. Contracts are valued at their execution price, or settlement price on market resolution. For longs, this is the cumulative collateral returned. For shorts, cumulative collateral returned is `cumulative_increasing_contracts * $1 - cumulative_reducing_notional_usd`, because completed lifecycles close all added contracts. A flip contributes only its closing part. | | `average_increasing_price` | decimal string, 6 d.p. | Contract-weighted average price used to increase the position, floored to 6 decimal places. | | `average_reducing_price` | decimal string, 6 d.p. | Contract-weighted average price used to reduce the position, floored to 6 decimal places. | | `open_seq` | u64 string | Sequence when the position instance opened. For a position aggregate, this is the earliest opening sequence. | | `open_ts_ms` | u64 string | Timestamp when the position instance opened. For a position aggregate, this is the earliest opening timestamp. | | `close_seq` | u64 string | Sequence when the position instance closed. For a position aggregate, this is the latest closing sequence. | | `close_ts_ms` | u64 string | Timestamp when the position instance closed. For a position aggregate, this is the latest closing timestamp. | ```bash curl 'https://data.pascal.trade/api/v2/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/position-instances?order_by=realized_pnl&sort_order=desc&limit=50' ``` ```json { "status": "success", "data": { "items": [ { "symbol": "SIM_EVENT_1.MARKET_1", "position_side": "LONG", "position_instance_count": "1", "held_until_resolution": true, "realized_pnl_usd": "1.350000", "cumulative_increasing_contracts": "3", "cumulative_increasing_notional_usd": "1.650000", "cumulative_reducing_notional_usd": "3.000000", "average_increasing_price": "0.550000", "average_reducing_price": "1.000000", "open_seq": "67891", "open_ts_ms": "1731536000050", "close_seq": "67892", "close_ts_ms": "1731536000100" } ], "next_cursor": "realized_pnl:desc:00000000000001350000:00000000000000067892:00000000000000067892:0000000001" }, "seq": "67892", "state_time_ms": "1731536000100", "server_time_ms": "1731536000123" } ``` ### PnL History `GET /api/v1/accounts/{owner}/history` Returns account value, total PnL, trading PnL, referral rewards received, builder fees received, and user rewards received for one account. One response includes all four timeframes; select an entry in `items` to change the chart range. **Timeframes and resolution** | `timeframe` | Lookback | Historical point spacing | | --- | --- | --- | | `day` | 24 hours | 30 minutes | | `week` | 7 days | 4 hours | | `month` | 30 days, not a calendar month | 12 hours | | `all_time` | All available account history | 24 hours | All windows end at the latest completed 30-minute history boundary. The final `all_time` interval can be shorter than 24 hours, for example when 09:00 daily samples are followed by a final point at the latest history boundary of 15:30 that day, leaving a 6.5-hour interval. | Series | Accounting treatment | | --- | --- | | `account_value_history` | Free collateral plus the marked value of open positions. Includes deposits, withdrawals, realized and unrealized trading results, trading fees, maker rebates, and all rewards received. | | `total_pnl_history` | Account value minus net deposits. Includes trading PnL, referral rewards, builder fees, and user rewards received. | | `trading_pnl_history` | Realized and unrealized trading PnL after venue and builder fees, including maker rebates. Equals account value minus net deposits, referral rewards, user rewards, and builder fees received. | | `referral_rewards_received_history` | Cumulative referral fee share received in USD. VIP referral rewards are user rewards and appear in `user_rewards_received_history`. | | `builder_fees_received_history` | Cumulative builder fees credited to this account as a builder. Excludes fees the account pays as a trader, maker rebates, referral rewards, and user rewards. | | `user_rewards_received_history` | Cumulative rewards paid from Pascal reward campaigns, for example Midterms 2026 weekly trading rewards and VIP referral rewards. Use `user_reward_reason` in [transfer history](#transfer-history) for per-reason detail. Excludes referral fee share and builder fees received. | Notes: - Net deposits are credited deposits minus withdrawals debited from account collateral. - Pending deposits do not increase account value. - At every point, `total_pnl_history` equals `trading_pnl_history` plus `referral_rewards_received_history`, `builder_fees_received_history`, and `user_rewards_received_history`. - Maker rebates are part of trading PnL. **Historical data and the live point** Every point returned by this endpoint, including the last point, comes from stored history. The endpoint does not append a live update point. History advances in completed 30-minute intervals. The response envelope's `seq` and `state_time_ms` describe indexed progress. The cutoff is the last historical point's timestamp: for a 14:00 cutoff, history includes events before 14:00, so the live calculation must account for transfers at or after 14:00. To add a live point, combine the last historical sample with the [account state snapshot](#account-state-snapshot), which includes position marks, and [transfer history](#transfer-history). Refresh these through REST polling, or use the [account WebSocket channel](#account-channel) and [market update channel](#market-update-channel) for ongoing account and mark updates: ```text live_account_value = current collateral + current marked position value live_total_pnl = last historical total PnL + (live_account_value - last historical account value) - deposit and withdrawal collateral changes since the historical cutoff live_trading_pnl = live_total_pnl - live referral rewards received - live builder fees received - live user rewards received ``` Deposit and withdrawal changes sum `collateral_change_usd` for `DEPOSIT` and `WITHDRAWAL` transfers. Do not subtract `REWARD` transfers: user rewards are part of total PnL. The live referral rewards, builder fees, and user rewards received are the current `rewards` totals: `referral_rewards_received_usd`, `builder_fees_received_usd`, and `user_rewards_received_usd`. The Pascal portfolio chart applies this approach: it appends a live point to the historical series and rebases PnL and reward lines to the selected range. Its `All` view uses the finest available window that covers the account's history, so a young account's `All` chart can have finer spacing than the API's daily `all_time` series. **JSON Response Payload** | Field | Type | Description | |---|---|---| | `items` | array of [AccountHistoryTimeseries](#accounthistorytimeseries) | Account value and PnL series, one entry per standard timeframe. | Every element in `items` is a JSON object: #### AccountHistoryTimeseries Account history series for one timeframe. | Field | Type | Description | |---|---|---| | `timeframe` | string enum: `"day"`, `"week"`, `"month"`, `"all_time"` | Chart lookback window for this series. | | `data` | [AccountHistoryTimeseriesData](#accounthistorytimeseriesdata) | Sampled account value and PnL points for this timeframe. | `data` object: #### AccountHistoryTimeseriesData Chart lines for account history. | Field | Type | Description | |---|---|---| | `account_value_history` | array of [TimeseriesPoint](#timeseriespoint) | Total account value in USD: free collateral plus marked open positions. Includes net deposits, trading fees, maker rebates, and all rewards received. | | `total_pnl_history` | array of [TimeseriesPoint](#timeseriespoint) | Cumulative total PnL in USD: account value minus net deposits. Equals trading PnL plus referral rewards, builder fees, and user rewards received. Values are cumulative since account inception. | | `trading_pnl_history` | array of [TimeseriesPoint](#timeseriespoint) | Cumulative realized and unrealized trading PnL in USD, after venue and builder fees paid and including maker rebates. Excludes net deposits, referral rewards, builder fees received, and user rewards. Values are cumulative since account inception. | | `referral_rewards_received_history` | array of [TimeseriesPoint](#timeseriespoint) | Cumulative referral fee share received in USD. VIP referral rewards are user rewards and appear in `user_rewards_received_history`. Values are cumulative since account inception. | | `builder_fees_received_history` | array of [TimeseriesPoint](#timeseriespoint) | Cumulative builder fees received in USD as a builder, not fees paid as a trader. Values are cumulative since account inception. | | `user_rewards_received_history` | array of [TimeseriesPoint](#timeseriespoint) | Cumulative rewards paid from Pascal reward campaigns in USD, for example Midterms 2026 weekly trading rewards and VIP referral rewards. Excludes referral fee share and builder fees received. Values are cumulative since account inception. | Every value in an account-history series is a two-element array `[timestamp_ms, decimal_string]`: #### TimeseriesPoint One chart point, serialized as `[timestamp_ms, decimal_string]`. | Field | Type | Description | |---|---|---| | `0` | u64 string | Timestamp in milliseconds. | | `1` | string | Decimal string value. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/history' ``` ```json { "status": "success", "data": { "items": [ { "timeframe": "day", "data": { "account_value_history": [ [ "1731536000000", "1000.000000" ] ], "total_pnl_history": [ [ "1731536000000", "30.000000" ] ], "trading_pnl_history": [ [ "1731536000000", "20.000000" ] ], "referral_rewards_received_history": [ [ "1731536000000", "3.000000" ] ], "builder_fees_received_history": [ [ "1731536000000", "2.000000" ] ], "user_rewards_received_history": [ [ "1731536000000", "5.000000" ] ] } } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Market PnL Movers `GET /api/v1/accounts/{owner}/pnl-movers` Returns the markets with the largest changes in an account's trading PnL, including closed and resolved positions. PnL combines realized and unrealized gains and losses after fees. Current PnL is live. Historical starts advance in 30-minute steps. Limited to 60 requests per minute per IP, shared across accounts and windows. Returns `SERVICE_UNAVAILABLE` until PnL data is ready, and again if historical starts fall more than an hour behind. While they are delayed by less than that, `start_ms` still identifies the actual start of the returned interval. **Query Parameters** | Field | Type | Description | |---|---|---| | `window` | string enum: `"7d"`, `"24h"`, `"12h"`, `"6h"` | Trailing window: `7d`, `24h`, `12h`, or `6h`. | | `limit` | integer | (Optional) Number of contributions to return, from 1 through 100. Defaults to 5. | | `symbols` | comma-separated array of string ([Symbol](#market-symbols)) | (Optional) Comma-delimited list of up to 100 market symbols to filter by. Empty returns all markets. | | `order_by` | string enum: `"abs_pnl_change"`, `"pnl_change"` | (Optional) Field used to order contributions. Defaults to `abs_pnl_change`. | | `sort_order` | string enum: `"asc"`, `"desc"` | (Optional) Direction used to sort contributions. Defaults to `desc`. | **JSON Response Payload** | Field | Type | Description | |---|---|---| | `owner` | base58 string | Pascal account owner. | | `window` | string enum: `"7d"`, `"24h"`, `"12h"`, `"6h"` | Requested trading PnL window. | | `start_ms` | u64 string | Actual historical start, aligned to a closed 30-minute boundary. | | `as_of_ms` | u64 string | Live endpoint's exchange time, matching the response context. | | `total_markets` | u32 string | Requested markets with nonzero PnL change before applying `limit`. | | `total_pnl_change_usd` | signed decimal string, 6 d.p. | Signed trading PnL change across the requested markets, before applying `limit`, in USD. | | `entries` | array of [AccountMarketPnlContribution](#accountmarketpnlcontribution) | Sorted by `order_by` in `sort_order`, then market ID ascending. | Every element in `entries` is a JSON object: #### AccountMarketPnlContribution One market's signed contribution to account trading PnL. | Field | Type | Description | |---|---|---| | `market_id` | `MarketId` | Market traded, combining both directions and all position lifecycles. | | `symbol` | string ([Symbol](#market-symbols)) | Market identifier used by market-data endpoints and display metadata. | | `pnl_change_usd` | signed decimal string, 6 d.p. | Realized plus unrealized PnL change, net of venue and builder fees, in USD. | | `mark_price` | decimal string, 6 d.p. | (Optional) Current canonical mark, including the final price for resolved markets. | | `mark_price_start` | decimal string, 6 d.p. | (Optional) Canonical mark at `start_ms`. Omitted when historical pricing is unavailable. | | `position_size_start` | i64 string | Signed position size in contracts at `start_ms`. | | `position_size` | i64 string | Signed position size in contracts at `as_of_ms`. Zero when closed. | | `resolved` | boolean | Whether the market has resolved, regardless of how the account exited its position. | ```bash curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/pnl-movers?window=7d&limit=1&order_by=abs_pnl_change&sort_order=desc' ``` ```json { "status": "success", "data": { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "window": "7d", "start_ms": "1730930400000", "as_of_ms": "1731536000050", "total_markets": "8", "total_pnl_change_usd": "3410.000000", "entries": [ { "market_id": "0", "symbol": "SIM_EVENT_1.MARKET_1", "pnl_change_usd": "-2236.000000", "mark_price": "0.180000", "mark_price_start": "0.450000", "position_size_start": "10000", "position_size": "8000", "resolved": false } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ## Market Data WebSocket `GET ` upgrades to a WebSocket connection. Clients send JSON text frames. The server allows up to 10,000 concurrent connections and 5,000 subscriptions per connection. ### Client Messages Clients send JSON text frames. There are three message types: `subscribe`, `unsubscribe`, and `ping`. **Subscribe** to one or more channels. The server responds with a `snapshot` on each channel, followed by `update` messages as state changes. ```json { "type": "subscribe", "channels": [ { "channel": "book", "symbol": "SIM_EVENT_1.MARKET_1" }, { "channel": "trades", "symbol": "SIM_EVENT_1.MARKET_1" }, { "channel": "all_trades" } ] } ``` **Unsubscribe** uses the same shape with `type: "unsubscribe"`. ```json { "type": "unsubscribe", "channels": [ { "channel": "book", "symbol": "SIM_EVENT_1.MARKET_1" } ] } ``` **Ping** keeps the connection alive. The server responds with the literal frame `{"type":"pong"}`. ```json { "type": "ping" } ``` #### Channels Each entry in `channels` is one of: ```json {"channel": "book", "symbol": ""} {"channel": "trades", "symbol": ""} {"channel": "candles", "symbol": "", "interval": "1m"} {"channel": "mark_price_candles", "symbol": "", "interval": "1m"} {"channel": "account", "owner": ""} {"channel": "market_update"} ``` - `book`: order book snapshots and price-level updates for one market. - `trades`: trades for one market. - `candles`: current in-progress candle for one market and interval; `interval` must be one of `1m`, `5m`, `15m`, `1h`, `4h`, `12h`, and `1d`. - `mark_price_candles`: current in-progress mark price candle for one market and interval; `interval` must be one of `1m`, `5m`, `15m`, `1h`, `4h`, `12h`, and `1d`. - `account`: live account updates for one wallet. - `market_update`: market listings and updates. ### Server Messages For each subscribed channel, the server emits a `snapshot` message followed by `update` messages whenever the channel state changes. Error messages flatten the channel descriptor with `type: "error"` and error fields. **Message type enum** #### WsMessageType Type of WebSocket message sent to clients. | Value | Description | |---|---| | `"snapshot"` | A snapshot message is sent when a client first subscribes. | | `"update"` | An update message is sent when channel state changes. | | `"error"` | An error message reports a client- or channel-level failure. It may be sent in response to a malformed or rejected request, or emitted later for a subscribed channel that can no longer be served. | **Shared server context fields** | Field | Type | Description | |---|---|---| | `seq` | u64 string | (Optional) Matching engine sequence number. Not present if the request is rejected before reaching the matching engine, for example due to invalid request data. | | `state_time_ms` | u64 string | (Optional) Matching engine timestamp as of the last matching round. Not present if the request is rejected before reaching the matching engine, for example due to invalid request data. | | `server_time_ms` | u64 string | API server timestamp when this result was produced. Requests in a batch request can resolve at different times, so items in one response may carry different timestamps. | **Error data object** | Field | Type | Description | |---|---|---| | `code` | [ApiErrorCode](#apierrorcode) | Stable, machine-readable error code. | | `message` | string | Human-readable error message. May evolve over time. For debugging and display only. | ```json { "channel": "book", "symbol": "SIM_EVENT_1.MARKET_1", "type": "error", "data": { "code": "SYMBOL_NOT_FOUND", "message": "symbol not found: symbol=SIM_EVENT_1.MARKET_1" }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Book Channel L2 book feed for one market. The first message after subscription is always a `snapshot` message with the full order book state and [market spec](#marketspec). Subsequent messages are `update`s which include price level changes (level removals are represented with `size = 0`) and any changes to the market spec. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"book"` | Channel discriminator. | | `symbol` | string ([Symbol](#market-symbols)) | Market symbol to stream order book updates for. | **JSON Message Payload** Payload uses [OrderBookMsg](#orderbookmsg). ```json { "channel": "book", "symbol": "SIM_EVENT_1.MARKET_1", "type": "snapshot", "data": { "asks": [ [ "0.560000", "12" ], [ "0.570000", "20" ] ], "bids": [ [ "0.540000", "15" ], [ "0.530000", "7" ] ], "spec": { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2 } }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ```json { "channel": "book", "symbol": "SIM_EVENT_1.MARKET_1", "type": "update", "data": { "asks": [ [ "0.570000", "0" ] ], "bids": [ [ "0.540000", "15" ] ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Trades Channel Trades for one market. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"trades"` | Channel discriminator. | | `symbol` | string ([Symbol](#market-symbols)) | Market symbol to stream public trades for. | **JSON Message Payload** `data` is a JSON array of trade objects. Every element in the array is a JSON object using [PublicTradeMsg](#publictrademsg). ```json { "channel": "trades", "symbol": "SIM_EVENT_1.MARKET_1", "type": "update", "data": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "taker_side": "BID", "price": "0.550000", "size": "3", "maker": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "taker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "maker_reduce_only": false, "taker_reduce_only": false, "maker_position_size_prev": "10", "taker_position_size_prev": "0", "seq": "67891", "trade_ts_ms": "1731536000050" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### All Trades Channel Recent public trades across all markets. The initial `snapshot` contains up to 100 recent trades. Subsequent `update` messages contain every new trade since the last update. Snapshots and updates are ordered chronologically, oldest first. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"all_trades"` | Channel discriminator. | **JSON Message Payload** `data` is a JSON array of trade objects. Every element in the array is a JSON object using [PublicTradeMsg](#publictrademsg). ```json { "channel": "all_trades", "type": "snapshot", "data": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "taker_side": "BID", "price": "0.550000", "size": "3", "maker": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "taker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "maker_reduce_only": false, "taker_reduce_only": false, "maker_position_size_prev": "10", "taker_position_size_prev": "0", "seq": "67891", "trade_ts_ms": "1731536000050" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ```json { "channel": "all_trades", "type": "update", "data": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "taker_side": "BID", "price": "0.550000", "size": "3", "maker": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "taker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "maker_reduce_only": false, "taker_reduce_only": false, "maker_position_size_prev": "10", "taker_position_size_prev": "0", "seq": "67891", "trade_ts_ms": "1731536000050" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Filtered Trades Channel Recent public trades across all markets, filtered to highlight larger trades. Use `all_trades` for the complete feed. The initial `snapshot` contains up to 100 recent trades from this feed. Subsequent `update` messages contain new trades that pass the filter. Both use the same trade objects as `all_trades` and are ordered chronologically, oldest first. Filter criteria may change over time. Changes apply to new trades and do not revise the recent history. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"filtered_trades"` | Channel discriminator. | **JSON Message Payload** `data` is a JSON array of [PublicTradeMsg](#publictrademsg) objects. ### Candles Channel Current in-progress candle for one market and interval. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"candles"` | Channel discriminator. | | `symbol` | string ([Symbol](#market-symbols)) | Market symbol to stream candles for. | | `interval` | string enum: `"1m"`, `"5m"`, `"15m"`, `"1h"`, `"4h"`, `"12h"`, `"1d"` | Candle aggregation interval. | **JSON Message Payload** `data` is a JSON array of candle objects. Every element in the array uses [CandleMsg](#candlemsg). ```json { "channel": "candles", "symbol": "SIM_EVENT_1.MARKET_1", "interval": "1m", "type": "update", "data": [ { "time": "1731535960000", "open": "0.520000", "high": "0.570000", "low": "0.510000", "close": "0.550000", "volume": "120", "taker_buy_volume": "70", "taker_sell_volume": "50", "volume_notional": "66.000000", "taker_buy_volume_notional": "38.500000", "taker_sell_volume_notional": "27.500000", "trade_count": "8", "taker_buy_count": "5", "taker_sell_count": "3" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Mark Price Candles Channel Current in-progress mark price candle for one market and interval. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"mark_price_candles"` | Channel discriminator. | | `symbol` | string ([Symbol](#market-symbols)) | Market symbol to stream mark price candles for. | | `interval` | string enum: `"1m"`, `"5m"`, `"15m"`, `"1h"`, `"4h"`, `"12h"`, `"1d"` | Candle aggregation interval. | **JSON Message Payload** `data` is a JSON array of mark price candle objects. Every element in the array uses [MarkPriceCandleMsg](#markpricecandlemsg). ```json { "channel": "mark_price_candles", "symbol": "SIM_EVENT_1.MARKET_1", "interval": "1m", "type": "update", "data": [ { "time": "1731535960000", "open": "0.540000", "high": "0.580000", "low": "0.530000", "close": "0.560000" } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ### Account Channel Live account updates for one account. The first message after subscription is always a `snapshot` message, containing the same fields as the [Account State Snapshot](#account-state-snapshot). Subsequent `update` messages only populate fields which have changed since the most recent message. For example, if you cancel an order, you will see one update inside the `order_updates` array. If an order fills, you will see an update with changes in `order_updates`, `positions`, and `collateral_usd`. We chose this consolidated shape to make it easy for consumers to atomically register updates to the account state. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"account"` | Channel discriminator. | | `owner` | base58 string | Account owner's wallet public key (Solana address). | | `exclude_orders` | boolean | (Optional) When true, the initial snapshot contains an empty `orders` array and subsequent order updates are not sent. Defaults to false. | **JSON Message Payload** | Field | Type | Description | |---|---|---| | `has_deposited` | boolean | (Optional) True after the owner's first deposit is credited, including after all funds are withdrawn. Present in REST and WebSocket subscription snapshots. WebSocket updates include it when a deposit is credited. | | `username` | `Username` | (Optional) Owner-chosen handle. Present in REST responses and WebSocket subscription snapshots when set; always omitted from incremental WebSocket updates. | | `twitter_account_connection` | [TwitterAccountConnection](#twitteraccountconnection) | (Optional) Twitter account connected to the owner. Present in REST responses and WebSocket subscription snapshots when connected; always omitted from incremental WebSocket updates. | | `collateral_usd` | decimal string, 6 d.p. | (Optional) Current free collateral in USD. The account WebSocket channel omits this field when there has been no collateral change since the last snapshot. | | `rewards` | [AccountRewardsMsg](#accountrewardsmsg) | (Optional) Complete cumulative account reward totals. Present in REST and WebSocket snapshots. The account WebSocket channel omits this field when no reward component changed since the last snapshot. | | `lifetime_stats` | [AccountLifetimeStatsMsg](#accountlifetimestatsmsg) | (Optional) Complete account lifetime trade totals. Omitted if data is not available. The account WebSocket channel also omits this field when no trade has changed the totals since the last snapshot. | | `orders` | array of [OrderMsg](#ordermsg) | Open orders for this account, or all changed orders when used on the account WebSocket channel. | | `positions` | array of [PositionMsg](#positionmsg) | Current open positions for this account, or all changed positions when used on the account WebSocket channel. | | `pending_deposits` | array of [DepositMsg](#depositmsg) | Deposits being processed onchain but not yet credited. | | `pending_withdrawals` | array of [PendingWithdrawalMsg](#pendingwithdrawalmsg) | Active pending withdrawals. | | `order_updates` | array of [OrderUpdateMsg](#orderupdatemsg) | (Optional) Incremental order changes, including the resulting order state and update reason. Omitted from snapshots and updates without order changes. | | `fills` | array of [FillMsg](#fillmsg) | (Optional) Fills since the last snapshot or update. Omitted when no fills occurred. | | `resolutions` | array of [PositionResolutionMsg](#positionresolutionmsg) | (Optional) Position resolutions since the last snapshot or update. Omitted when no resolutions occurred. | | `closed_positions` | array of [PositionHistoryMsg](#positionhistorymsg) | (Optional) Completed position instances since the last snapshot or update, newest close first. Includes full closes, direction flips, and market resolutions. Each item has `position_instance_count` equal to `"1"`. Omitted from snapshots and updates with no closes. | | `transfers` | array of [TransferMsg](#transfermsg) | (Optional) Balance transfers since the last snapshot or update. Omitted when no transfers occurred. | #### OrderUpdateMsg One incremental order change. | Field | Type | Description | |---|---|---| | `order` | [OrderMsg](#ordermsg) | Order state after this change. | | `update_reason` | string enum: `"USER_CANCEL"`, `"MASS_CANCELLED"`, `"EXPIRED"`, `"UNDERCOLLATERALIZED"`, `"SELF_TRADE"`, `"IOC_REMAINDER_CANCELED"`, `"MARKET_RESOLVED"`, `"POSITION_OVERFLOW"`, `"TRADING_KEY_EXPIRED"`, `"TRADING_KEY_REVOKED"`, `"TRADING_KEY_REPLACED"`, `"NO_REDUCIBLE_POSITION"`, `"REDUCE_ONLY_RESIZE"`, `"RESTING_ORDER_LIMIT"`, `"REPLACED"` | (Optional) Classification of this update. The order's quantities describe its resulting state. | ```json { "channel": "account", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "type": "update", "data": { "has_deposited": true, "collateral_usd": "100.000000", "rewards": { "referral_rewards_received_usd": "12.500000", "builder_fees_received_usd": "34.375000", "user_rewards_received_usd": "5.000000" }, "lifetime_stats": { "maker": { "volume_contracts": "1250", "weighted_volume": "250.000000", "volume_notional_usd": "625.000000", "fees_paid_usd": "-0.625000" }, "taker": { "volume_contracts": "750", "weighted_volume": "150.000000", "volume_notional_usd": "375.000000", "fees_paid_usd": "1.125000" } }, "orders": [ { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "7", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" } ], "positions": [ { "symbol": "SIM_EVENT_1.MARKET_1", "size": "3", "cumulative_increasing_contracts": "3", "cumulative_increasing_notional_usd": "1.650000", "cumulative_reducing_contracts": "0", "cumulative_reducing_notional_usd": "0.000000", "cumulative_fees_usd": "0.000742", "realized_pnl_usd": "0.000000", "unrealized_pnl_usd": "0.000000", "mark_price": "0.550000", "average_entry_price": "0.550000", "seq": "67880", "update_seq": "67891", "open_ts_ms": "1731535900000", "update_ts_ms": "1731536000050" } ], "pending_deposits": [ { "deposit_id": "7", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "amount": "100.000000", "transfer_status": "PROCESSING" } ], "pending_withdrawals": [ { "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "status": "VERIFYING", "amount": "25.000000", "destination_authority": "29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2" } ], "order_updates": [ { "order": { "id": "987654321", "client_order_id": "42", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "price": "0.550000", "size_original": "10", "size_remaining": "7", "size_filled": "3", "notional_filled": "1.650000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": false, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" } } ], "fills": [ { "trade_id": "555001", "symbol": "SIM_EVENT_1.MARKET_1", "side": "BID", "reduce_only": false, "fill_price": "0.550000", "fill_size": "3", "liquidity": "TAKER", "counterparty": "5Z6Ay5NEcbg3xhopc522sBCRXQujkTiuDRnHGfQdcnSf", "order_id": "987654321", "client_order_id": "42", "fee_usd": "0.000742", "collateral_change_usd": "-1.650742", "position_size_prev": "0", "realized_pnl_usd": "0.000000", "position_open_seq": "67891", "position_open_ts_ms": "1731536000050", "seq": "67891", "trade_ts_ms": "1731536000050" } ], "transfers": [ { "type": "DEPOSIT", "status": "COMPLETED", "collateral_change_usd": "100.000000", "fee_usd": "0.000000", "net_usd": "100.000000", "seq": "67880", "transfer_ts_ms": "1731535900000" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` The following update shows a reduce-only order canceled because it can no longer reduce the position. Its final state appears in both order arrays, and `update_reason` belongs to the update entry. ```json { "channel": "account", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "type": "update", "data": { "orders": [ { "id": "987654322", "client_order_id": "43", "symbol": "SIM_EVENT_1.MARKET_1", "side": "ASK", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "0", "notional_filled": "0.000000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": true, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" } ], "positions": [], "pending_deposits": [], "pending_withdrawals": [], "order_updates": [ { "order": { "id": "987654322", "client_order_id": "43", "symbol": "SIM_EVENT_1.MARKET_1", "side": "ASK", "price": "0.550000", "size_original": "10", "size_remaining": "0", "size_filled": "0", "notional_filled": "0.000000", "tif": "GTC", "type": "LIMIT", "post_only": false, "reduce_only": true, "seq": "67880", "update_seq": "67891", "place_ts_ms": "1731536000050", "update_ts_ms": "1731536000050" }, "update_reason": "NO_REDUCIBLE_POSITION" } ] }, "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` **Closed position instances** When a position fully closes, flips direction, or resolves, `closed_positions` contains its complete history row. Partial reductions continue updating `positions`. A direction flip completes the old instance and updates `positions` with the new instance in the same message. An update can contain multiple completed instances for the same market. Each entry uses the [PositionHistoryMsg](#positionhistorymsg) format of [closed position history](#closed-position-history), with `position_instance_count` equal to `"1"`. Contract totals and average entry and exit prices cover the entire instance. Realized PnL excludes fees. Entries are ordered by `close_seq` descending. The following example shows a 3-contract long entered at $0.55 and resolved at $1.00. The completed instance has $1.35 of realized PnL, while `positions` reports zero size and `resolutions` records the settlement. ```json { "channel": "account", "owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB", "type": "update", "data": { "collateral_usd": "101.349258", "orders": [], "positions": [ { "symbol": "SIM_EVENT_1.MARKET_1", "size": "0", "cumulative_increasing_contracts": "0", "cumulative_increasing_notional_usd": "0.000000", "cumulative_reducing_contracts": "0", "cumulative_reducing_notional_usd": "0.000000", "cumulative_fees_usd": "0.000000", "realized_pnl_usd": "0.000000", "unrealized_pnl_usd": "0.000000", "mark_price": "1.000000", "seq": "67891", "update_seq": "67892", "open_ts_ms": "1731536000050", "update_ts_ms": "1731536000100" } ], "pending_deposits": [], "pending_withdrawals": [], "resolutions": [ { "symbol": "SIM_EVENT_1.MARKET_1", "resolution": "1.000000", "size": "3", "collateral_change_usd": "3.000000", "realized_pnl_usd": "1.350000", "position_open_seq": "67891", "position_open_ts_ms": "1731536000050", "seq": "67892", "position_resolution_ts_ms": "1731536000100" } ], "closed_positions": [ { "symbol": "SIM_EVENT_1.MARKET_1", "position_side": "LONG", "position_instance_count": "1", "held_until_resolution": true, "realized_pnl_usd": "1.350000", "cumulative_increasing_contracts": "3", "cumulative_increasing_notional_usd": "1.650000", "cumulative_reducing_notional_usd": "3.000000", "average_increasing_price": "0.550000", "average_reducing_price": "1.000000", "open_seq": "67891", "open_ts_ms": "1731536000050", "close_seq": "67892", "close_ts_ms": "1731536000100" } ] }, "seq": "67892", "state_time_ms": "1731536000100", "server_time_ms": "1731536000123" } ``` ### Market Update Channel Listings and updates for all markets. The initial `snapshot` message contains an empty `data` payload; you may retrieve all markets via the GET /api/v1/markets endpoint. In order to guarantee your markets list is up to date, you may perform the following initialization process: 1. Subscribe to the market_update websocket channel. 2. Upon receiving the first message in the channel, request from the GET /api/v1/markets endpoint. 3. Cache all updates from the websocket locally. 4. When you finish receiving all markets from the GET /api/v1/markets endpoint, compare the sequence id of the markets snapshot to the sequence ids of each cached websocket message. 5. If update.seq > snapshot.seq, then apply the update to your local store of markets. Subsequent `update` messages pertain to changes that occur to markets, such as a new listing or resolution. Note: the `stats`, `best_bid`, and `best_ask` fields present in the GET /api/v1/markets endpoint do not exist in the websocket update message. If needed, you can get them from the GET /api/v1/markets endpoint. **Channel Descriptor** | Field | Type | Description | |---|---|---| | `channel` | string literal `"market_update"` | Channel discriminator. | **Update Type** #### MarketUpdateType Reason a market appeared on the `market_update` channel. | Value | |---| | `"new"` | | `"display_attributes_change"` | | `"resolved"` | **JSON Message Payload** | Field | Type | Description | |---|---|---| | `update_type` | [MarketUpdateType](#marketupdatetype) | Update type. | | `market` | [MarketStateMsg](#marketstatemsg) | Market. | ```json { "channel": "market_update", "type": "snapshot", "data": [], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ```json { "channel": "market_update", "type": "update", "data": [ { "update_type": "new", "market": { "symbol": "SIM_EVENT_1.MARKET_1", "taker_fee_rate": "0.001000", "maker_rebate_share": "0.500000", "tick_size_min": "0.010000", "tick_sig_figs": 2, "display_attributes": { "event_description": "API documentation sample market", "expected_resolution_time_ms": "1735000000000", "market_description": "Yes", "reverse_description": "No", "sort_order": "SYMBOL_ASC", "tags": [ "Docs" ], "topic": { "id": "docs", "description": "Documentation examples" } }, "listing_ts_ms": "1731536000000", "rules_url": "https://sample-rules-url.com/events/SIM_EVENT_1/SIM_EVENT_1.MARKET_1.md", "mark_price": "0.550000", "open_interest": "250", "long_interest": "250", "short_interest": "250" } } ], "seq": "67891", "state_time_ms": "1731536000050", "server_time_ms": "1731536000123" } ``` ## Reference Types ### Error Codes API errors return an error envelope whose `data` object has these fields: | Field | Type | Description | |---|---|---| | `code` | [ApiErrorCode](#apierrorcode) | Stable, machine-readable error code. | | `message` | string | Human-readable error message. May evolve over time. For debugging and display only. | Error codes are `SCREAMING_SNAKE_CASE` identifiers. Use `code` for client control flow and `message` for display, logging, and more specific diagnostics. Stable error codes: #### ApiErrorCode Stable machine-readable error codes for public API operations. | Value | Description | |---|---| | `"INVALID_JSON"` | The request body could not be parsed as valid JSON. | | `"INVALID_REQUEST"` | The request was well-formed JSON but failed validation, such as a missing or malformed field, or a value outside its accepted range. | | `"UNAUTHORIZED"` | The request signature was missing or invalid, or the signing key is not authorized to act for the account. | | `"SYMBOL_NOT_FOUND"` | A request referenced a symbol that does not exist. | | `"ACCOUNT_NOT_FOUND"` | A request referenced an account owner that does not exist. | | `"ACCOUNT_RESTRICTED"` | The account is restricted by compliance policy. | | `"GEO_RESTRICTED"` | The request is restricted by geography policy. | | `"INVALID_GEO_HEADERS"` | Required Cloudflare geo headers were missing, malformed, or unusable. | | `"ORDER_NOT_FOUND"` | The referenced order does not exist or is no longer open. | | `"TRADING_KEY_NOT_FOUND"` | The referenced trading key does not exist for the account. | | `"DUPLICATE_REQUEST"` | A request with the same signature was already received. | | `"DUPLICATE_CLIENT_ORDER_ID"` | An open order with the same `client_order_id` already exists for the account. | | `"UNSUPPORTED_ORDER_TYPE"` | The requested order type is not supported. | | `"INVALID_SIZE"` | The order size is zero or otherwise invalid for the market. | | `"INVALID_PRICE"` | The order price is out of range or not a multiple of the market's tick size. | | `"INVALID_EXPIRATION"` | The provided expiration timestamp is invalid for the order's time in force. | | `"WOULD_CROSS"` | A post-only order was rejected because it would have crossed the book and traded as a taker. | | `"SELF_TRADE"` | The order was rejected because it would have matched against another of the account's own resting orders. | | `"INSUFFICIENT_COLLATERAL"` | The account does not have enough free collateral to place the order. | | `"NOT_FUNDED"` | The account has not been funded and cannot trade. | | `"DEPOSIT_ADDRESS_NOT_REGISTERED"` | The account has not registered a Pascal deposit address. | | `"MARKET_RESOLVED"` | The market has resolved and no longer accepts orders. | | `"POSITION_LIMIT_EXCEEDED"` | The order would move the account's position beyond the market's position limit. | | `"RESTING_ORDER_LIMIT_EXCEEDED"` | The account already holds the maximum number of resting orders for this market. Cancel a resting order before placing another, or send an immediate-or-cancel order. | | `"TRADING_KEY_SLOTS_FULL"` | The account has reached its maximum number of trading keys. | | `"TRADING_KEY_PUB_KEY_ALREADY_USED"` | A trading key with the same public key already exists for the account. | | `"TRADING_KEY_NAME_ALREADY_USED"` | A trading key with the same name already exists for the account. | | `"TRADING_KEY_MUST_OVERWRITE_WITH_GREATER_EXPIRATION"` | Overwriting an existing trading key requires an expiration later than the current one. | | `"REDUCE_ONLY_REJECTED"` | A reduce-only order was rejected because it would have increased the account's position. | | `"PENDING_WALLET_OP_CONFLICTS"` | Another wallet-signed operation (such as a withdrawal) is already in flight for the account; retry once it settles. | | `"PENDING_DEPOSIT_REGISTRATION"` | Deposit-address registration is already being processed for this account. | | `"REPLACEMENT_ORDER_NOT_FOUND"` | The order targeted for replacement does not exist or is no longer open. | | `"REPLACEMENT_ORDER_DIFF_MARKET"` | The replacement order's market differs from the order being replaced. | | `"REPLACEMENT_ORDER_DIFF_SIDE"` | The replacement order's side differs from the order being replaced. | | `"RECV_WINDOW_TOO_LARGE"` | The request's `recv_window_ms` exceeds the maximum allowed value. | | `"CLIENT_TS_TOO_FAR_IN_FUTURE"` | The request's `client_ts_ms` is too far ahead of server time, beyond the allowed clock-skew tolerance. | | `"CLIENT_TS_EXPIRED"` | The request expired: `server_time_ms - client_ts_ms` exceeded `recv_window_ms`. | | `"CLIENT_TS_NOT_INCREASING"` | A wallet-signed request's `client_ts_ms` did not increase past the last accepted timestamp nonce for the account. | | `"CONFLICT"` | The request conflicts with existing state, such as creating something that already exists. | | `"RATE_LIMITED"` | The client exceeded its rate limit; slow down before retrying. | | `"BACKPRESSURE"` | The server is shedding load and rejected the request; retry after a short delay. | | `"NOT_READY"` | The service is still starting up or catching up and cannot serve the request yet. | | `"HISTORY_NOT_READY"` | Historical data has not reached the requested pagination boundary. Retry the same boundary after a delay. | | `"BAD_GATEWAY"` | Transient upstream failure, retry. | | `"SERVICE_UNAVAILABLE"` | The service is temporarily unavailable; retry later. | | `"WRITE_RESULT_UNAVAILABLE"` | The write request reached the write pipeline, but the service could not return a result. | | `"TIMEOUT"` | The request timed out before completing. | | `"INTERNAL_ERROR"` | An unexpected internal error occurred. | | `"REFERRAL_CODE_INVALID"` | The provided Referral Code could not be resolved for registration. | | `"BUILDER_FEE_RATE_INVALID"` | The order's builder fee rate is outside the accepted range. | | `"BUILDER_INVALID"` | The order's builder pubkey is not accepted as a builder. | | `"BUILDER_COLLATERAL_BELOW_MINIMUM"` | The order's builder is below the minimum free collateral required to be named on new orders. |