> ## Documentation Index
> Fetch the complete documentation index at: https://docs.4casters.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Place orders

> Submit one or more orders to the exchange

Submit one or more orders. The endpoint accepts a batch — per-order results come back in `data.createdSessions`, **positionally** with your input `orders` array. Each entry is either a successful `{ matched, unmatched }` result or an `{ error, errorType }` failure.

## Request

`POST /session/v3/place`

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.4casters.io/session/v3/place \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "orders": [
        {
          "gameID": "688c0516fbc14da0c202d426",
          "type": "moneyline",
          "side": "5c12bc1ce0daba000f47ba8b",
          "odds": -175,
          "bet": 100,
          "orderType": "post",
          "userReference": "docs-example"
        }
      ]
    }'
  ```

  ```json JSON body theme={null}
  {
    "orders": [
      {
        "gameID": "4CASTER_GAME_ID",
        "type": "moneyline | spread | total | moneyline1x2",
        "side": "PARTICIPANT_ID | over | under | yes | no",
        "market": "PARTICIPANT_ID | draw",
        "odds": -110,
        "bet": 100,
        "number": 3.5,
        "orderType": "limit | post | postArb | fillAndKill",
        "expirationMinutes": 10,
        "userReference": "OPTIONAL_CLIENT_SIDE_IDENTIFIER"
      }
    ]
  }
  ```
</CodeGroup>

### Order fields

<Snippet file="types/order-fields.mdx" />

## Order types

### `limit`

The default order type. Executes against any matching liquidity at your price or better as a taker (with taker commission), and rests any remainder on the book as a maker. A `limit` order can finish fully matched, fully resting, or partially matched with the remainder resting.

### `post`

Create a resting offer. If the order **would match** existing liquidity when placed, the server rejects it (`rejected_order_type_rules`, e.g. `"post order cannot have matches"`).

### `postArb`

Behaves like `post`, but you may post **even when the order would match**, **only if** your American odds are within **1%** of the odds on the resting order you would match. If the price difference is more than 1%, the place is rejected.

Examples:

* Best offer **+100** and you post **+100** — `post` is rejected (it would match); `postArb` is allowed.
* Best offer **+200** — `postArb` at **+190** is rejected (too far from **+200**). About **+198** is at the edge of the 1% band vs **+200**.
* Best order **−200** — the band extends to about **−202** on the negative side (same 1% rule).
* With **+100** on the book, **−101** is the reference limit on the other side for the 1% tolerance.

`postArb` avoids **taker fees** across a normal trade — you are not charged taker fees on this flow the way you would be if you lifted resting liquidity as a taker.

Orders placed as `postArb` are flagged with `isPostArb: true` on their [user feed](/pages/streaming/user-feed) and [price feed](/pages/streaming/price-feed) updates; the field is omitted for other order types. The place response itself does not include the flag.

### `fillAndKill`

Must execute immediately against available size at your price, or the order is rejected. See the Fill And Kill scenarios in the [examples](#examples) below.

## Response

<ResponseField name="data.createdSessions" type="array">
  Per-order results, positional with the input `orders` array. Each entry is either a successful place or an error.

  <Expandable title="Successful place">
    <ResponseField name="matched" type="array">
      Matched fills produced by this order. Empty when the order did not match.

      <Expandable title="MatchedFill">
        <ResponseField name="amount" type="number">Stake on the matched portion.</ResponseField>
        <ResponseField name="odds" type="integer">American odds of the fill.</ResponseField>
        <ResponseField name="number" type="number">Spread or total of the fill (`null` for moneylines).</ResponseField>
        <ResponseField name="type" type="string">Market type.</ResponseField>
        <ResponseField name="side" type="string">Order side.</ResponseField>
        <ResponseField name="market" type="string">Present for `moneyline1x2`.</ResponseField>
        <ResponseField name="orderID" type="string">Order id of the matched offer on the other side.</ResponseField>
        <ResponseField name="txID" type="string">Transaction id of the fill.</ResponseField>
        <ResponseField name="wagerRequestID" type="string">Server-generated id grouping every fill / offer derived from this input order.</ResponseField>

        <ResponseField name="userReference" type="string" />

        <ResponseField name="risk" type="number" />

        <ResponseField name="win" type="number">Win amount, net of taker commission.</ResponseField>
        <ResponseField name="winWithoutCommission" type="number">Win amount before commission.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="unmatched" type="object">
      Resting offer created from the unfilled remainder. May be empty (`{}`) when the order matched fully.

      <Expandable title="UnmatchedOffer">
        <ResponseField name="orderID" type="string">Id of the new resting order. Use it to cancel.</ResponseField>

        <ResponseField name="wagerRequestID" type="string" />

        <ResponseField name="offered" type="number" />

        <ResponseField name="odds" type="integer" />

        <ResponseField name="type" type="string" />

        <ResponseField name="side" type="string" />

        <ResponseField name="market" type="string">Present for `moneyline1x2`.</ResponseField>

        <ResponseField name="number" type="number" />

        <ResponseField name="userReference" type="string" />
      </Expandable>
    </ResponseField>
  </Expandable>

  <Expandable title="Per-order error">
    <ResponseField name="error" type="string">Human-readable failure description.</ResponseField>
    <ResponseField name="errorType" type="string">`validation_error`, `rejected_liability`, `rejected_order_type_rules`, or `system_error`.</ResponseField>
  </Expandable>
</ResponseField>

## Examples

### Scenario 1 — Match orders with no leftover liquidity

Three orders that all match instantly with available liquidity.

<CodeGroup>
  ```json Request theme={null}
  {
    "orders": [
      { "gameID": "688c0516fbc14da0c202d426", "type": "moneyline", "side": "5c12bc1ce0daba000f47ba8b", "odds": -175, "bet": 100, "orderType": "post" },
      { "gameID": "688c0516fbc14da0c202d426", "type": "spread",    "side": "5c12bc1ce0daba000f47ba8b", "odds": -110, "bet": 100, "orderType": "post", "number": 3.5 },
      { "gameID": "688c0516fbc14da0c202d426", "type": "total",     "side": "over",                     "odds": -104, "bet": 100, "orderType": "post", "number": 50 }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "createdSessions": [
        {
          "matched": [
            {
              "amount": 50, "odds": -175, "type": "moneyline",
              "side": "5d48bd5198366d41ec7238da",
              "orderID": "68d42f82cfebf0b249a2c26e",
              "txID":    "68d42f83cfebf0b249a2c279",
              "wagerRequestID": "68d42f83cfebf0b249a2c278",
              "risk": 50.286, "win": 28.286, "winWithoutCommission": 28.571
            }
          ],
          "unmatched": {}
        },
        { "matched": [/* spread fill */], "unmatched": {} },
        { "matched": [/* total fill  */], "unmatched": {} }
      ]
    }
  }
  ```
</CodeGroup>

### Scenario 2 — Limit order with leftover liquidity

A limit order for 300 that partially matches and leaves the remainder resting.

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "createdSessions": [
        {
          "matched": [
            {
              "amount": 185, "odds": -185, "type": "moneyline",
              "side": "5d48bd5198366d41ec7238da",
              "orderID": "68d43574cfebf0b249a2c28d",
              "txID":    "68d43574cfebf0b249a2c290",
              "wagerRequestID": "68d43574cfebf0b249a2c28f",
              "risk": 186, "win": 99, "winWithoutCommission": 100
            }
          ],
          "unmatched": {
            "orderID": "68d43575cfebf0b249a2c292",
            "wagerRequestID": "68d43574cfebf0b249a2c28f",
            "offered": 115, "odds": -185, "type": "moneyline",
            "side": "5d48bd5198366d41ec7238da", "number": null
          }
        }
      ]
    }
  }
  ```
