Pred Orders API
Order placement, cancellation, and management endpoints. ## EIP-712 order signature All orders must be signed using EIP-712. Format: `0x` (132 hex characters), low-s normalized. **Domain (HAS version field):** `chainId` is ENV-specific. `verifyingContract` must come from market discovery for the specific parent market. ``` EIP712Domain type: "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" name: "Pred CTF Exchange" version: "1" chainId: verifyingContract: ``` **Order contract source (MANDATORY):** Use `parent_market_data.contract_address` from market discovery for the target parent market. Do not assume one static contract per environment; markets can have different order contract addresses. **Order type (FIELD ORDER IS CRITICAL):** ``` "Order(uint256 salt,address maker,address signer,address taker,uint256 price,uint256 quantity,uint256 expiration,uint256 nonce,bytes32 questionId,uint256 feeRateBps,uint8 intent,uint8 signatureType)" ``` **Field meanings:** - `salt`: `"02"` + 10-digit random number (e.g. `"020123456789"`); stringified integer - `maker`: proxy wallet address (from login `proxy_wallet_addr`) - `signer`: EOA wallet address - `taker`: `0x0000000000000000000000000000000000000000` (always zero) - `price`: wei-scaled = `(priceCents / 100) * 1e6` (e.g. 55 cents → 550000; not `55 * 1e6`) - `quantity`: wei-scaled = `quantity * 1e6` (e.g. 100 shares → 100000000) - `expiration`: Unix seconds; `0` = GTC - `nonce`: `0` (always) - `questionId`: market ID as bytes32; left-pad with zeros if hex < 64 chars - `feeRateBps`: `0` (default) - `intent`: LONG = `0`, SHORT = `1` (uint8) - `signatureType`: `2` (always; uint8) ## Cross-matching Cross-matching applies only **across child markets of the same parent market** (e.g. a parent with 3-outcome markets: Team A, Team B, Draw). It is enabled when the parent is configured for it. **When it applies:** A key validity condition — **Long orders summing to 100c across all markets of a cross-match–enabled parent form a valid match** (e.g. Long 25 in A + Long 25 in B + Long 50 in C = 100c). **How it works:** Orders match across child markets by combining liquidity from peer (sibling) markets into synthetic bid/ask levels. For a given child market, synthetic *Short* levels are derived from *Long* orders in peer child markets (e.g. Long 25 in B + Long 25 in C + Long 25 in D → synthetic Short at 75 for market A). An incoming Long order in A can match against these synthetic Short levels; execution is against the underlying peer Long orders. Similarly, synthetic *Long* levels can be built from *Short* orders in peer child markets; an incoming Short order can match against them. **Self-match:** Self-match is **allowed** when cross-matching is involved: the same user can match against their own liquidity **across different child markets within the same parent**. For example, your Long orders in two outcomes can provide synthetic liquidity that your Short order in the third outcome matches against. This is intentional and valid for cross-match–enabled parents. Self-match *within* the same market may be restricted; self-match *across* markets in the same parent is allowed. **Implications for API users:** - Orderbook responses may include synthetic levels; orderbook metadata can indicate `cross_matching_enabled` (e.g. in WebSocket orderbook payloads). - Placing Long orders in multiple child markets can create combined liquidity (e.g. summing to 100c Long across the parent's markets is a valid match). A Short order in another child market may match—including self-match—against that liquidity. - 422 / validation may still apply for other liquidity or validation reasons. **Example:** Long 25 in markets B, C, D → synthetic Short 75 for market A; or Long 25+25+50 across A, B, C = 100c valid match. ## Units - `price`: **cents** (e.g. 55.0 = $0.55) - `quantity`: **shares** - `amount`: **USD** - `fee_rate_bps`: **basis points** (default 0) - `timestamp`: **Unix seconds** (in place order payload; within 15s of server time)
Documentation
Specifications
openapi: 3.1.0
info:
title: PRED Trading Platform Authentication Orders API
version: 1.0.0
description: "Required headers, token refresh, and EIP-712 CreateProxy signature for login. For overview, getting started, and environment configuration, see **Overview**.\n\n## Required headers\n\n- `Authorization: Bearer <token>`\n (for authenticated endpoints)\n- `X-API-Key: <api_key>`\n (required for login-with-signature only)\n- `X-Wallet-Address: <wallet_address>`\n (for order and portfolio endpoints)\n- `X-Proxy-Address: <proxy_wallet_address>`\n (for order and portfolio endpoints)\n\n**Token refresh:** Access tokens are automatically refreshed by the SDK whenever their remaining lifetime drops below the configured threshold. The default threshold is **5 minutes**, and can be overridden via the `RefreshThresholdSeconds` client option.\n\n## EIP-712 login signature — CreateProxy\n\nFormat: `0x<r><s><v>` (132 hex characters). Low-s normalized: `s <= curveOrderHalf`.\n\n**Domain (NO version field):** All values below are ENV-specific; use the row for your chosen environment (see Environment configuration in **Overview**).\n```\nEIP712Domain type: \"EIP712Domain(string name,uint256 chainId,address verifyingContract)\"\nname: \"Pred Contract Proxy Factory\"\nchainId: <Chain ID from environment table — Testnet 84532, Mainnet 8453>\nverifyingContract: <Login Contract from environment table>\n```\n\n**Message:**\n```\nCreateProxy type: \"CreateProxy(address paymentToken,uint256 payment,address paymentReceiver)\"\npaymentToken: 0x0000000000000000000000000000000000000000\npayment: 0\npaymentReceiver: 0x0000000000000000000000000000000000000000\n```\n\n**Normalization constants:**\n- `curveOrder = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141`\n- `curveOrderHalf = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0`\n"
servers:
- url: https://testnet.pred.app
description: Testnet (ENV-specific). Base Sepolia; chain ID 84532. Use with Testnet row in Environment configuration.
- url: https://www.pred.app
description: Mainnet (ENV-specific). Base; chain ID 8453. Use with Mainnet row in Environment configuration.
tags:
- name: Orders
description: 'Order placement, cancellation, and management endpoints.
## EIP-712 order signature
All orders must be signed using EIP-712. Format: `0x<r><s><v>` (132 hex characters), low-s normalized.
**Domain (HAS version field):** `chainId` is ENV-specific. `verifyingContract` must come from market discovery for the specific parent market.
```
EIP712Domain type: "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
name: "Pred CTF Exchange"
version: "1"
chainId: <Chain ID from environment table — Testnet 84532, Mainnet 8453>
verifyingContract: <parent_market_data.contract_address from discovery for this parent market>
```
**Order contract source (MANDATORY):** Use `parent_market_data.contract_address` from market discovery for the target parent market. Do not assume one static contract per environment; markets can have different order contract addresses.
**Order type (FIELD ORDER IS CRITICAL):**
```
"Order(uint256 salt,address maker,address signer,address taker,uint256 price,uint256 quantity,uint256 expiration,uint256 nonce,bytes32 questionId,uint256 feeRateBps,uint8 intent,uint8 signatureType)"
```
**Field meanings:**
- `salt`: `"02"` + 10-digit random number (e.g. `"020123456789"`); stringified integer
- `maker`: proxy wallet address (from login `proxy_wallet_addr`)
- `signer`: EOA wallet address
- `taker`: `0x0000000000000000000000000000000000000000` (always zero)
- `price`: wei-scaled = `(priceCents / 100) * 1e6` (e.g. 55 cents → 550000; not `55 * 1e6`)
- `quantity`: wei-scaled = `quantity * 1e6` (e.g. 100 shares → 100000000)
- `expiration`: Unix seconds; `0` = GTC
- `nonce`: `0` (always)
- `questionId`: market ID as bytes32; left-pad with zeros if hex < 64 chars
- `feeRateBps`: `0` (default)
- `intent`: LONG = `0`, SHORT = `1` (uint8)
- `signatureType`: `2` (always; uint8)
## Cross-matching
Cross-matching applies only **across child markets of the same parent market** (e.g. a parent with 3-outcome markets: Team A, Team B, Draw). It is enabled when the parent is configured for it.
**When it applies:** A key validity condition — **Long orders summing to 100c across all markets of a cross-match–enabled parent form a valid match** (e.g. Long 25 in A + Long 25 in B + Long 50 in C = 100c).
**How it works:** Orders match across child markets by combining liquidity from peer (sibling) markets into synthetic bid/ask levels. For a given child market, synthetic *Short* levels are derived from *Long* orders in peer child markets (e.g. Long 25 in B + Long 25 in C + Long 25 in D → synthetic Short at 75 for market A). An incoming Long order in A can match against these synthetic Short levels; execution is against the underlying peer Long orders. Similarly, synthetic *Long* levels can be built from *Short* orders in peer child markets; an incoming Short order can match against them.
**Self-match:** Self-match is **allowed** when cross-matching is involved: the same user can match against their own liquidity **across different child markets within the same parent**. For example, your Long orders in two outcomes can provide synthetic liquidity that your Short order in the third outcome matches against. This is intentional and valid for cross-match–enabled parents. Self-match *within* the same market may be restricted; self-match *across* markets in the same parent is allowed.
**Implications for API users:**
- Orderbook responses may include synthetic levels; orderbook metadata can indicate `cross_matching_enabled` (e.g. in WebSocket orderbook payloads).
- Placing Long orders in multiple child markets can create combined liquidity (e.g. summing to 100c Long across the parent''s markets is a valid match). A Short order in another child market may match—including self-match—against that liquidity.
- 422 / validation may still apply for other liquidity or validation reasons.
**Example:** Long 25 in markets B, C, D → synthetic Short 75 for market A; or Long 25+25+50 across A, B, C = 100c valid match.
## Units
- `price`: **cents** (e.g. 55.0 = $0.55)
- `quantity`: **shares**
- `amount`: **USD**
- `fee_rate_bps`: **basis points** (default 0)
- `timestamp`: **Unix seconds** (in place order payload; within 15s of server time)
'
paths:
/api/v1/order/{parentMarketID}/place:
post:
tags:
- Orders
summary: Place a new order
description: 'Place an order (limit, market, or IOC). Requires EIP-712 Order signature; see **Orders** section for domain, type hash, field meanings, and wei conversion.
**Order types:** `limit` (resting), `market` (fill at best), `ioc` (immediate-or-cancel). **Expiration:** Unix seconds; `0` = GTC. **Idempotency:** `salt` is the key; same salt returns cached response.
**Validation:** Price > 0, < 100, multiple of tick size; side long/short; quantity ≥ 5, at most 1 decimal; timestamp within 15s of server (seconds); fee_rate_bps = 0.
'
operationId: placeOrder
parameters:
- $ref: '#/components/parameters/XWalletAddress'
- $ref: '#/components/parameters/XProxyAddress'
- $ref: '#/components/parameters/ParentMarketIDPath'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PlaceOrderRequest'
responses:
'202':
description: Order accepted for processing
content:
application/json:
schema:
$ref: '#/components/schemas/OrderResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
description: Insufficient liquidity or self-match (when not allowed)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/order/{parentMarketID}/cancel:
delete:
tags:
- Orders
summary: Cancel an order
description: Cancel a single open order by order ID and market ID.
operationId: cancelOrder
parameters:
- $ref: '#/components/parameters/XWalletAddress'
- $ref: '#/components/parameters/XProxyAddress'
- $ref: '#/components/parameters/ParentMarketIDPath'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CancelOrderRequest'
responses:
'200':
description: Order cancelled
content:
application/json:
schema:
$ref: '#/components/schemas/CancelOrderResponse'
'202':
description: Cancellation submitted
content:
application/json:
schema:
$ref: '#/components/schemas/CancelOrderResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Order not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/order/{parentMarketID}/cancel/all:
delete:
tags:
- Orders
summary: Cancel all orders for a parent market
description: 'Cancel all open orders for a parent market. No request body required.
'
operationId: cancelAllForParent
parameters:
- $ref: '#/components/parameters/XWalletAddress'
- $ref: '#/components/parameters/XProxyAddress'
- $ref: '#/components/parameters/ParentMarketIDPath'
responses:
'200':
description: All orders cancelled
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
'202':
description: Cancellation submitted
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/order/{parentMarketID}/cancel/bulk:
delete:
tags:
- Orders
summary: Bulk cancel orders for a parent market
description: 'Cancel multiple orders for a parent market. Send an array of order references.
If `orders` is empty, the backend fetches all open orders for this parent and cancels them.
'
operationId: cancelOrdersBulk
parameters:
- $ref: '#/components/parameters/XWalletAddress'
- $ref: '#/components/parameters/XProxyAddress'
- $ref: '#/components/parameters/ParentMarketIDPath'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BulkCancelOrdersRequest'
responses:
'200':
description: Bulk cancel completed
content:
application/json:
schema:
$ref: '#/components/schemas/BulkCancelOrdersResponse'
'202':
description: Bulk cancel submitted
content:
application/json:
schema:
$ref: '#/components/schemas/BulkCancelOrdersResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/order/{parentMarketID}/open-orders:
get:
tags:
- Orders
summary: Get open orders for a parent market (recommended)
description: 'Returns all open orders scoped to a specific parent market.
**Recommended:** Use this endpoint for fetching open orders. It returns real-time data directly from the order service with no delay. Prefer this over `GET /api/v1/portfolio/open-orders` for accurate, up-to-date order state.
'
operationId: getOpenOrdersForParent
parameters:
- $ref: '#/components/parameters/XWalletAddress'
- $ref: '#/components/parameters/XProxyAddress'
- $ref: '#/components/parameters/ParentMarketIDPath'
responses:
'200':
description: Open orders for parent
content:
application/json:
schema:
$ref: '#/components/schemas/OpenOrdersResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/order/{parentMarketID}/orderbook/{marketID}:
get:
tags:
- Orders
summary: Get order book for a market
description: 'Retrieve the order book (bids and asks) for a specific market.
Prices are in **cents**, sizes are in **shares**.
'
operationId: getOrderBook
parameters:
- $ref: '#/components/parameters/XWalletAddress'
- $ref: '#/components/parameters/XProxyAddress'
- $ref: '#/components/parameters/ParentMarketIDPath'
- name: marketID
in: path
required: true
description: Market ID (bytes32 hex). See **Market Discovery** for what market_id is and how to get it (e.g. from GET /api/v1/market-discovery/discover).
schema:
type: string
pattern: ^0x[a-fA-F0-9]{64}$
- name: depth
in: query
required: false
description: Order book depth (1–100). Default 20.
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: include_metadata
in: query
required: false
description: Include orderbook metadata (e.g. spread, mid price, cross_matching_enabled). Default true.
schema:
type: boolean
default: true
responses:
'200':
description: Order book retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/OrderBookResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Market not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
CancelOrderResponse:
type: object
properties:
status:
type: string
enum:
- user_cancelled
order_id:
type: string
message:
type: string
success:
type: boolean
OpenOrdersResponse:
type: object
properties:
orders:
type: array
items:
$ref: '#/components/schemas/OpenOrder'
BulkCancelOrdersRequest:
type: object
properties:
orders:
type: array
description: Orders to cancel. If empty, cancels all open orders for the parent market.
items:
type: object
required:
- order_id
properties:
order_id:
type: string
market_id:
type: string
OpenOrder:
type: object
properties:
order_id:
type: string
market_id:
type: string
market_name:
type: string
parent_market_id:
type: string
side:
type: string
enum:
- long
- short
price:
type: string
description: Price in cents
quantity:
type: string
description: Original quantity in shares
remaining_quantity:
type: string
description: Remaining quantity in shares
OrderBookLevel:
type: object
properties:
price:
type: string
description: Price in cents
size:
type: string
description: Size in shares
OrderBookResponse:
type: object
properties:
success:
type: boolean
bids:
type: array
description: Buy orders sorted by price descending
items:
$ref: '#/components/schemas/OrderBookLevel'
asks:
type: array
description: Sell orders sorted by price ascending
items:
$ref: '#/components/schemas/OrderBookLevel'
ErrorResponse:
type: object
properties:
error:
$ref: '#/components/schemas/Error'
message:
type: string
success:
type: boolean
default: false
CancelOrderRequest:
type: object
required:
- order_id
- market_id
properties:
order_id:
type: string
description: Order ID to cancel
market_id:
type: string
description: Market ID (bytes32 hex). See **Market Discovery** for what market_id is and how to get it (e.g. from GET /api/v1/market-discovery/discover).
pattern: ^0x[a-fA-F0-9]{64}$
PlaceOrderRequest:
type: object
required:
- salt
- market_id
- side
- price
- quantity
- signature
- timestamp
properties:
salt:
type: string
description: Unique salt for order (idempotency key). Use '02' + 10-digit random number.
user_id:
type: string
description: User ID; filled from JWT if omitted.
market_id:
type: string
description: Market ID (bytes32 hex / questionId). See **Market Discovery** for what market_id is and how to get it (e.g. from GET /api/v1/market-discovery/discover).
pattern: ^0x[a-fA-F0-9]{64}$
side:
type: string
enum:
- long
- short
description: 'Order side: long = YES exposure, short = NO exposure'
token_id:
type: string
description: Optional. Not used by order-service; omit or leave empty.
price:
type: string
description: Price in cents (decimal string). Must be > 0, < 100, multiple of market tick size.
quantity:
type: string
description: Quantity in shares (decimal string). Minimum 5; at most 1 decimal place.
amount:
type: string
description: 'Amount in USD. Long: (price*qty)/100. Short: ((100-price)*qty)/100. Computed if omitted for limit/ioc.'
is_low_priority:
type: boolean
default: false
description: Low priority / market maker flag.
signature:
type: string
description: 'EIP-712 Order signature. Format: 0x<r><s><v> (132 hex chars), low-s normalized.'
pattern: ^0x[a-fA-F0-9]{130}$
type:
type: string
enum:
- limit
- market
- ioc
default: limit
description: limit = resting order; market = fill at best available; ioc = immediate-or-cancel (fill immediately or cancel remainder).
timestamp:
type: integer
format: int64
description: Unix timestamp in **seconds** (not milliseconds). Must be within 15 seconds of server time.
expiration:
type: integer
format: int64
description: Expiration as Unix timestamp in **seconds**. 0 = no expiration (GTC). If > 0 must be > timestamp; values in milliseconds are rejected.
default: 0
reduce_only:
type: boolean
default: false
fee_rate_bps:
type: integer
description: Fee rate in basis points. Must be 0 (fees not supported).
default: 0
Error:
type: object
properties:
code:
type: string
message:
type: string
details:
type: string
OrderResponse:
type: object
description: Place order response. See status enum for meanings.
properties:
status:
type: string
enum:
- order_created
- matched
- partial_matched
- open_order
- failed
description: 'order_created — accepted
matched — fully filled
partial_matched — partially filled
open_order — resting
failed — placement failed
'
order_id:
type: string
description: Unique order ID
message:
type: string
BulkCancelOrdersResponse:
type: object
properties:
results:
type: array
items:
type: object
properties:
order_id:
type: string
status:
type: string
message:
type: string
total_requested:
type: integer
total_unique:
type: integer
total_cancelled:
type: integer
total_failed:
type: integer
responses:
Unauthorized:
description: Authentication required or failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
BadRequest:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
parameters:
ParentMarketIDPath:
name: parentMarketID
in: path
required: true
description: Parent market ID (bytes32 hex). See **Market Discovery** for what parent_market_id is and how to get it (e.g. from GET /api/v1/market-discovery/discover).
schema:
type: string
pattern: ^0x[a-fA-F0-9]{64}$
XWalletAddress:
name: X-Wallet-Address
in: header
required: true
description: Your EOA wallet address
schema:
type: string
pattern: ^0x[a-fA-F0-9]{40}$
XProxyAddress:
name: X-Proxy-Address
in: header
required: true
description: Your proxy wallet address (from login response)
schema:
type: string
pattern: ^0x[a-fA-F0-9]{40}$
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: JWT access token from login endpoint