> Treat every fee rate, margin ratio, order limit, minimum and address in this documentation as an illustration of the shape of the value, never as a constant to hardcode - the live values are served by GET /markets and GET /config on the Perps API.

# Position Lifecycle

> Enter, protect, adjust, and exit a position — in the order the API requires.

The sequence every integration implements, in the order the API enforces. Four rules shape it:

| Rule                                                           | If you miss it                                        |
| -------------------------------------------------------------- | ----------------------------------------------------- |
| `200` from `POST /orders` means **queued**, not filled         | You book a position that never existed                |
| `/positions` lags the fill                                     | You read "no position" and leave the fill unprotected |
| Direction is the **sign of `size`** — a position has no `side` | Your stop sits on the wrong side and fires at once    |
| A position with an armed trigger **cannot be closed**          | You cannot exit until the trigger is cancelled        |

> **Tip**
>
> Step 4 folds into step 1 if you attach the stop to the entry as a [bracket order](/developers/perps-api/trading#bracket-orders): the TP/SL legs are accepted with the entry and arm themselves once it fills, so the position is never unprotected while you poll steps 2 and 3. Everything from step 5 on applies unchanged once they are armed.

## 1. Enter

`POST /orders` — one `MARKET` leg, `limit_price` as your slippage bound.

The leg returns `SUBMITTED` — queued, not filled.

## 2. Confirm the fill

Poll `GET /orders/{orderId}` until a [terminal status](/developers/perps-api/trading#order-lifecycle).

An early `404` means not yet persisted. `FILLED` or `PARTIALLY_FILLED_CANCELLED` is a fill; `avg_fill_price` is the executed price.

## 3. Read the position

Poll `GET /account/{account_id}/positions` until the market appears. A single read right after the fill regularly returns an empty array on a position that exists.

Direction is the sign of `size`: positive long, negative short.

## 4. Attach the stop

`POST /orders` — one reduce-only `TRIGGER` leg with `size: "0"`, which closes the whole position at fire time. (Skip this step if the stop was attached at entry — it is already armed, and it replaced any older whole-position stop of the same kind.)

| Position | `side` | `trigger_price` | `limit_price` (fire) |
| -------- | ------ | --------------- | -------------------- |
| Long     | `SELL` | below mark      | below the trigger    |
| Short    | `BUY`  | above mark      | above the trigger    |

* Derive the level from the current mark, not `entry_price`; a trigger already past the mark on its fire side is rejected.
* `limit_price` is where the IOC fires; `trigger.trigger_price` is what arms it.
* Keep the returned `order_id` for the modify and the exit.

## 5. Move the stop

`POST /orders/{orderId}/modify` — every field, every call; there is no partial modify.

* Only `limit_price`, `size`, `trigger_price` change. Resend `size: "0"`.
* A stop still waiting for its entry (`parent_order_id` set) cannot be modified — move the entry instead, or cancel the stop and attach a new one once the entry fills.
* `client_order_id` must be new and is signed here, unlike on an order leg.
* Success returns a **new `order_id`**; the old one is dead.

An armed trigger reads `SUBMITTED` for its whole life. That is not a stuck order.

## 6. Exit

In this order:

1. **Cancel every armed trigger on the market** — `GET /account/position-triggers?wallet=…`, then `POST /orders/{orderId}/cancel` with `order_type: "TRIGGER"` for each.
2. **Wait until they are gone** — cancellation is asynchronous; poll `position-triggers`.
3. **Close** — one reduce-only `MARKET` leg, `side` against the position, `size` from `closeable`. Reduce-only legs are exempt from `min_order_value`.

Closing while a trigger is still armed fails with `close_trigger_coexistence`. There is no atomic flip: reverse by exiting, then entering again.

**Next:** [Account Data](/developers/perps-api/account)