</CodeGroup>

### Scenario 3 — Fill and Kill, full match

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "createdSessions": [
        {
          "matched": [
            {
              "amount": 100, "odds": -186, "type": "moneyline",
              "side": "5d48bd5198366d41ec7238da",
              "orderID": "68d4368acfebf0b249a2c298",
              "txID":    "68d436bacfebf0b249a2c29b",
              "wagerRequestID": "68d436bacfebf0b249a2c29a",
              "userReference": "docs-fillandkill-match",
              "risk": 100.538, "win": 53.226, "winWithoutCommission": 53.763
            }
          ],
          "unmatched": {}
        }
      ]
    }
  }
  ```
</CodeGroup>

### Scenario 4 — Fill and Kill, no match

A `fillAndKill` with no match returns a per-order error.

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "createdSessions": [
        {
          "error": "fill and kill has no matches",
          "errorType": "rejected_order_type_rules"
        }
      ]
    }
  }
  ```
</CodeGroup>

### Scenario 5 — `postArb` vs `post` when the book would match

`post` rejects an order that would match resting liquidity (for example, best offer **+100** and you try to post **+100**). `postArb` allows that situation when your odds are within **1%** of the order you would match — so the same **+100** post can succeed as `postArb`, and you avoid the taker fees you'd pay across a normal trade. If your price is too far from the resting quote (e.g. best offer **+200** but you send **+190**), `postArb` is rejected.

