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)

OpenAPI Specification

pred-orders-api-openapi.yml Raw ↑
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 &lt; 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