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

# Errors

> Status codes, the error response shape, and how venue rejections are surfaced

<Warning>
  **Error reporting is still in development.** Responses today carry a human-readable `message` and an HTTP `statusCode`, but **not** a stable machine-readable error code, and some venue rejections arrive without the venue's own reason attached (see [Reading order rejections](#reading-order-rejections)). Readable venue errors and a stable error code are planned. Until they ship, branch your logic on `statusCode` and treat `message` as text whose wording can change.
</Warning>

Every error response from the API uses one of three JSON shapes. Validation and most client errors follow the standard shape:

```json theme={null}
{
  "statusCode": 400,
  "message": "Invalid signature",
  "error": "Bad Request"
}
```

Errors that originate at a connected exchange use a slimmer shape, with the venue name prefixed onto the message:

```json theme={null}
{
  "message": "hyperliquid: Create order error: Insufficient margin",
  "statusCode": 503
}
```

Order-parameter validation failures use a third shape that lists each rejected field:

```json theme={null}
{
  "errors": [
    { "message": "Size \"5.1911\" does not match the market step size (0.01). Use the normalized value: 5.19", "path": ["size"] }
  ],
  "message": "aster: Order validation failed",
  "statusCode": 400
}
```

Each entry's `path` names the offending field (`["size"]`, `["price"]`, `["timeInForce"]`, `["trigger","price"]`, and so on). When a value is merely too precise, the message includes the exact normalized value to resubmit — or set `normalizeParams: true` on the order to have the API round it for you (see [Preparing orders](/docs/trading-preparing-orders)).

Operations a venue doesn't offer at all return `400` with `"<exchange>: <method> is not supported"` — for example `{ "message": "extended: batchCreateOrders is not supported", "statusCode": 400 }`.

An API key missing a permission the venue requires is also a `400`, raised when you connect the exchange rather than when you trade. The message names what to turn on:

```json theme={null}
{
  "statusCode": 400,
  "message": "binance: API key is missing the \"Enable Futures\" permission. Turn it on in Binance API Management, then reconnect."
}
```

MEXC reports the same way, listing every futures permission the key lacks. Fix the key at the venue and call the connect endpoint again — see [Use your own keys](/docs/exchanges).

There is no machine-readable error code field today — match on `statusCode` and, where you need finer detail, the `message` string.

## Status codes

| Status | Meaning | Where it comes from |
| - | - | - |
| `400` | The request was rejected before reaching the venue | Failed order validation — a price/size precision or tick/step mismatch, a value below the venue minimum or above a maximum, a price outside the allowed band, a `timeInForce` the venue or market doesn't accept (or sent on a market order), an [ambiguous `asset`](/docs/trading-markets#ambiguity), an operation the venue doesn't support, or an API key missing a required venue permission. Order-parameter failures carry the per-field `errors` array (see above) |
| `401` | The connected exchange rejected the credentials | The body carries `exchange` and a fixed `message: "Invalid credentials"` |
| `503` | The venue rejected the order or was unavailable | The `message` is `"<exchange>: <reason>"` — readability varies by venue (see below). Genuinely venue-side rejections land here: insufficient margin, would-not-reduce, and similar |
| `500` | An unexpected server-side error | Something failed while handling the request; it is usually safe to retry |

A `400` means the order never left VOOI — fix the request and resend. A `503` means the venue saw the order and either refused it or was briefly unreachable. Because those two cases share a status code, **confirm whether the order actually landed before retrying** (see [Reading order rejections](#reading-order-rejections)): retrying a true rejection is safe, but retrying after an order that silently went through will double-fill.

## Reading order rejections

When a venue rejects an order, VOOI returns it as a `503`. Whether the venue's own reason reaches you depends on the exchange and on how the venue reported the failure:

| Exchange | Rejection readability | What you see |
| - | - | - |
| **Aster** | Readable | The venue's message and numeric code are included in `message` |
| **Hyperliquid** | Mostly readable | Logical rejections (insufficient margin, would-not-reduce, and similar) include the venue's reason; a smaller class of transport-level failures returns a status-only message without the reason |
| **Extended** | Readable | The venue's message and numeric code are included in `message`, as `Extended error: <reason>. Code: <code>` |
| **Lighter** | Readable, verbose | The venue's raw response is included in `message`; confirm the outcome through order status or the stream rather than parsing this string |
| **Robinhood** | Readable, verbose | Same as Lighter — Robinhood runs on Lighter's infrastructure |
| **Binance** | Readable | The venue's message is included in `message` |
| **Bybit** | Readable | `Bybit server error: <reason>. Code: <retCode>` |
| **MEXC** | Readable | `Mexc error: <reason>. Code: <code>` |
| **Ondo** | Readable | `Ondo error: <reason>. Code: <code>` |

Because the surfaced reason is venue-dependent and not yet guaranteed, **confirm every order's outcome through [order status or the real-time stream](/docs/trading-streams)** rather than trusting the submit response alone. This is the single most reliable way to know whether an order was accepted, across all venues.

## Common order-preparation failures

Most rejections on order placement trace back to a value the venue couldn't accept. These are now caught before the order leaves VOOI and returned as readable `400`s with the per-field `errors` array. Each of these is covered in [Preparing orders](/docs/trading-preparing-orders):

* **Below the venue minimum** — e.g. a sub-\$10 order on Hyperliquid returns `Order must have minimum value of $10` on `path: ["size"]`. See [Minimum order size](/docs/trading-preparing-orders#minimum-order-size), where `reduceOnly` waives the check on most venues.
* **Price or size precision mismatch** — too many decimals or, on Hyperliquid, too many significant figures. The message includes the normalized value to resubmit. See [Price and size precision](/docs/trading-preparing-orders#price-and-size-precision).
* **Size rounded to zero** — a notional too small to represent one lot step. Don't submit it.
* **`timeInForce` without a `price`** — send a market order as `{ asset, exchange, side, size }` with no `timeInForce`.

## Token errors

Errors specific to creating API tokens — expired signatures, unset public keys, name collisions — are listed with [API Tokens](/docs/tokens#errors).

## Next steps

<CardGroup cols={2}>
  <Card title="Preparing orders" icon="ruler-combined" href="/docs/trading-preparing-orders">
    Round and size orders so the venue accepts them
  </Card>

  <Card title="Real-time updates" icon="rss" href="/docs/trading-streams">
    Confirm fills through the account and order stream
  </Card>
</CardGroup>


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