<CodeGroup>
  ```json Request theme={null}
  {
    "orders": [
      {
        "gameID": "688c0516fbc14da0c202d426",
        "type": "moneyline",
        "side": "5c12bc1ce0daba000f47ba8b",
        "odds": 100,
        "bet": 50,
        "orderType": "postArb",
        "userReference": "docs-postarb-same-line-as-offer"
      }
    ]
  }
  ```
</CodeGroup>

### Scenario 6 — `moneyline1x2` (soccer three-way)

`moneyline1x2` is the soccer three-way market — home / away / draw — placed as a yes/no bet on the outcome named by `market`. Below, **yes on the draw** at +250.

<CodeGroup>
  ```json Request theme={null}
  {
    "orders": [
      {
        "gameID": "65f0c3...",
        "type": "moneyline1x2",
        "side": "yes",
        "market": "draw",
        "odds": 250,
        "bet": 50,
        "orderType": "post",
        "userReference": "docs-ml1x2-yes-draw"
      }
    ]
  }
  ```
</CodeGroup>

To bet that the **home team does not win**, send `side: "no"` and set `market` to the home participant's id.

## Live delay

Orders placed on a game marked as live follow these rules:

1. If the order does not match any existing liquidity, it is placed immediately.
2. If the order does match existing liquidity, it incurs a delay before execution.
3. Different leagues have different live delay periods:
   * **NFL, UFCMMA, NCAAF** — 3 seconds.
   * **NCAAB, NBA** — 5 seconds.
   * **ATP, WTA** — 8 seconds.
   * **Default** — 10 seconds.
4. After the delay, the order attempts to execute:
   * If the odds improve, it matches instantly.
   * If the odds decrease, it does not match.

## Per-order errors

<Snippet file="types/order-error.mdx" />

