Pascal#
Pascal is a noncustodial prediction market exchange on Solana.
Pascal is in private beta. Trade with caution. The API should be considered unstable, though we will do our best not to make breaking changes without good reason, and we will maintain backwards compatibility at least until beta ends.
Quickstart#
Web: visit app.pascal.trade and connect your wallet to start trading.
Python Quickstart: github.com/pascal-research-inc/pascal-python-quickstart includes examples for request signing and order placement, plus a simple market making script you can adapt for custom trading logic.
Architecture#
Traders post collateral in a Solana smart contract and submit signed orders to the offchain matching engine. When a trade occurs, the matching engine broadcasts a transaction containing both signed orders to settle the trade onchain.
Only the matching engine can broadcast trades onchain, meaning all state transitions occur through the offchain API and trades are guaranteed to execute as soon as they are matched offchain. Practically, this means that after depositing, traders do not need to interact with the chain to trade on Pascal.
Onboarding#
Open the web app, connect a Solana wallet (or sign up with email), and deposit USDC.
Deposit USDC directly from a wallet, or by withdrawing from Polymarket or another centralized exchange to your generated deposit address.
Fees#
Fees are charged per fill and configured per market.
Fields for taker_fee_rate and maker_rebate_share are returned in each market's MarketSpec.
Today they default to:
{
"taker_fee_rate": "0.020000", // Takers pay 2% * price * (1 - price) * size
"maker_rebate_share": "0.250000" // Makers receive 25% of the taker fee
}Fees are denominated in USD and rounded to 6 fractional digits.
For a fill of size contracts at price price ($0.00–$1.00), the taker pays:
size × price × (1 - price) × taker_fee_rateMaker Rebates#
For a fill of size contracts at price price ($0.00–$1.00), the maker is paid:
size × price × (1 - price) × taker_fee_rate × maker_rebate_shareReferral Program#
When a new user redeems an invite code, two independent things are created, each with its own cap — one for the inviter (the API's Reward Owner) and one for the new user (the Referred Owner):
- The inviter earns referral rewards. By default, for each of the referred user's taker fills, the inviter receives 10% of the venue fee — the taker fee after the referred user's discount and the maker rebate — until rewards for that user reach $10,000.00.
- The referred user gets a taker fee discount. Their taker fees are reduced by 5% until the discount has saved them $250.00.
Read your referrals and rewards with Referrals, and your discount usage with Taker Discount Terms.
Contracts#
Pascal offers outcome contracts for trading.
Outcome contracts are bilateral, fully collateralized derivative contracts which settle to a price p in the interval [$0, $1]. Counterparties are either long (receive p at settlement) or short (receive 1 - p at settlement).
There is no notion of "yes" or "no" on Pascal. If you are long 5 contracts, you can sell 8 and you will be short 3.
Collateral#
Accounts hold USDC collateral on the Pascal smart contract.
When entering positions, an account's Free Collateral is reduced by enough to cover the worst case loss on the position.
For example, if going long q contracts at 30c, free collateral is reduced by q * 0.30 USD. If going short q at 30c, free collateral is reduced by q * (1 - 0.30) USD.
Free collateral is automatically unlocked when a market settles, or after partially or completely trading out of a position.
Realized and Unrealized PnL#
Pascal reports realized and unrealized PnL on open positions for display and portfolio tracking.
Core exchange accounting is based on collateral locked and unlocked during trading, not on these display fields.
For display purposes, positions follow these rules:
- The lifecycle of a position resets when touching zero. Fields like average entry price accumulate while the position stays open in one direction (long or short) and reset on touching or crossing zero.
- Average entry price updates only when the magnitude of the position increases (ie longing when already long, shorting when already short).
- Unrealized PnL is
size * (mark - entry), wheresizeis signed. - Realized PnL updates by
size_traded * (price - entry)on every closing trade. - Realized and unrealized PnL do not include fees. At the end of a position's lifecycle, the realized PnL is exactly equal to the account's USDC balance change attributable to that position, excluding fees.
Self-Trade Prevention#
The matching engine rejects any fill where the same account would be both maker and taker by cancelling the taker order.
The taker order may match against other maker orders before cancelling, if the order that would cause self-trade is not at the top of the book.
Matching Rounds And Priority#
The exchange batches incoming orders into rounds and processes them every 50ms.
Within a round, orders are processed in three groups, in this order:
- Post-only placements: order place or replace requests with
post_only: true. - Cancels
- Other orders: IOC, GTC limits without
post_only, GTT, market.
Requests within the same group are processed first come, first served by the order they were received by the write API.
This is comparable to Hyperliquid's matching algorithm.
Order Book#
Each market has a single limit order book with bid and ask sides. Within a side, orders are sorted by price, then by order of arrival. Order book insertion happens in the order described in Matching Rounds and Priority.
Tick Sizes#
Each market is parameterized with a tick_sig_figs and tick_size_min.
Both tick_size_min and tick_sig_figs are returned in each market's MarketSpec and on List Markets. For a market with tick_size_min = 0.001 and tick_sig_figs = 2, the tick size at various prices is:
Price range (px) | Tick size | Example valid prices |
|---|---|---|
[0.10, 0.90] | 0.0100 | 0.50, 0.51, 0.85 |
[0.010, 0.100) or (0.900, 0.990] | 0.0010 | 0.025, 0.043, 0.975 |
< 0.010 or > 0.990 | 0.0010 (floor) | 0.001, 0.997 |
Specifically, for a given price p:
p' = min(p, 1 - p) # Symmetrical around 50c
p_sigs = floor(log10(p')) + 1
tick_size = max(10^(p_sigs - tick_sig_figs), tick_size_min)The min(p, 1 - p) mirror keeps tick precision symmetric around 0.50, so a market priced near $0.01 has the same precision as one priced near $0.99.
The intuition is to use a fixed number of significant figures regardless of how many leading zeros the price has, and the min tick size adds a bound as the price approaches the endpoints.
API#
Public reference for integrating Pascal's REST and WebSocket APIs.
All write requests are signed. Most read requests are unauthenticated; invite-code and referral reads require a signed request payload, as documented per endpoint. The Write and Read APIs use distinct base URLs; see 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 |
| 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 are provisional during private beta, and may change. Please program your clients to respect HTTP 429 error codes.
Initial rate limits: 100 requests / second (note you can batch up to 50 orders per request).
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).
See Request Signing for constructing the exact payloads to sign over.
The easiest way to start signing requests is with github.com/pascal-research-inc/pascal-python-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.
- Open the Pascal web app, for example 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.
- Click your account image in the upper right, then Trading Keys > Create Key.
- 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 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. Commands 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. |
Error data is an object with fields:
| Field | Type | Description |
|---|---|---|
code | ApiErrorCode | Stable, machine-readable error code. |
message | string | Human-readable error message. May evolve over time. For debugging and display only. |
Batch endpoints return an array of envelopes, one per submitted command, in the same order as the request batch.
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
{
"status": "success",
"data": {
"server_time_ms": "1731536000123"
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Error envelope
{
"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
[
{
"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 at_or_before_seq.
at_or_before_seqallows starting pagination at a sequence number. Continue paginating using the cursor.before_cursoris the cursor returned from a previous page.limituses min 1, default 100, max 500.
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. 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 and state_time_ms indicate the freshness of the historical data.
See the historical 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
seqat which the order was placed. - REST account and order book snapshots include the snapshot's
seqin the response envelope. - WebSocket messages include the
seqof the state that produced the update. - Historical data queries include the highest
seqof indexed state. For example, the historical trades response is guaranteed to include every trade wheretrade.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 and not every global state change affects each specific WebSocket channel.
Write Endpoints#
Place And Replace Orders#
POST <write_base_url>/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.
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) | 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 | (Optional) The order's 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. |
auth | 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. |
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 | The state of the order after placement, including any fills that occurred immediately. |
fills | array of FillMsg | Fills that occurred during the placement of the order. |
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) | 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. | The cumulative sum of price * size for fills to this order. Divide by size_filled to get the average fill price. |
tif | TimeInForce | The order's TimeInForce. |
type | string enum: "LIMIT", "MARKET" | Order type. LIMIT is a standard limit order. MARKET is for IOCs submitted via the front end market tab. For display purposes. |
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. |
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) | Symbol for the market being traded. |
side | string enum: "BID", "ASK" | Side of the order that was filled. |
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). |
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. | Trading fee paid for this fill. Positive values are fees paid; negative values are rebates. |
collateral_change_usd | signed decimal string, 6 d.p. | Net change to account collateral from this fill, including fees. |
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. |
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. |
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[
{
"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",
"fill_price": "0.550000",
"fill_size": "3",
"liquidity": "TAKER",
"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 <write_base_url>/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 | 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 with the cancelled order's final state.
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[
{
"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"
}
]Create A Trading Key#
POST <write_base_url>/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 | 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. |
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{
"status": "success",
"data": {
"trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"name": "api-doc-key",
"expiration_ts_ms": "1732140800000"
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Revoke A Trading Key#
POST <write_base_url>/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 | 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. |
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{
"status": "success",
"data": {
"trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1"
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Withdraw Collateral#
POST <write_base_url>/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 default signed withdrawal fee is $0.10, and the maximum fee is $0.50.
Destination address
Derive the destination_token_account associated token account (ATA) 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 canonical 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 | 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. |
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{
"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 <write_base_url>/api/v1/deposit-addresses
Registers the account's deposit address for balance scanning. This request is wallet-signed. When Invite Codes are enabled, a new account must include an invite_code in this request. Use Features to check whether the gate is currently enabled.
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). |
invite_code | string | (Optional) Invite code submitted for registration. Required during private beta. |
auth | 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. |
curl -X POST https://trade.pascal.trade/api/v1/deposit-addresses \
-H 'Content-Type: application/json' \
--data @- <<'EOF'
{
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"invite_code": "4PHPW-S4BM2-U9MS6-KP2JF",
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signature": "3y43tLaBWdQyxkj7BAbbSEmGfd6Fu3Xop5ATyWcLiC4XYFgNJSmoKXmyC8Qnu7oRUzAnEycmfSLzdyAV5L3cxecc"
}
}
EOF{
"status": "success",
"data": {
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"deposit_address": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"registered": true
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Market Data Endpoints#
Server Time#
GET <read_base_url>/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. |
curl 'https://data.pascal.trade/api/v1/time'{
"status": "success",
"data": {
"server_time_ms": "1731536000123"
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Features#
GET <read_base_url>/api/v1/features
Returns feature flags that clients may need before choosing a write flow.
JSON Response Payload
| Field | Type | Description |
|---|---|---|
invite_codes_enabled | boolean | Whether address registration currently requires a valid Invite Code. |
curl 'https://data.pascal.trade/api/v1/features'{
"status": "success",
"data": {
"invite_codes_enabled": true
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}List Markets#
GET <read_base_url>/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) | 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. |
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 | (Optional) Stats. |
mark_price | decimal string, 6 d.p. | (Optional) Median of whichever of (best bid, best ask, last trade) are available. |
open_interest | u64 string | Current open interest. |
resolution | 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 Yes. |
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 win". In that case the reverse_description would be something like "Mets win". None indicates reversing the UI is not available for this market. This is a UI-only field. |
topic | TopicInfo | (Optional) Higher-level topic this event belongs to, if any. |
game_page | GamePageDisplayInfo | (Optional) Display metadata for sections (game lines) under the same game. When absent, clients should use regular event-based display. |
market_type | 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 | Default UI sort order |
range | RangeDisplayAttributes | (Optional) Deprecated alias for scalar-market display metadata, retained during client migration. |
scalar | RangeDisplayAttributes | (Optional) Scalar-market display metadata. |
reference | ReferenceAttributes | (Optional) Reference that informs this market's resolution criteria. |
display_attributes.scalar and legacy display_attributes.range objects:
RangeDisplayAttributes#
| Field | Type | Description |
|---|---|---|
upper | u16 string | Upper bound of the scalar market outcome space, from 0 to 999. |
lower | u16 string | Lower bound of the scalar market outcome space, from 0 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 | Used by the front end 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 frontend grouping/filtering id, 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. |
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.market_type enum:
MarketType#
Typed market category and category-specific details for a listed market.
For example, sports markets use kind: "sports" with a nested sport value such as "soccer".
| Value |
|---|
"sports" |
"sports" fields
| Field | Type | Description |
|---|---|---|
kind | string literal "sports" | Discriminator. |
0 | SportsMarketType | 0. |
display_attributes.market_type.sport enum for kind: sports market types:
SportsMarketType#
Sport-specific market type details.
| Value |
|---|
"soccer" |
"soccer" fields
| Field | Type | Description |
|---|---|---|
sport | string literal "soccer" | Discriminator. |
period | SoccerPeriod | Period. |
teams | SoccerTeams | Teams. |
market | SoccerMarket | Market. |
display_attributes.market_type.period enum for kind: sports, sport: soccer market types:
SoccerPeriod#
Portion of the game a soccer 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.teams object for kind: sports, sport: soccer market types:
SoccerTeams#
Soccer teams for fixture-scoped market types.
| Field | Type | Description |
|---|---|---|
home_name | string | Human-readable home team name used in generated labels and rules. |
home_subject_id | string | Stable subject slug for the home team. |
away_name | string | Human-readable away team name used in generated labels and rules. |
away_subject_id | string | Stable subject slug for the away team. |
display_attributes.market_type.market enum for kind: sports, sport: soccer market types:
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 | DrawMoneylineOutcome | Outcome. |
"spread" fields
| Field | Type | Description |
|---|---|---|
type | string literal "spread" | Request discriminator. |
team | 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.outcome enum for moneyline market types that can draw:
DrawMoneylineOutcome#
Moneyline outcomes for markets where a draw is a possible result.
| Value |
|---|
"home" |
"away" |
"draw" |
display_attributes.market_type.market.team enum for soccer team-scoped market types:
TeamSide#
| Value |
|---|
"home" |
"away" |
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 | (Optional) Rolling 24-hour market statistics snapshot. |
all_time | MarketStatsSnapshotMsg | (Optional) All-time market statistics snapshot. |
Each stats.last_24h and stats.all_time object:
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. | Total quote-notional bought by takers in the window. |
taker_sell_volume_notional | decimal string, 6 d.p. | Total quote-notional sold by takers in the window. |
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. |
curl 'https://data.pascal.trade/api/v1/markets'{
"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",
"open_interest": "250"
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Resolved Markets#
GET <read_base_url>/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_all_time. Defaults to resolution_time. |
limit | integer | Optional. Maximum number of markets to return. Defaults to 50. Maximum 200. |
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_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_all_time | Sort by all-time contract volume. |
order_by=volume_24h is not accepted for this endpoint.
For resolved markets, mark_price is the final resolution price and stats.last_24h is omitted.
JSON Response Payload
Returns a JSON array of market objects. Same object shape as GET /api/v1/markets.
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.
curl 'https://data.pascal.trade/api/v1/markets/resolved?lookback=24h&order_by=resolution_time&limit=50'{
"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": {
"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",
"resolution": {
"price": "1.000000",
"seq": "67891",
"market_resolution_ts_ms": "1731536000050"
}
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Market Search#
POST <read_base_url>/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.
For resolved markets, mark_price is the final resolution price and stats.last_24h is omitted.
JSON Request Body
| Field | Type | Description |
|---|---|---|
symbols | array of string (Symbol) | (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.
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{
"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",
"open_interest": "250"
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Market Text Search#
POST <read_base_url>/api/v1/markets/text-search
Returns markets whose symbol, event description, or market description 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.
For resolved markets, mark_price is the final resolution price and stats.last_24h is omitted.
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 | (Optional) Resolution status filter. |
order_by | 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.
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{
"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",
"open_interest": "250"
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Order Books#
GET <read_base_url>/api/v1/books
Returns order books for up to 50 comma-delimited symbols.
Query Parameters
| Field | Type | Description |
|---|---|---|
symbols | comma-separated array of string (Symbol) | Comma-delimited list of market symbols. |
JSON Response Payload
| Field | Type | Description |
|---|---|---|
books | object mapping string (Symbol) to 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 | (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) | 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]. |
curl 'https://data.pascal.trade/api/v1/books?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2'{
"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 <read_base_url>/api/v1/trade-candles
Legacy compatibility path: GET <read_base_url>/api/v1/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, and 1d. count_back defaults to 100 and must be between 1 and 500.
Query Parameters
| Field | Type | Description |
|---|---|---|
symbol | string (Symbol) | Symbol of the market to fetch candles for. |
interval | string enum: "1m", "5m", "15m", "1h", "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. | Total price * size traded in the interval. |
taker_buy_volume_notional | decimal string, 6 d.p. | Price * size bought by takers in the interval. |
taker_sell_volume_notional | decimal string, 6 d.p. | Price * size sold by takers in the interval. |
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. |
curl 'https://data.pascal.trade/api/v1/trade-candles?symbol=SIM_EVENT_1.MARKET_1&interval=1m&end_time_ms=1731536000000&count_back=2'{
"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 <read_base_url>/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, and 1d. count_back defaults to 100 and must be between 1 and 500.
Query Parameters
| Field | Type | Description |
|---|---|---|
symbol | string (Symbol) | Symbol of the market to fetch mark price candles for. |
interval | string enum: "1m", "5m", "15m", "1h", "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. |
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'{
"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 <read_base_url>/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) | Comma-delimited list of market symbols to fetch mark price candles for. |
interval | string enum: "1m", "5m", "15m", "1h", "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 | 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 symbol for this result. |
candles | array of MarkPriceCandleMsg | Mark price candles for this symbol, sorted newest-first. |
Each value in candles is a mark price candle object using MarkPriceCandleMsg.
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'{
"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 <read_base_url>/api/v1/trades
Returns trade history for up to 30 markets.
Query Parameters
| Field | Type | Description |
|---|---|---|
symbols | comma-separated array of string (Symbol) | 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 | 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, 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 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. |
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. |
Next-page request using the data.next_cursor value above as before_cursor:
curl 'https://data.pascal.trade/api/v1/trades?symbols=SIM_EVENT_1.MARKET_1&limit=50'{
"status": "success",
"data": {
"items": [
{
"trade_id": "555001",
"symbol": "SIM_EVENT_1.MARKET_1",
"taker_side": "BID",
"price": "0.550000",
"size": "3",
"maker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"taker": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"seq": "67891",
"trade_ts_ms": "1731536000050"
}
],
"next_cursor": "00000000000000067890:0000000000"
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}curl 'https://data.pascal.trade/api/v1/trades?symbols=SIM_EVENT_1.MARKET_1&before_cursor=00000000000000067890%3A0000000000&limit=50'Batch Trades#
GET <read_base_url>/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) | 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 | 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 symbol for this result. |
trades | array of PublicTradeMsg | Recent trades for this symbol, sorted newest-first. |
Each value in trades is a public trade object using PublicTradeMsg.
curl 'https://data.pascal.trade/api/v1/trades/batch?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2&limit=10'{
"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": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"taker": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"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": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"taker": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"seq": "67891",
"trade_ts_ms": "1731536000050"
}
]
}
]
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Top Holders#
GET <read_base_url>/api/v1/top-holders
Returns the largest active position holders across up to 50 comma-delimited symbols. Results are ordered by absolute contract size descending, then owner public-key byte order ascending. contracts is signed: positive values are long positions and negative values are short positions. Requested symbols for resolved markets are returned with empty holders arrays because they no longer have active positions.
Query Parameters
| Field | Type | Description |
|---|---|---|
symbols | comma-separated array of string (Symbol) | Comma-delimited list of market symbols. |
limit | integer | (Optional) Maximum number of holders to return per market. Defaults to 20. Maximum 100. |
JSON Response Payload
| Field | Type | Description |
|---|---|---|
holders | object mapping string (Symbol) to array of TopHolderMsg | Largest active holder rows keyed by market symbol. |
Every holder row in each holders array is a JSON object:
TopHolderMsg#
Holder row for a market.
| Field | Type | Description |
|---|---|---|
owner | base58 string | Account owner public key. |
contracts | i64 string | Signed position size in contracts. Positive is long, negative is short. |
curl 'https://data.pascal.trade/api/v1/top-holders?symbols=SIM_EVENT_1.MARKET_1,SIM_EVENT_1.MARKET_2&limit=20'{
"status": "success",
"data": {
"holders": {
"SIM_EVENT_1.MARKET_1": [
{
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"contracts": "125"
}
],
"SIM_EVENT_1.MARKET_2": [
{
"owner": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"contracts": "-80"
}
]
}
},
"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 <read_base_url>/api/v1/accounts/{owner}
Returns open orders, positions, pending deposits/withdrawals, and collateral for one account. The same shape is sent over the account WebSocket channel.
JSON Response Payload
| Field | Type | Description |
|---|---|---|
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. |
orders | array of OrderMsg | Open orders for this account, or all changed orders when used on the account WebSocket channel. |
positions | array of PositionMsg | Current open positions for this account, or all changed positions when used on the account WebSocket channel. |
pending_deposits | array of DepositMsg | Deposits being processed onchain but not yet credited. |
pending_withdrawals | array of PendingWithdrawalMsg | Active pending withdrawals. |
Every element in positions is a JSON object. See 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) | Symbol of the market this position is in. |
size | i64 string | Signed position size. Positive for long, negative for short. |
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. Equal to size * (mark - entry). |
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 rounded to 6dp. 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. |
status | string enum: "PENDING", "FINALIZED" | Deprecated legacy deposit lifecycle state. @deprecated Use transfer_status instead. This field remains PENDING/FINALIZED during the frontend rollout so old frontend bundles keep validating account WebSocket messages. |
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. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB'{
"status": "success",
"data": {
"collateral_usd": "100.000000",
"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_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",
"status": "PENDING",
"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 <read_base_url>/api/v1/accounts/{owner}/trading-keys
Returns active trading keys for one account.
JSON Response Payload
Response data is an array of TradingKeyMsg objects.
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/trading-keys'{
"status": "success",
"data": [
{
"trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"name": "api-doc-key",
"expiration_ts_ms": "1732140800000"
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Trading Key Accounts#
GET <read_base_url>/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.
curl 'https://data.pascal.trade/api/v1/trading-keys/2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1/accounts'{
"status": "success",
"data": [
"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB"
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Taker Discount Terms#
GET <read_base_url>/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 for the terms applied when an invite code is redeemed.
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 | (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. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/taker-discount-terms'{
"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"
}Invite Codes#
POST <read_base_url>/api/v1/invite-codes
Returns Invite Codes for the owner in the signed request. The request does not accept an owner path or query parameter; the backend always uses the verified auth.owner.
The request may be signed by the owner wallet or by any active trading key for that owner.
JSON Request Payload
OwnerInviteCodesRequest#
Request body for the owner Invite Codes endpoint.
| Field | Type | Description |
|---|---|---|
auth | RequestAuth | Signed request authentication metadata. The lookup owner is always auth.owner. |
JSON Response Payload
OwnerInviteCodesMsg#
Owner Invite Codes response.
| Field | Type | Description |
|---|---|---|
owner_invite_codes | array of OwnerInviteCodeMsg | Owner invite codes. |
Every element in owner_invite_codes is a JSON object:
OwnerInviteCodeMsg#
One Invite Code available to the owner.
| Field | Type | Description |
|---|---|---|
invite_code | string | Invite code. |
status | string enum: "active", "redeemed" | Status. |
redeemed_by | base58 string | (Optional) Redeemed by. |
curl -X POST https://data.pascal.trade/api/v1/invite-codes \
-H 'Content-Type: application/json' \
--data @- <<'EOF'
{
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"signature": "u9FGGLvU3mbj8n64XVg1MaR4bBecTDteBxEdpdSvxeuVzi5ViS1nVEJroHt5e8CdADvzngcJy1TtVto4ymVFsYH"
}
}
EOF{
"status": "success",
"data": {
"owner_invite_codes": [
{
"invite_code": "4PHPW-S4BM2-U9MS6-KP2JF",
"status": "active"
},
{
"invite_code": "ABCDE-FGHJK-LMNPQ-RSTUV",
"status": "redeemed",
"redeemed_by": "J2xccRtuG43drESLYznHhLhQkLTdfepcKYbiQ9BsJVaf"
}
]
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Referrals#
POST <read_base_url>/api/v1/referrals
Returns the Referrals for which the owner in the signed request is the Reward Owner: one entry per referred user, with the reward paid so far and the reward-paid cap. The request does not accept an owner path or query parameter; the backend always uses the verified auth.owner.
The request may be signed by the owner wallet or by any active trading key for that owner.
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 for the current terms.
JSON Request Payload
OwnerReferralsRequest#
Request body for the owner referrals endpoint.
| Field | Type | Description |
|---|---|---|
auth | RequestAuth | Signed request authentication metadata. The lookup owner is always auth.owner. |
JSON Response Payload
OwnerReferralsMsg#
Owner referrals response: the current owner's Referrals as their Reward Owner.
| Field | Type | Description |
|---|---|---|
referral_reward_terms | 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. |
referrals | array of ReferralMsg | This owner's Referrals as Reward Owner, one per referred owner, newest first. |
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 who redeemed the invite code that 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. |
curl -X POST https://data.pascal.trade/api/v1/referrals \
-H 'Content-Type: application/json' \
--data @- <<'EOF'
{
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"signature": "2gypF8ds55gagM8c9koyp7g4eZr4vSaehkbVFP9ohSzd4EMZsE29xYyi8Jcfp7BLLK3hWK7SP1JVHAoZBoYre6xm"
}
}
EOF{
"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",
"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"
}
]
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Deposit Address#
GET <read_base_url>/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. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/deposit-address'{
"status": "success",
"data": {
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"deposit_address": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"registered": true
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Fill History#
GET <read_base_url>/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) | (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 | Result page items. |
next_cursor | string | (Optional) Cursor for the next page of results. Omitted on the last page. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/fills?symbols=SIM_EVENT_1.MARKET_1&limit=50'{
"status": "success",
"data": {
"items": [
{
"trade_id": "555001",
"symbol": "SIM_EVENT_1.MARKET_1",
"side": "BID",
"fill_price": "0.550000",
"fill_size": "3",
"liquidity": "TAKER",
"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"
}Transfer History#
GET <read_base_url>/api/v1/accounts/{owner}/transfers
Returns transfer (deposit and withdrawal) history 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 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" | Whether this transfer was a deposit or withdrawal. |
status | string enum: "VERIFYING", "PROCESSING", "REJECTED", "COMPLETED" | Current user-facing lifecycle state for this transfer. |
collateral_change_usd | signed decimal string, 6 d.p. | Net change to account collateral from this transfer. |
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. | Signed external transfer amount after fees. |
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. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/transfers?limit=50'{
"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 <read_base_url>/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 | 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 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. |
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. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/position-resolutions?limit=50'{
"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",
"seq": "67891",
"position_resolution_ts_ms": "1731536000050"
}
]
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}PnL History#
GET <read_base_url>/api/v1/accounts/{owner}/history
Returns account value and PnL time series for one account.
Historical windows are day (30m), week (4h), month (12h), and all_time (24h).
JSON Response Payload
| Field | Type | Description |
|---|---|---|
items | array of 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 | 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 | Sampled total account value over time. |
pnl_history | array of TimeseriesPoint | Sampled cumulative PnL over time. |
Every value in account_value_history and pnl_history 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. |
curl 'https://data.pascal.trade/api/v1/accounts/GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB/history'{
"status": "success",
"data": {
"items": [
{
"timeframe": "day",
"data": {
"account_value_history": [
[
"1731536000000",
"1000.000000"
]
],
"pnl_history": [
[
"1731536000000",
"25.000000"
]
]
}
}
]
},
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}Market Data WebSocket#
GET <ws_base_url> upgrades to a WebSocket connection. Clients send JSON text frames.
The server allows up to 10,000 concurrent connections and 250 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.
Unsubscribe uses the same shape with type: "unsubscribe".
Ping keeps the connection alive. The server responds with the literal frame {"type":"pong"}.
Channels#
Each entry in channels is one of:
{"channel": "book", "symbol": "<market symbol>"}
{"channel": "trades", "symbol": "<market symbol>"}
{"channel": "candles", "symbol": "<market symbol>", "interval": "1m"}
{"channel": "mark_price_candles", "symbol": "<market symbol>", "interval": "1m"}
{"channel": "account", "owner": "<wallet base58>"}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;intervalmust be one of1m,5m,15m,1h, and1d.mark_price_candles— current in-progress mark price candle for one market and interval;intervalmust be one of1m,5m,15m,1h, and1d.account— live account updates for one wallet.
{
"type": "subscribe",
"channels": [
{
"channel": "book",
"symbol": "SIM_EVENT_1.MARKET_1"
},
{
"channel": "trades",
"symbol": "SIM_EVENT_1.MARKET_1"
},
{
"channel": "all_trades"
}
]
}{
"type": "unsubscribe",
"channels": [
{
"channel": "book",
"symbol": "SIM_EVENT_1.MARKET_1"
}
]
}{
"type": "ping"
}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. Commands 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 | Stable, machine-readable error code. |
message | string | Human-readable error message. May evolve over time. For debugging and display only. |
{
"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.
Subsequent messages are updates 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 symbol to stream order book updates for. |
JSON Message Payload
Payload uses OrderBookMsg.
{
"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"
}{
"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 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.
{
"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": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"taker": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"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.
{
"channel": "all_trades",
"type": "snapshot",
"data": [
{
"trade_id": "555001",
"symbol": "SIM_EVENT_1.MARKET_1",
"taker_side": "BID",
"price": "0.550000",
"size": "3",
"maker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"taker": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"seq": "67891",
"trade_ts_ms": "1731536000050"
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}{
"channel": "all_trades",
"type": "update",
"data": [
{
"trade_id": "555001",
"symbol": "SIM_EVENT_1.MARKET_1",
"taker_side": "BID",
"price": "0.550000",
"size": "3",
"maker": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"taker": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"seq": "67891",
"trade_ts_ms": "1731536000050"
}
],
"seq": "67891",
"state_time_ms": "1731536000050",
"server_time_ms": "1731536000123"
}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 symbol to stream candles for. |
interval | string enum: "1m", "5m", "15m", "1h", "1d" | Candle aggregation interval. |
JSON Message Payload
data is a JSON array of candle objects. Every element in the array uses CandleMsg.
{
"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 symbol to stream mark price candles for. |
interval | string enum: "1m", "5m", "15m", "1h", "1d" | Candle aggregation interval. |
JSON Message Payload
data is a JSON array of mark price candle objects. Every element in the array uses MarkPriceCandleMsg.
{
"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.
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 orders array, and no other fields. If an order fills, you will see an update with changes in orders, 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). |
JSON Message Payload
| Field | Type | Description |
|---|---|---|
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. |
orders | array of OrderMsg | Open orders for this account, or all changed orders when used on the account WebSocket channel. |
positions | array of PositionMsg | Current open positions for this account, or all changed positions when used on the account WebSocket channel. |
pending_deposits | array of DepositMsg | Deposits being processed onchain but not yet credited. |
pending_withdrawals | array of PendingWithdrawalMsg | Active pending withdrawals. |
fills | array of FillMsg | (Optional) Fills since the last snapshot or update. Omitted when no fills occurred. |
resolutions | array of PositionResolutionMsg | (Optional) Position resolutions since the last snapshot or update. Omitted when no resolutions occurred. |
transfers | array of TransferMsg | (Optional) Balance transfers since the last snapshot or update. Omitted when no transfers occurred. |
{
"channel": "account",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"type": "update",
"data": {
"collateral_usd": "100.000000",
"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_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",
"status": "PENDING",
"transfer_status": "PROCESSING"
}
],
"pending_withdrawals": [
{
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"status": "VERIFYING",
"amount": "25.000000",
"destination_authority": "29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2"
}
],
"fills": [
{
"trade_id": "555001",
"symbol": "SIM_EVENT_1.MARKET_1",
"side": "BID",
"fill_price": "0.550000",
"fill_size": "3",
"liquidity": "TAKER",
"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"
}
],
"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"
}Request Signing#
This section describes the payloads to sign over for each request type.
In order to settle transactions directly on Solana, requests to Pascal's API use binary permits that are propagated onchain, which must be compact for performance, especially for order requests.
This section is for advanced users who want to roll their own signing code.
The easiest way to start signing requests is with github.com/pascal-research-inc/pascal-python-quickstart
All test vectors in this section use the following keys:
| Role | Private key bytes | Public key |
|---|---|---|
| Owner wallet | [7; 32] (0707070707070707070707070707070707070707070707070707070707070707) | GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB |
| Trading key | [8; 32] (0808080808080808080808080808080808080808080808080808080808080808) | 2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1 |
And constants:
| Constant | Value |
|---|---|
deployment_id | 3 |
Deployment Id#
All signed requests encode a deployment_id field to make sure requests are not replayable across environments:
| Environment | deployment_id |
|---|---|
local | 0 |
staging | 1 |
canary | 2 |
prod | 3 |
Use prod for real trading.
Permit Header#
Binary permits use little-endian encoding for all multi-byte integer fields, both in the header and in each permit body.
All binary permits begin with the same 80-byte header:
| Field | Byte offset | Size | Notes |
|---|---|---|---|
version | 0 | 1 byte | Permit envelope version; always the first byte. |
cmd_type | 1 | 1 byte | Command type discriminator (1 = place order, 2 = cancel, ...), used to decode the body. |
deployment_id | 2 | 1 byte | Protection against replay across deployments on same or different networks. |
_padding0 | 3 | 1 byte | Reserved padding. Must be zero. |
recv_window_ms | 4 | 4 bytes | Freshness validity window in milliseconds from client_ts_ms. |
client_ts_ms | 8 | 8 bytes | Client timestamp in epoch milliseconds, also the strictly increasing replay nonce for wallet-signed requests. |
owner | 16 | 32 bytes | Account owner public key. |
signer | 48 | 32 bytes | Public key that produced the signature; may equal owner. |
Place Order#
Signed by: Trading key.
Used for: Place And Replace Orders
The body follows the 80-byte PermitHeader, starting at byte offset 80:
| Field | Byte offset | Size | Notes |
|---|---|---|---|
size | 80 | 8 bytes | Order size in contracts. |
price | 88 | 8 bytes | Limit price multiplied by 1e6. |
client_order_id | 96 | 8 bytes | Client-assigned order identifier. |
replace_client_order_id | 104 | 8 bytes | Client order id being replaced. 0xFFFFFFFFFFFFFFFF (all bits set) when replace_client_order_id is omitted. |
expire_ts_ms | 112 | 8 bytes | GTT expiration timestamp. 0 when expires_ts_ms is omitted (non-GTT orders). |
reserved | 120 | 32 bytes | Zeros. |
symbol | 152 | 32 bytes | Fixed market symbol bytes, zero-padded. |
reserved | 184 | 4 bytes | Zeros. |
offchain_fields_digest | 188 | 32 bytes | Hash of offchain-only order attributes included as instructions to the matching engine. Reserved for future order semantics. Currently always the fixed value 0xdcaf53738f1d5f06164660498a75888e7f0c78ed5f1bc17be1dd1cb3ccf53ce3 (SHA256 of pascal.place_order.offchain_fields.v1). |
reserved | 220 | 1 byte | Zero byte. |
side | 221 | 1 byte | 0 = bid, 1 = ask. |
order_type | 222 | 1 byte | 0 = limit, 1 = market. Defaults to 0 (limit). |
tif | 223 | 1 byte | 0 = GTC, 1 = GTT, 2 = IOC. Defaults to 0 (GTC). |
flags | 224 | 1 byte | Packed order flags: bit 0 = post_only, bit 1 = reduce_only, bit 2 = allow_missing_replace, bit 3 = replace_with_open_size. Defaults to 0 when no flag is set; other bits must be zero. |
_padding1 | 225 | 7 bytes | Trailing padding. Must be zero. |
Test vector
{
"permit": "PlaceOrderPermit",
"signer_role": "trading_key",
"private_key_hex": "0808080808080808080808080808080808080808080808080808080808080808",
"public_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"deployment_id": 3,
"message_hex": "01010300881300000094962793010000ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22c1398f62c6d1a457c51ba6a4b5f3dbd2f69fca93216218dc8997e416bd17d93ca0a0000000000000070640800000000002a000000000000002b000000000000008082cd2793010000000000000000000000000000000000000000000000000000000000000000000053494d5f4556454e545f312e4d41524b45545f3100000000000000000000000000000000dcaf53738f1d5f06164660498a75888e7f0c78ed5f1bc17be1dd1cb3ccf53ce3000000010700000000000000",
"signature": "46gXGMwTxEckctHD3tjVyedhGG95VK2F97F3idkSYFR3WNEX4rG9g6g8KDTQPoxzLieudk8WHDXw7xNV3R2WwKCd"
}[
{
"client_order_id": "42",
"replace_client_order_id": "43",
"symbol": "SIM_EVENT_1.MARKET_1",
"side": "BID",
"price": "0.550000",
"size": "10",
"tif": "GTT",
"post_only": true,
"reduce_only": true,
"allow_missing_replace": true,
"expires_ts_ms": "1731539600000",
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"signature": "46gXGMwTxEckctHD3tjVyedhGG95VK2F97F3idkSYFR3WNEX4rG9g6g8KDTQPoxzLieudk8WHDXw7xNV3R2WwKCd"
}
}
]Cancel Order#
Signed by: Trading key.
Used for: Cancel orders
The body follows the 80-byte PermitHeader, starting at byte offset 80:
| Field | Byte offset | Size | Notes |
|---|---|---|---|
cancel_by | 80 | 1 byte | 0 = client order id, 1 = exchange order id. |
reserved | 81 | 1 byte | Zero byte. |
_padding0 | 82 | 6 bytes | Reserved padding. Must be zero. |
id | 88 | 8 bytes | Client order id or exchange order id, depending on cancel_by. |
Test vector
{
"permit": "CancelOrderPermit",
"signer_role": "trading_key",
"private_key_hex": "0808080808080808080808080808080808080808080808080808080808080808",
"public_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"deployment_id": 3,
"message_hex": "01020300881300000094962793010000ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22c1398f62c6d1a457c51ba6a4b5f3dbd2f69fca93216218dc8997e416bd17d93ca00000000000000002a00000000000000",
"signature": "591YziUjZzro24mamxgRrSDdYD71NpsDBmU4UGSyrBYbFiWzYaLjR9he1P5DP8djMKMJzHTsZzwRAagvLqgtiXnc"
}[
{
"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"
}
}
]Create Trading Key#
Signed by: Wallet key.
Used for: Create A Trading Key
Wallets sign the UTF-8 JSON array below, not the binary permit. The binary body follows the 80-byte PermitHeader, starting at byte offset 80:
| Field | Byte offset | Size | Notes |
|---|---|---|---|
name | 80 | 16 bytes | Fixed-length trading key name bytes, UTF-8. |
trading_key | 96 | 32 bytes | Trading key public key being authorized. |
expiration | 128 | 8 bytes | Trading key expiration timestamp in milliseconds. |
JSON field order:
| Index | Field | JSON type | Notes |
|---|---|---|---|
| 0 | command_type | string | "create_trading_key". |
| 1 | name | string | Trading key name. |
| 2 | trading_key | string | Trading key public key, base58. |
| 3 | expiration | integer | Expiration timestamp in milliseconds. |
| 4 | version | integer | Permit envelope version. |
| 5 | deployment_id | integer | Deployment identifier. |
| 6 | recv_window_ms | integer | Request validity window in milliseconds. |
| 7 | client_ts_ms | integer | Client timestamp in milliseconds. |
| 8 | owner | string | Account owner public key, base58. |
| 9 | signer | string | Must equal owner. |
Test vector
{
"permit": "CreateTradingKeyPermit",
"signer_role": "owner_wallet",
"private_key_hex": "0707070707070707070707070707070707070707070707070707070707070707",
"public_key": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"deployment_id": 3,
"message_utf8": "[\"create_trading_key\",\"api-doc-key\",\"2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1\",1732140800000,1,3,5000,1731536000000,\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\",\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\"]",
"signature": "kyc2KdqPTeEWqoK3pXR2rgPUiK4zXg9QdQffPUWk8eALpFy7uaHBEsASt7RQyVzDeubHVsPqQAHXrt8m8KZ78Ep"
}{
"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"
}
}Revoke Trading Key#
Signed by: Wallet key.
Used for: Revoke A Trading Key
Wallets sign the UTF-8 JSON array below, not the binary permit. The binary body follows the 80-byte PermitHeader, starting at byte offset 80:
| Field | Byte offset | Size | Notes |
|---|---|---|---|
trading_key | 80 | 32 bytes | Trading key public key being revoked. |
JSON field order:
| Index | Field | JSON type | Notes |
|---|---|---|---|
| 0 | command_type | string | "revoke_trading_key". |
| 1 | trading_key | string | Trading key public key, base58. |
| 2 | version | integer | Permit envelope version. |
| 3 | deployment_id | integer | Deployment identifier. |
| 4 | recv_window_ms | integer | Request validity window in milliseconds. |
| 5 | client_ts_ms | integer | Client timestamp in milliseconds. |
| 6 | owner | string | Account owner public key, base58. |
| 7 | signer | string | Must equal owner. |
Test vector
{
"permit": "RevokeTradingKeyPermit",
"signer_role": "owner_wallet",
"private_key_hex": "0707070707070707070707070707070707070707070707070707070707070707",
"public_key": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"deployment_id": 3,
"message_utf8": "[\"revoke_trading_key\",\"2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1\",1,3,5000,1731536000000,\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\",\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\"]",
"signature": "3muKct6upyRNAr1CPdYcxQGCojpuB4gaEnWvbNwEBJ6rrKaThRZrKY56rpYbFEsGUZqJ4iCTJqYrPuxfGYVzXQX3"
}{
"trading_key": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signature": "3muKct6upyRNAr1CPdYcxQGCojpuB4gaEnWvbNwEBJ6rrKaThRZrKY56rpYbFEsGUZqJ4iCTJqYrPuxfGYVzXQX3"
}
}Signed Withdrawal#
Signed by: Wallet key.
Used for: Withdraw Collateral
Wallets sign the UTF-8 JSON array below, not the binary permit. The binary body follows the 80-byte PermitHeader, starting at byte offset 80:
| Field | Byte offset | Size | Notes |
|---|---|---|---|
gross_amount | 80 | 8 bytes | Gross withdrawal amount in micro-USD. |
destination_authority | 88 | 32 bytes | Owner of the destination token account. |
destination_token_account | 120 | 32 bytes | Destination token account. |
JSON field order:
| Index | Field | JSON type | Notes |
|---|---|---|---|
| 0 | command_type | string | "withdrawal". |
| 1 | gross_amount | integer | Gross withdrawal amount in micro-USD. |
| 2 | destination_authority | string | Destination authority public key, base58. |
| 3 | destination_token_account | string | Destination token account public key, base58. |
| 4 | version | integer | Permit envelope version. |
| 5 | deployment_id | integer | Deployment identifier. |
| 6 | recv_window_ms | integer | Request validity window in milliseconds. |
| 7 | client_ts_ms | integer | Client timestamp in milliseconds. |
| 8 | owner | string | Account owner public key, base58. |
| 9 | signer | string | Must equal owner. |
Test vector
{
"permit": "SignedWithdrawalPermit",
"signer_role": "owner_wallet",
"private_key_hex": "0707070707070707070707070707070707070707070707070707070707070707",
"public_key": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"deployment_id": 3,
"message_utf8": "[\"withdrawal\",25000000,\"29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2\",\"3JF3sEqM796hk5WFqA6EtmEwJQ9quALszsfJyvXNQKy3\",1,3,5000,1731536000000,\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\",\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\"]",
"signature": "61YYUBk6BTXkfrksMbqDLUNxwo6jhMPqdP7fNnWjNF2FYvMDujTsaUxfCiRxXX72jxy4oLtt6AtLodCvjDi4XpDn"
}{
"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"
}
}Register Deposit Address#
Signed by: Wallet key.
Used for: Register A Deposit Address
There is no fixed binary layout; sign the UTF-8 JSON array field order below. When invite_code is present in the request body, append it after signer.
JSON field order:
| Index | Field | JSON type | Notes |
|---|---|---|---|
| 0 | command_type | string | "register_deposit_address". |
| 1 | owner | string | Account owner public key, base58. |
| 2 | version | integer | Permit envelope version. |
| 3 | deployment_id | integer | Deployment identifier. |
| 4 | recv_window_ms | integer | Request validity window in milliseconds. |
| 5 | client_ts_ms | integer | Client timestamp in milliseconds. |
| 6 | owner | string | Repeated owner public key from auth.owner, base58. |
| 7 | signer | string | Must equal owner. |
| 8 | invite_code | string | Optional. Present only when registering with an Invite Code. |
Test vector
{
"permit": "RegisterDepositAddressPermit",
"signer_role": "owner_wallet",
"private_key_hex": "0707070707070707070707070707070707070707070707070707070707070707",
"public_key": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"deployment_id": 3,
"message_utf8": "[\"register_deposit_address\",\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\",1,3,5000,1731536000000,\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\",\"GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB\",\"4PHPW-S4BM2-U9MS6-KP2JF\"]",
"signature": "3y43tLaBWdQyxkj7BAbbSEmGfd6Fu3Xop5ATyWcLiC4XYFgNJSmoKXmyC8Qnu7oRUzAnEycmfSLzdyAV5L3cxecc"
}{
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"invite_code": "4PHPW-S4BM2-U9MS6-KP2JF",
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signature": "3y43tLaBWdQyxkj7BAbbSEmGfd6Fu3Xop5ATyWcLiC4XYFgNJSmoKXmyC8Qnu7oRUzAnEycmfSLzdyAV5L3cxecc"
}
}Owner Invite Codes#
Signed by: Wallet key or active trading key.
Used for: Invite Codes
There is no fixed binary layout; sign the UTF-8 JSON array field order below.
JSON field order:
| Index | Field | JSON type | Notes |
|---|---|---|---|
| 0 | command_type | string | "owner_invite_codes". |
| 1 | version | integer | Permit envelope version. |
| 2 | deployment_id | integer | Deployment identifier. |
| 3 | recv_window_ms | integer | Request validity window in milliseconds. |
| 4 | client_ts_ms | integer | Client timestamp in milliseconds. |
| 5 | owner | string | Account owner public key, base58. |
| 6 | signer | string | Owner wallet or active trading key public key, base58. |
{
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"signature": "u9FGGLvU3mbj8n64XVg1MaR4bBecTDteBxEdpdSvxeuVzi5ViS1nVEJroHt5e8CdADvzngcJy1TtVto4ymVFsYH"
}
}Owner Referrals#
Signed by: Wallet key or active trading key.
Used for: Referrals
There is no fixed binary layout; sign the UTF-8 JSON array field order below.
JSON field order:
| Index | Field | JSON type | Notes |
|---|---|---|---|
| 0 | command_type | string | "owner_referrals". |
| 1 | version | integer | Permit envelope version. |
| 2 | deployment_id | integer | Deployment identifier. |
| 3 | recv_window_ms | integer | Request validity window in milliseconds. |
| 4 | client_ts_ms | integer | Client timestamp in milliseconds. |
| 5 | owner | string | Account owner public key, base58. |
| 6 | signer | string | Owner wallet or active trading key public key, base58. |
{
"auth": {
"client_ts_ms": "1731536000000",
"recv_window_ms": "5000",
"owner": "GmaDrppBC7P5ARKV8g3djiwP89vz1jLK23V2GBjuAEGB",
"signer": "2KW2XRd9kwqet15Aha2oK3tYvd3nWbTFH1MBiRAv1BE1",
"signature": "2gypF8ds55gagM8c9koyp7g4eZr4vSaehkbVFP9ohSzd4EMZsE29xYyi8Jcfp7BLLK3hWK7SP1JVHAoZBoYre6xm"
}
}Reference Types#
Error Codes#
API errors return an error envelope whose data object has these fields:
| Field | Type | Description |
|---|---|---|
code | 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. |
"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. |
"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. |
"INVITE_CODE_REQUIRED" | An invite code is required to perform this action. |
"INVITE_CODE_INVALID" | The provided invite code is invalid or expired. |
"INVITE_CODE_ALREADY_EXISTS" | An invite code with the same value already exists. |
"INVITE_REDEMPTIONS_CAP_REACHED" | The invite code has reached its maximum number of redemptions. |