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

# Trading

> Place orders and read your orders and positions. The order lifecycle, polling, what to do after each answer, idempotency and every error code.

The same API also lets a program place orders on Splash prediction markets, read its own orders,
positions and transaction history, and read its fee settings and cash balance. Find the markets to trade with the [market endpoints](https://docs.splashsports.com/predictions/overview.md); a market's `id` is
the `market_id` you order against.

## Rate limits

Requests are limited to 10 per second per IP address; see [Rate limits](https://docs.splashsports.com/predictions/rate-limits.md) for what a `429`
looks like, how to retry it and how to stay under the limit.

## Base URL

```
https://api.splashsports.com/predictions-service/api
```

Paths in the reference are relative to the base URL. The market endpoints are open. The order, position, account and balance endpoints must
be [signed](https://docs.splashsports.com/predictions/authentication.md) with an API key. API keys are issued by Splash on request, never expire, and stay
valid until Splash revokes them, which it does on request.

## Placing an order and following it

Orders are asynchronous.

Before a buy, check that you have the cash for it. Splash adds a fee to the contracts cost and debits both when the order
is submitted; a buy without enough cash is accepted and then cancelled without filling. Read your fee settings and
`max_trade` from `GET /account` and your `cash_available` from `GET /balance`, compute the order's cost, and send the raw
order only when it fits. The order never carries a fee: Splash computes it. The formulas and a calculator are in
[Fees and order cost](https://docs.splashsports.com/predictions/fees.md).

1. `POST /trading/orders` returns `200` with `order_status: "pending"`. That means the order was
   **accepted**, not filled: it is queued.
2. Poll `GET /orders?request_id=<your request_id>` until the order's `status` is `executed` or
   `cancelled`. Poll every 500 ms to 1 second for the first few seconds, then every few seconds; keep polling while
   it reads `pending`. See [Rate limits](https://docs.splashsports.com/predictions/rate-limits.md).
3. While the order is queued, or after it was refused, the poll returns a minimal
   `{ id, request_id, status, error? }` (see [PendingOrder](https://docs.splashsports.com/predictions/types/pending-order.md)). Once the order is
   recorded it returns the full [OrderWithMarket](https://docs.splashsports.com/predictions/types/order-with-market.md).

Only fill-or-kill is available today: set `time_in_force` to `"FOK"`, and the order fills completely at your
price or better, or is cancelled. `IOC` is coming; until then it is a `400` with
`IOC orders are not yet supported`.

## Order outcomes and retries

Not getting a `200` does not mean the order failed. What each answer to `POST /trading/orders` tells you, and what to do
next:

| You got | What it means | What to do |
| - | - | - |
| `200` with `order_status: "pending"` | The order is queued. | Poll `GET /orders?request_id=` (or `GET /orders/{id}`) until `status` is `executed` or `cancelled`. |
| No response (your client timed out, the connection dropped), or a `504` from the gateway | **Unknown.** The order may have gone through. | Check `GET /orders?request_id=`. If it returns the order, follow it. If it returns nothing, send the request again with the **same** `request_id`: it can never place the order twice. |
| `401`, `415`, `429`, `400 Invalid request parameters`, `400 IOC orders are not yet supported`, or `503 TRADING_API_UNAVAILABLE` | Refused before the order was accepted. Nothing was recorded. | Fix the cause and send it again; after a `429`, wait the `Retry-After` seconds first. Reusing the same `request_id` is fine. |
| Any other error: a `400` order-check message (`Invalid price`, `Invalid quantity`, `Market is not active`, `Not eligible to trade this market`, `Order exceeds the maximum trade of $<limit>`, the opposite-side message, the maintenance pause), `403`, `404`, `409`, `503 EXCHANGE_UNAVAILABLE` or `SERVICE_UNAVAILABLE`, or `500` | The order was not placed, and that `request_id` is **spent**. | Send a **new** `request_id` to try again. |

A spent `request_id` stays spent for 24 hours. Polling it reads `cancelled` with the refusal's message in `error`, and
sending it again returns `200` with `order_status: "cancelled"` and places nothing. Two exceptions to the poll: after a
`409` it returns nothing, because the `request_id` is another account's; and after a `500`, poll before trying again and
follow the order if the poll returns one.

A body that is not valid JSON, or over 3 MB, is refused by the web framework before anything is read, with a `400` or
`413` that may not be JSON. Nothing was recorded, so the same `request_id` is fine once the body is fixed.

A poll of a spent `request_id`, here after `400 Market is not active`:

```json theme={null}
{
  "status": "success",
  "data": [
    {
      "id": "ord_6aededb87e1c4b9f2d6a03e8b5c1d7f2",
      "request_id": "a6d0e3b7-92c4-4f1e-8b5a-d3c7f0e9a214",
      "status": "cancelled",
      "error": "Market is not active"
    }
  ],
  "total": 1,
  "offset": 0,
  "limit": 50
}
```

## Idempotency

`request_id` is required on every order: 1 to 64 characters from `A-Z a-z 0-9 _ -`. A UUID is ideal.

* **One `request_id` never places more than one order.** Sending your own `request_id` again returns the order it
  already names (same `order_id`) with its current status, and `cancelled` if the first request was refused. When to
  reuse it and when to send a new one: [Order outcomes and retries](#order-outcomes-and-retries).
* **Another account's `request_id` is `409 DUPLICATE_REQUEST_ID`.** Use a fresh random value.

## Conventions

* **No `null` in a response.** A value that is unset is omitted. Test for presence.
* **Timestamps are epoch milliseconds** on orders and positions.
* **Money is a number in dollars.** A price is per contract, between 0 and 1. A price you send is between
  0.01 and 0.99.
* **Dollar fields are the source of truth.** Fills land on half-cents, so do not recompute a cost as price
  times quantity; read `fill_cost`, `cost` and the other dollar fields.
* **Pagination.** Lists take `limit` (1 to 50, default 50) and `offset`.
* **Rate limit.** At most 10 requests per second per IP address, whichever key they use. See
  [Rate limits](https://docs.splashsports.com/predictions/rate-limits.md).
* **Your data only.** Every route acts as the key's own account. `GET /orders`, `GET /orders/{id}`, `GET /positions`,
  `GET /positions/{id}` and `GET /transactions` refuse a `user_id` query parameter with a `401`; the other routes ignore it.

## Errors

An error is `{ "status": "error", "error": "<CODE>", "message": "<text>" }`. `error` is a string; the codes below
are the ones these routes return today, and a code not listed here should be handled by its HTTP status. Every code
is also on [Enumerations](https://docs.splashsports.com/predictions/enums.md#trading-error-codes-error). For `POST /trading/orders`, whether a `request_id`
can be reused after each error is in [Order outcomes and retries](#order-outcomes-and-retries).

| HTTP | `error` | When |
| - | - | - |
| `400` | `BAD_REQUEST` | The body or query is invalid (`Invalid request parameters`); `IOC orders are not yet supported`; or an order check failed (`Invalid price`, `Invalid quantity`, `Market is not active`, `Not eligible to trade this market`, `Order exceeds the maximum trade of $<limit>` (`<limit>` is the account's configured maximum), `You already own the other side of this market. Please sell your open position for this market first.`, `Trading is temporarily disabled for scheduled maintenance. Please try again later.`). |
| `401` | `NOT_AUTHORIZED` | The signature check failed; the request had no `SPLASH-ACCESS-KEY` header (`You are not authorized to perform the requested action.`); or `user_id` was passed to one of the five routes above. See [Authentication errors](https://docs.splashsports.com/predictions/authentication.md#authentication-errors). |
| `403` | `FOBIDDEN` | `Not authorized to trade`: the account may not trade. The code is spelled `FOBIDDEN` on the wire; match it exactly. |
| `404` | `NOT_FOUND` | No such order or position on your account, or `Market not found`. |
| `409` | `DUPLICATE_REQUEST_ID` | The `request_id` belongs to another account's order. |
| `415` | `UNSUPPORTED_MEDIA_TYPE` | A signed request carried a body that is not JSON. Send `Content-Type: application/json`. |
| `500` | `INTERNAL_ERROR` | The request could not be handled. On `POST /trading/orders` the order was not placed; poll the `request_id`, then try again with a new one. |
| `503` | `TRADING_API_UNAVAILABLE` | The Trading API is not taking signed requests right now. Retry later; on `POST /trading/orders` the same `request_id` is fine. |
| `503` | `EXCHANGE_UNAVAILABLE` or `SERVICE_UNAVAILABLE` | The exchange is unavailable, or an eligibility check could not run. The order was not placed; retry later with a new `request_id`. On `GET /balance`, `SERVICE_UNAVAILABLE` means your balance could not be read; retry shortly. |

A `429 Too Many Requests` is the rate limit, not one of these codes. It records
nothing: wait and retry the same request, as in [Rate limits](https://docs.splashsports.com/predictions/rate-limits.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.