# Orders and positions

> MetaTrader API docs for developers and coding agents. Last updated: 2026-10-14. Index: https://www.metatraderapi.net/llms.txt
>
> © 2026 Apex Vanguard Dynamics LLC. You, and the software tools and AI agents working for you, may read and use this documentation to build, run and support integrations with MetaTrader API; its code examples may be copied into your own software. Republishing or redistributing the documentation, in whole or in substantial part, is not permitted. Terms: https://www.metatraderapi.net/en/terms/

All trading endpoints are `GET` requests with query parameters, authenticated like every other call (`x-api-key` header plus `?id=`). They work the same on MT4 and MT5 unless noted.

## Place an order: /OrderSend

```bash
curl -s -H "x-api-key: $MTAPI_KEY" \
  "https://api.metatraderapi.net/OrderSend?id=$MTAPI_ID&symbol=EURUSD&operation=Buy&volume=0.10&stoploss=1.0800&takeprofit=1.0950&expertId=42&comment=bot-1&clientOrderId=sig-7781-acct-3"
```

| Param | Required | Meaning |
|---|---|---|
| `symbol` | yes | the broker's symbol name, exactly (see `/SymbolList`) |
| `operation` | yes | `Buy`, `Sell`, `BuyLimit`, `SellLimit`, `BuyStop`, `SellStop`, `BuyStopLimit`, `SellStopLimit` (case-insensitive), or the MetaTrader code `0`-`7` in that order. Stop-limit types are MT5 only. |
| `volume` | yes | size in **lots** |
| `price` | pending orders | the order's price. Market orders always use the current ask (buy) or bid (sell). |
| `stoploss`, `takeprofit` | no | absolute prices; `0` or omitted = none |
| `slippage` | no | maximum deviation in **points**, default `10` |
| `stopLimitPrice` | stop-limit only | the limit price that is placed when the stop price is reached |
| `expiration` | no | pending orders only; ISO-8601 or unix seconds. MetaTrader reads it in the **broker server's** time: add the account's offset from `/ServerTimezone` (minutes) to the UTC time you want. Without it a pending order is good until cancelled. |
| `expertId` (alias `magic`) | no | magic number, to recognise your own orders later |
| `comment` | no | free text; the broker may shorten or replace it |
| `clientOrderId` | strongly recommended | your unique id for this order, 1–64 characters of `A-Z a-z 0-9 _ . : -`. Also accepted as the `Idempotency-Key` header. |

The response is an order object:

```json
{"ticket": 123456789, "symbol": "EURUSD", "orderType": 0, "lots": 0.1, "volume": 1000, "openPrice": 1.08734, "comment": "Request executed", "state": 10009, "openTime": "2026-10-12T09:15:02Z", "openTimestampUTC": 1791796502, "digits": 5, "contractSize": 100000.0, "requestId": 1}
```

(Shortened; the full object also has `profit`, `swap`, `commission`, close fields and more.) This is the broker's acknowledgement: `comment` is the broker's result text, and stop loss, take profit and magic number are not echoed here (they read `0`). Read `/OpenedOrders` to see the position with its SL, TP, `expertId` and your comment.

## The one check you must not skip

**A broker refusal is HTTP 200.** Always read `state`:

- `10009`: done. The order is placed or filled; `ticket` identifies it.
- anything else: the broker refused it, and `state` is the reason. Common MT5 codes: `10004` requote, `10006` request rejected, `10013` invalid request, `10014` invalid volume, `10015` invalid price, `10016` invalid stops, `10018` market closed, `10019` not enough money, `10027` algo trading disabled in the client. (`10030`, filling mode not supported, is handled for you: the API retries with the other filling modes.) On MT4 a refusal puts the MT4 error number in `state`.

The fill price in this answer is a fast acknowledgement. For the authoritative open time and price, read `/OpenedOrders` afterwards.

## Safe retries with clientOrderId

Networks fail after a request was sent. `clientOrderId` makes `/OrderSend` safe to retry:

- Same `clientOrderId` again: you get the **original** result (with header `Idempotent-Replay: true`) and no second order is sent.
- `409 IDEMPOTENCY_UNCERTAIN`: the first attempt timed out after it was sent, so the API cannot know whether the broker executed it. Check `/OpenedOrders` (filter by your `expertId`) before sending again **with a new id**.
- `422 IDEMPOTENCY_KEY_REUSED`: that id was used for a different order. Use a new id for every new order.
- A broker refusal is a final result too: the same id returns the same refusal. To send a corrected order, use a new id.
- Ids are remembered per account for 24 hours, in the API's memory: a restart of the API (for example during an update) forgets them. After a long outage, check `/OpenedOrders` before resending.

`clientOrderId` applies to `/OrderSend` only. Close, modify and delete are not de-duplicated: before retrying one, check whether it already happened.

## Modify, close, delete

| Endpoint | Params | Notes |
|---|---|---|
| `/OrderModify` | `ticket`, `stoploss`, `takeprofit`, `price`, `expiration`, `stoplimit` | omitted values are kept, except a pending order's `expiration`: send it again on every modify, or the order becomes good until cancelled |
| `/OrderClose` | `ticket`, `lots`, `price`, `slippage`, `comment` | `lots` omitted, `0` or more than open = full close; less = partial close. On MT5 a pending order's ticket deletes it. |
| `/OrderDelete` | `ticket` | deletes a pending order |
| `/OrderCloseBy` | `ticket`, `opposite` | closes a position by an opposite position of the same symbol (hedging accounts) |

All return an order object with `state` as above.

## Before you send: checks that prevent refusals

- **Symbol**: resolve from `/SymbolList`; brokers add suffixes (`EURUSD.m`, `XAUUSDm`). Never hard-code across brokers.
- **Volume**: `/SymbolParams?symbol=` returns `symbolGroup.minVolume`, `maxVolume` and `volumeStep` in lots x 10000 (so `100` = 0.01 lot). Round your lot size to the step.
- **Stops distance**: `/SymbolParams` returns, in `symbolInfo`, `stopsLevel` (minimum SL/TP distance from the current price, in points) and `freezeLevel` (within this distance an order can no longer be modified).
- **Market open**: `/IsTradeSession?symbol=` answers `true` when a price arrived in the last 2 minutes and the symbol is tradable (it is an estimate, not the broker's session calendar); the broker refuses orders outside its trading session (`10018`).
- **Margin**: `/RequiredMargin?symbol=&lots=&operation=` returns the margin the order needs; compare with `freeMargin` from `/AccountSummary`.

## Reading positions and history

| Endpoint | Returns |
|---|---|
| `/OpenedOrders` | open positions **and** pending orders, as order objects |
| `/OpenedOrder?ticket=` | one of them |
| `/OrderHistory?from=&to=` | closed trades. `from` / `to` are ISO-8601 or unix seconds; without them, the whole history. |
| `/OrderHistoryPagination?ordersPerPage=&pageNumber=` | the same, paged (defaults 100 and 1; `partialResponse: true` means more pages) |
| `/HistoryPositions`, `/HistoryDealsByPositionId?ticket=` | closed positions; all deals of one position |
| `/BalanceHistory` | deposits, withdrawals and other balance operations (MT5 only; MT4 accounts answer `501 NOT_ON_THIS_TERMINAL`) |

Every time in these responses is UTC; `openTimestampUTC` / `closeTimestampUTC` are unix seconds.