Example with multiple places, some erroring:

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "createdSessions": [
        { "matched": [/* successful fill */], "unmatched": {} },
        { "error": "game not found: invalid gameID", "errorType": "validation_error" },
        { "error": "Insufficient balance.",          "errorType": "rejected_liability" },
        { "error": "post order cannot have matches", "errorType": "rejected_order_type_rules" },
        { "error": "failed to interact with database","errorType": "system_error" }
      ]
    }
  }
  ```
</CodeGroup>


## OpenAPI

````yaml POST /session/v3/place
openapi: 3.1.0
info:
  title: 4casters REST API
  version: 1.0.0
  description: >-
    Public REST API for the 4casters peer-to-peer betting exchange. Use this API
    to manage your account, query the orderbook and games, and place / edit /
    cancel orders.


    All responses (unless noted otherwise) are JSON envelopes of the form `{
    "data": ... }`.
  contact:
    name: 4casters
    url: https://4casters.io
servers:
  - url: https://api.4casters.io
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: Login and account session management
  - name: User
    description: Read account info, bets, and orders
  - name: Orders
    description: Place, edit, look up, and cancel orders
  - name: Markets
    description: Browse leagues, games, participants, and orderbooks
  - name: Affiliate
    description: Affiliate / referral commission
paths:
  /session/v3/place:
    post:
      tags:
        - Orders
      summary: Place orders
      description: >-
        Submit one or more orders. Per-order results are returned in
        `data.createdSessions`, **positionally** with the input `orders` array.
        Each entry is either a successful `{ matched, unmatched }` result or an
        `{ error, errorType }` failure.


        See the dedicated [Place order](/pages/rest/orders/place-order) page for
        examples and the full set of order-type rules (`limit`, `post`,
        `postArb`, `fillAndKill`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaceOrderRequest'
      responses:
        '200':
          description: Per-order results (positional with input).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      createdSessions:
                        type: array
                        items:
                          $ref: '#/components/schemas/PlaceOrderResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          description: Orders service unreachable
components:
  schemas:
    PlaceOrderRequest:
      type: object
      required:
        - orders
      properties:
        orders:
          type: array
          items:
            $ref: '#/components/schemas/PlaceOrderInput'
    PlaceOrderResult:
      oneOf:
        - $ref: '#/components/schemas/PlaceOrderSuccess'
        - $ref: '#/components/schemas/OrderError'
      description: >-
        Either a successful place result or a per-order error. Returned
        positionally with the request `orders` array.
    PlaceOrderInput:
      type: object
      required:
        - gameID
        - type
        - side
        - odds
        - bet
      properties:
        gameID:
          type: string
          description: 4casters game id.
        type:
          $ref: '#/components/schemas/MarketType'
        side:
          $ref: '#/components/schemas/MarketSide'
        market:
          type: string
          description: >-
            **`moneyline1x2` only.** Either `"draw"` or a participant id — names
            the outcome you're betting yes/no on.
        odds:
          type: integer
          description: Order odds in **American** format (e.g. `-110`, `+150`).
        bet:
          type: number
          description: Amount to risk on the bet.
        number:
          type: number
          description: >-
            Spread or total number. Required for `spread` and `total`. Not used
            for `moneyline` or `moneyline1x2`.
        orderType:
          $ref: '#/components/schemas/OrderType'
        expirationMinutes:
          type: integer
          description: Auto-cancel after N minutes. Omit to keep until game start.
        userReference:
          type: string
          description: >-
            Client-defined identifier preserved on every fill / offer derived
            from this order.
    PlaceOrderSuccess:
      type: object
      properties:
        matched:
          type: array
          items:
            $ref: '#/components/schemas/MatchedFill'
        unmatched:
          $ref: '#/components/schemas/UnmatchedOffer'
    OrderError:
      type: object
      properties:
        error:
          type: string
          description: Human-readable, non-stable description of the failure.
        errorType:
          $ref: '#/components/schemas/ErrorType'
        error_type:
          type: string
          deprecated: true
          description: >-
            Legacy alias of `errorType` (still emitted by the WebSocket placeV3
            path).
    MarketType:
      type: string
      enum:
        - moneyline
        - spread
        - total
        - moneyline1x2
      description: >-
        Market type. `moneyline1x2` is **soccer-only** (three-way money line:
        home / away / draw).
    MarketSide:
      type: string
      description: |-
        Order side. Meaning depends on `type`:

        - `moneyline`, `spread` — the participant id you are backing.
        - `total` — `"over"` or `"under"`.
        - `moneyline1x2` — `"yes"` or `"no"` on the outcome named by `market`.
    OrderType:
      type: string
      enum:
        - limit
        - post
        - postArb
        - fillAndKill
      description: >-
        Order placement strategy.


        - `limit` — default. Match against any liquidity at your price or better
        as a taker, rest the remainder as a maker.

        - `post`  — guarantee maker. Reject if the order would match resting
        liquidity.

        - `postArb` — like `post`, but allowed to match if the matched odds are
        within 1% of yours, avoiding taker fees.

        - `fillAndKill` — guarantee taker. Match immediately or reject.
    MatchedFill:
      type: object
      description: A portion of an order that matched against existing liquidity.
      properties:
        amount:
          type: number
          description: Stake on the matched portion.
        odds:
          type: integer
          description: American odds of the fill.
        number:
          type: number
          nullable: true
          description: Spread or total of the fill (`null` for moneylines).
        type:
          $ref: '#/components/schemas/MarketType'
        side:
          $ref: '#/components/schemas/MarketSide'
        market:
          type: string
          description: Present for `moneyline1x2`.
        orderID:
          type: string
          description: Order id of the matched offer on the other side.
        txID:
          type: string
          description: Transaction id of the fill.
        wagerRequestID:
          type: string
          description: >-
            Server-generated id grouping every fill / offer derived from the
            input order.
        userReference:
          type: string
        risk:
          type: number
        win:
          type: number
          description: Win amount, net of taker commission.
        winWithoutCommission:
          type: number
          description: Win amount before commission.
    UnmatchedOffer:
      type: object
      description: >-
        Resting offer created from the unfilled remainder of a place request.
        May be empty (`{}`) when the order matched fully.
      properties:
        orderID:
          type: string
          description: Id of the new resting order.
        wagerRequestID:
          type: string
        offered:
          type: number
        odds:
          type: integer
        type:
          $ref: '#/components/schemas/MarketType'
        side:
          $ref: '#/components/schemas/MarketSide'
        market:
          type: string
          description: Present for `moneyline1x2`.
        number:
          type: number
          nullable: true
        userReference:
          type: string
    ErrorType:
      type: string
      enum:
        - validation_error
        - rejected_liability
        - rejected_order_type_rules
        - system_error
      description: >-
        Programmatic per-order error class:


        - `validation_error` — payload or state failed validation (missing
        fields, bad odds/number, invalid side, inactive game).

        - `rejected_liability` — user/account/liability constraints prevented
        posting or execution.

        - `rejected_order_type_rules` — order-type rules forbade execution (e.g.
        `fillAndKill` had no executable liquidity, `post` would match, or
        `postArb` is more than 1% away from the resting price).

        - `system_error` — transient/internal error; retry may succeed.
  responses:
    Unauthorized:
      description: Missing or invalid auth token
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Pass your auth token in the `Authorization` header. The `Bearer` prefix
        is optional; the server also accepts a signed `auth` cookie or a `token`
        field in the request body.

````