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

# Order lifecycle

> Authenticate, discover a market, sign, submit, confirm, cancel, replace, and manage sub-accounts.

This guide uses OMS for trading and account state. Market data comes from MDS; see [Market data client](/guides/market-data-client).

## Flow

1. Authenticate: `GET /nonce/{user_id}`, sign the nonce value, `POST /auth`.
2. Discover the market on OMS: `GET /markets` or `GET /market/{asset_id}` for book decimals and fees.
3. Sign the action-specific object: order create signs `order`, replace signs `request`, cancel signs `cancel`.
4. Submit over REST or the `/control` WebSocket.
5. Treat the REST response as admission, not finality. Confirm over the orders and trades streams or by refetching orders.

Numeric examples use raw book units. With `book_price_decimals = 2`, `250000` means `2500.00`. With `book_quantity_decimals = 8`, `125000000` means `1.25000000`.

## Raw REST

```bash theme={null}
curl -X POST https://oms.tplus.cx/orders/create \
  -H "Authorization: Bearer $TOKEN" \
  -H "User-Id: $USER_ID" \
  -H "Content-Type: application/json" \
  --data @create-order.json
```

`create-order.json` contains the signed body from [Signing](/guides/signing#create-order). Replace:

```bash theme={null}
curl -X PATCH https://oms.tplus.cx/orders/replace \
  -H "Authorization: Bearer $TOKEN" \
  -H "User-Id: $USER_ID" \
  -H "Content-Type: application/json" \
  --data @replace-order.json
```

Cancel:

```bash theme={null}
curl -X DELETE https://oms.tplus.cx/orders/cancel \
  -H "Authorization: Bearer $TOKEN" \
  -H "User-Id: $USER_ID" \
  -H "Content-Type: application/json" \
  --data @cancel-order.json
```

Batch create wraps signed create requests:

```json theme={null}
{
  "orders": [
    { "...": "signed create request 1" },
    { "...": "signed create request 2" }
  ]
}
```

Batch create is not all-or-nothing: accepted orders remain live even if a later item rejects.

Cancel-all is unsigned at the action layer and authenticated by the session. OMS collects open orders with `timestamp_ns < max_ts`; if `max_ts` is omitted, it uses server time:

```bash theme={null}
curl -X DELETE "https://oms.tplus.cx/orders/cancel-all?max_ts=1760000000000000000" \
  -H "Authorization: Bearer $TOKEN" \
  -H "User-Id: $USER_ID"
```

Use `DELETE /orders/cancel-all/{asset_id}` to scope to one market. Use `DELETE /orders/cancel-batch` with order IDs for a selected subset; IDs not owned by the caller are ignored.

## tpluspy

```python theme={null}
from tplus.client import OrderBookClient
from tplus.model.asset_identifier import AssetIdentifier
from tplus.model.limit_order import GTC
from tplus.model.order import TradeTarget
from tplus.utils.user import User

OMS = "https://oms.tplus.cx"
asset = AssetIdentifier(200)
user = User(private_key="<ed25519-private-key-hex>")

async with OrderBookClient(user=user, base_url=OMS) as client:
    market = await client.get_market(asset)
    created = await client.create_limit_order(
        asset_id=asset,
        side="Buy",
        price=250000,
        quantity=125000000,
        time_in_force=GTC(post_only=False),
        target=TradeTarget(account=1, is_spot=False),
    )
    replaced = await client.replace_order(
        original_order_id=created.order_id,
        asset_id=asset,
        new_price=251000,
        new_quantity=100000000,
    )
    canceled = await client.cancel_order(created.order_id, asset)
```

Pass `use_ws_control=True` to `OrderBookClient` to send create, replace, and cancel over `/control` instead of REST. The control channel accepts v0 raw action wrappers or v1 envelopes with `request_id`.

## Replace semantics

Replace preserves the order ID. The mutable fields are price, quantity, and trigger; the request also carries book decimals when price or quantity changes. Fill state of a partially filled enabled order is preserved. A replace with an older `timestamp_ns` than a newer applied replace is rejected by the orderbook.

REST replace and REST cancel first look up the current OMS order and return not found if absent. The control channel forwards directly to the orderbook; a cancel that reaches the fast queue before its create can reject the later create.

## Sub-accounts, transfers, and close-all

There is no standalone "create sub-account" call. Transfer spot balance into an unused `target_index` and include `target_account_type`.

Raw transfer:
Populate `signature` with the Ed25519 signature bytes over compact `inner` before sending.

```json theme={null}
{
  "inner": {
    "user": "<user public key>",
    "source_index": 0,
    "target_index": 1,
    "transfer_asset": "0",
    "transfer_amount": "1000000000000000000",
    "target_account_type": {"CrossMargin": null},
    "nonce": 1760000000000
  },
  "signature": [],
  "additional_signers": []
}
```

tpluspy-assisted transfer, using the SDK user signer and the raw request path:

```python theme={null}
import json
import time

inner = {
    "user": user.public_key,
    "source_index": 0,
    "target_index": 1,
    "transfer_asset": "0",
    "transfer_amount": str(10**18),
    "target_account_type": {"CrossMargin": None},
    "nonce": time.time_ns(),
}
signing_payload = json.dumps(inner, separators=(",", ":"))
signing_payload = signing_payload.replace(" ", "").replace("\r", "").replace("\n", "")
payload = {
    "inner": inner,
    "signature": list(user.sign(signing_payload)),
    "additional_signers": [],
}
await client._request("POST", "/account/transfer/sub-account", json_data=payload)
```

Use a client-generated, strictly increasing nonce per action domain and treat that nonce as the retry key: after a timeout, check `/account/events/{user_id}` and `/inventory/user/{user_id}` before sending a new nonce. The current `request_transfer_to_subaccount` SDK helper does not include the required signed `nonce` field; use the explicit signing path above until the helper exposes it.

Close-position is available only after the base leg is zero. It folds the remaining quote credits into spot USD, or repays quote liabilities from spot USD, and emits `PositionCleared`; if a quote liability remains, the spot USD balance must cover it.

```python theme={null}
inner = {
    "user": user.public_key,
    "account": 1,
    "asset_identifier": "200",
    "nonce": time.time_ns(),
}
signing_payload = json.dumps(inner, separators=(",", ":"))
signing_payload = signing_payload.replace(" ", "").replace("\r", "").replace("\n", "")
payload = {
    "inner": inner,
    "signature": list(user.sign(signing_payload)),
    "additional_signers": [],
}
await client._request("POST", "/account/transfer/close-position", json_data=payload)
```

The current `request_close_position` SDK helper also omits the required signed `nonce`; use the explicit signing path above until the helper exposes it.

Close-all is a preview loop:

1. `GET /positions/close-all/{user_id}/{sub_account}` returns unsigned reduce-only close orders.
2. Sign each suggested order.
3. Submit with `POST /orders/batch-create`.
4. Watch fills, then call close-position for each flattened asset.
5. Re-run the preview after partial fills or price movement.
