# Algo Trader Tools API

Version 1.0.0. Machine-readable OpenAPI document: https://algotradertools.com/openapi/v1.json

Connect MetaTrader 4 and 5 accounts, set up trade copiers and attach followers with their own rules, from your own code.

* Authenticate with `Authorization: Bearer att_…` (or `X-API-Key`). Create keys in the app under **API keys**.
* Errors are RFC 9457 problem details (`application/problem+json`) with a stable `code`.
* Lists are cursor-paginated: `limit` (1 to 100) and `cursor`; read `has_more` and `next_cursor`.
* Rate limits: 120 requests a minute per key, and 5 account connects, reconnects and disconnects a minute per user. Headers: `RateLimit-Policy`, `RateLimit`, and `Retry-After` on 429.

## Servers

- https://api.algotradertools.com — Production

## GET /v1/accounts

**List accounts**

Your connected MetaTrader accounts, newest first.

Scope: `accounts:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `limit` | query | integer | Items per page, 1 to 100 (default 50). |
| `cursor` | query | string | The `next_cursor` of the previous page. Leave it out for the first page. |

Responses:

- `200` A page of accounts.
- `400`
  - `invalid_cursor`: The cursor wasn't one this API returned.
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## POST /v1/accounts

**Connect an account**

Starts a hosted MetaTrader terminal for the account and logs it in. The answer comes at once with `status: connecting`; logging in takes two to three minutes. Poll the account every 15 seconds or slower until it's `connected` or `error`.

Each connect counts against the plan's accounts and the limit of 5 connects, reconnects and disconnects a minute. Retrying is safe: a second attempt fails with `already_connected`.

With `proxy_address`, the terminal reaches the broker through your proxy, so the broker sees its IP address. The proxy is tried first; one that doesn't work fails with `proxy_unreachable` and nothing is connected.

Scope: `accounts:write`.

Request body (`application/json`, required): ConnectAccount

| Field | Type | Description |
|---|---|---|
| `platform` (required) | "mt4" \| "mt5" | MetaTrader 4 or 5. |
| `server` (required) | string | The broker's server, exactly as MetaTrader shows it. |
| `login` (required) | integer | The MetaTrader account number. |
| `password` (required) | string | The master (trading) or investor (read-only) password. It's stored encrypted with the account's terminal and never shown again. |
| `credential_kind` (required) | "investor" \| "master" | Which password this is. Followers need `master`; a master can be either. |
| `nickname` | string | A name to tell it apart. |
| `proxy_type` | "socks5" \| "socks4" \| "http" \| null | The proxy's protocol: `socks5` when there's a proxy but no type. |
| `proxy_address` | string \| null | A proxy for the account's terminal to reach the broker through, as `host:port`, so the broker sees the proxy's IP address instead of ours. It's tried before the terminal starts. Left out, empty or null: a direct connection. |
| `proxy_username` | string | The proxy's username, if it needs one. Stored encrypted with the terminal, like the password, and never shown again. |
| `proxy_password` | string | The proxy's password, if it needs one. Like the username, stored encrypted and never shown again. |

Responses:

- `201` Connecting.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
  - `invalid_credentials`: The broker rejected the login and password.
  - `server_not_found`: No broker server has that name; copy it exactly as MetaTrader shows it.
  - `already_connected`: That MetaTrader account is already connected.
  - `proxy_unreachable`: Nothing could be reached through the account's proxy.
  - `connect_rejected`: The terminal host refused the account; see detail.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
  - `plan_limit_reached`: Every account the plan allows is connected, or there is no plan yet.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `502`
  - `terminal_unreachable`: The account's terminal couldn't be reached.
- `503`
  - `connect_unavailable`: New accounts can't be connected right now; retry later.
  - `terminal_unavailable`: The terminal is still starting up or busy; retry shortly.
- `504`
  - `terminal_timeout`: The terminal didn't answer in time.

## GET /v1/accounts/{account_id}

**Retrieve an account**

The account with its status, role and freshest figures.

Scope: `accounts:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `account_id` (required) | path | string | The account's ID. |

Responses:

- `200` The account.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## DELETE /v1/accounts/{account_id}

**Disconnect an account**

Stops the account's terminal and deletes the account with its stored history. An account a copier uses is refused with `account_in_use`: delete the copier or remove the follower first.

Scope: `accounts:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `account_id` (required) | path | string | The account's ID. |

Responses:

- `204` Done. There is no body.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `409`
  - `account_in_use`: The account is a master or follower in a copier; remove it there first.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `502`
  - `disconnect_failed`: The account's terminal couldn't be stopped; the account was kept.
- `503`
  - `disconnect_failed`: The account's terminal couldn't be stopped; the account was kept.
- `504`
  - `disconnect_failed`: The account's terminal couldn't be stopped; the account was kept.

## POST /v1/accounts/{account_id}/reconnect

**Reconnect a released account**

Starts a new terminal for an account whose terminal was released after the subscription lapsed (`released: true`), keeping the account and its history. Like connecting, it answers with `status: connecting`.

The account keeps its proxy unless `proxy_address` is sent. A proxy that needs a username and password needs them again, since they went with the old terminal.

Scope: `accounts:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `account_id` (required) | path | string | The account's ID. |

Request body (`application/json`, required): ReconnectAccount

| Field | Type | Description |
|---|---|---|
| `password` (required) | string | The master (trading) or investor (read-only) password. It's stored encrypted with the account's terminal and never shown again. |
| `credential_kind` (required) | "investor" \| "master" | Which password this is. Followers need `master`; a master can be either. |
| `proxy_type` | "socks5" \| "socks4" \| "http" \| null | Left out: the account's own, or `socks5` for a new proxy. |
| `proxy_address` | string \| null | Left out: the account keeps its proxy. A new `host:port` changes it; empty or null connects directly. |
| `proxy_username` | string | The proxy's username, if it needs one. It wasn't stored, so send it again. |
| `proxy_password` | string | The proxy's password, if it needs one. It wasn't stored either. |

Responses:

- `200` Connecting again.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
  - `invalid_credentials`: The broker rejected the login and password.
  - `server_not_found`: No broker server has that name; copy it exactly as MetaTrader shows it.
  - `already_connected`: That MetaTrader account is already connected.
  - `proxy_unreachable`: Nothing could be reached through the account's proxy.
  - `connect_rejected`: The terminal host refused the account; see detail.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
  - `plan_limit_reached`: Every account the plan allows is connected, or there is no plan yet.
- `404`
  - `not_found`: No such resource or endpoint.
- `409`
  - `account_not_released`: Only an account whose terminal was released can be reconnected.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `502`
  - `terminal_unreachable`: The account's terminal couldn't be reached.
- `503`
  - `connect_unavailable`: New accounts can't be connected right now; retry later.
  - `terminal_unavailable`: The terminal is still starting up or busy; retry shortly.
- `504`
  - `terminal_timeout`: The terminal didn't answer in time.

## GET /v1/copiers

**List copiers**

Your copiers with their followers, oldest first.

Scope: `copiers:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `limit` | query | integer | Items per page, 1 to 100 (default 50). |
| `cursor` | query | string | The `next_cursor` of the previous page. Leave it out for the first page. |

Responses:

- `200` A page of copiers.
- `400`
  - `invalid_cursor`: The cursor wasn't one this API returned.
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## POST /v1/copiers

**Create a copier**

A copier copies one master account to its followers. It starts without any: add them next. Masters must be hedging accounts (all of MT4, and MT5 hedging accounts). Retrying a failed request can create a second copier; list your copiers first.

Scope: `copiers:write`.

Request body (`application/json`, required): CreateCopier

| Field | Type | Description |
|---|---|---|
| `name` (required) | string | A name to tell it apart. |
| `master_id` (required) | string | One of your connected accounts that doesn't follow anything. Investor-password accounts can be masters. Several copiers can share a master. |
| `is_active` | boolean | Start paused with `false`. |

Responses:

- `201` Created.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `503`
  - `terminal_unavailable`: The terminal is still starting up or busy; retry shortly.

## GET /v1/copiers/{copier_id}

**Retrieve a copier**

The copier with its master, its followers and what the engine reports about each.

Scope: `copiers:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |

Responses:

- `200` The copier.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## PATCH /v1/copiers/{copier_id}

**Update a copier**

Rename, pause or resume a copier. Pausing stops new copies on every follower; copies already open stay and still follow the master's closes and changes. Resuming copies only what the master opens from then on.

Scope: `copiers:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |

Request body (`application/json`, required): UpdateCopier

| Field | Type | Description |
|---|---|---|
| `name` | string |  |
| `is_active` | boolean | Pause every follower with `false`. Copies already open stay and still follow the master's closes and changes. Resuming copies only what the master opens from then on. |

Responses:

- `200` The copier as it is now.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## DELETE /v1/copiers/{copier_id}

**Delete a copier**

Deletes the copier, its followers and their activity log. The accounts stay connected. With `close_positions=true` every copy is closed first.

Scope: `copiers:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |
| `close_positions` | query | boolean | `true` pauses copying and closes every copy at market before deleting; if a terminal refuses, nothing is deleted (`close_failed`). `false` leaves the copies open on the followers as ordinary positions. |

Responses:

- `204` Done. There is no body.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `502`
  - `close_failed`: The copies couldn't all be closed. Nothing was deleted; the followers handled up to the failure were paused.
- `503`
  - `close_failed`: The copies couldn't all be closed. Nothing was deleted; the followers handled up to the failure were paused.
- `504`
  - `close_failed`: The copies couldn't all be closed. Nothing was deleted; the followers handled up to the failure were paused.

## GET /v1/copiers/{copier_id}/followers

**List followers**

The copier's followers, oldest first.

Scope: `copiers:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |
| `limit` | query | integer | Items per page, 1 to 100 (default 50). |
| `cursor` | query | string | The `next_cursor` of the previous page. Leave it out for the first page. |

Responses:

- `200` A page of followers.
- `400`
  - `invalid_cursor`: The cursor wasn't one this API returned.
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## POST /v1/copiers/{copier_id}/followers

**Add a follower**

Attaches one of your accounts to the copier with its own rules; leave a rule out for its default. Copying starts at once (unless `is_active` is false), with what the master opens from then on. Retrying is safe: a second attempt fails with `already_following`.

Scope: `copiers:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |

Request body (`application/json`, required): AddFollower

| Field | Type | Description |
|---|---|---|
| `account_id` (required) | string | One of your connected accounts: connected with its master password, not this copier's master, not a master anywhere and not already copying this master. |
| `is_active` | boolean | Start paused with `false`. |
| `lot_mode` | "multiplier" \| "fixed" \| "balance_ratio" \| "equity_ratio" \| "risk_percent" | How each copy is sized. `multiplier`: the master's lots times `lot_value`. `fixed`: `lot_value` lots on every copy. `balance_ratio` / `equity_ratio`: the master's lots scaled by this account's balance or equity against the master's, times `lot_value`. `risk_percent`: sized so the master's stop loss risks `lot_value` percent of this account's equity; trades without a stop loss are skipped. |
| `lot_value` | number | The multiplier, the lot size or the risk percentage, by `lot_mode`. Above 0 and up to 1000; up to 100 for `risk_percent`. Up to four decimal places. |
| `adjust_contract_size` | boolean | Even out different contract sizes between the brokers (gold can be 100 oz a lot at one broker and 10 oz at another). Applies to `multiplier` and the ratio modes. |
| `below_minimum` | "minimum" \| "skip" | When a copy comes out below the broker's minimum lot: trade the minimum, or skip it. |
| `max_lot` | number \| null | The most lots one copy may have; null for no limit. |
| `reverse` | boolean | Sell when the master buys and the other way round; stop loss and take profit swap. |
| `copy_stops` | boolean | Copy stop loss and take profit, including later changes. |
| `max_slippage_pips` | number \| null | The slippage allowed per order; null uses the broker's default. |
| `max_delay_seconds` | integer | How late a master trade may still be copied, for example after a reconnect; older ones are skipped. |
| `copy_existing` | boolean | Also copy the positions and pending orders the master already has when the follower is attached. Nothing the master opens or places during a pause is ever copied. |
| `copy_pending` | boolean | Place the master's pending orders on the follower as pending orders, and follow their changes, rather than copying them once they fill. Needs both terminals on the current version. |
| `symbol_filter` | "all" \| "include" \| "exclude" | `all` copies every symbol, `include` only those in `symbols`, `exclude` all but them. |
| `symbols` | array of string | The master's symbol names for `symbol_filter`, which needs at least one unless it is `all`. `XAUUSD` also covers decorated names such as `XAUUSD.m`. |
| `sessions` | array of Session | When new trades are copied; empty means any time. Closes and changes to copies already open are always copied. |
| `timezone` | string | The clock `sessions` are read on: `broker` (the master broker's server time, as its MetaTrader charts show it) or a time zone name such as `Europe/London`. |
| `symbol_map` | object | Master symbol to follower symbol, for names that don't match by themselves. Matching tries this map, then the same name, the name without the broker's decorations (`EURUSD.m`, `US30.cash`), then known aliases (`XAUUSD` and `GOLD`); anything still ambiguous is skipped, never guessed. |

Responses:

- `201` Added.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `503`
  - `terminal_unavailable`: The terminal is still starting up or busy; retry shortly.

## GET /v1/copiers/{copier_id}/followers/{follower_id}

**Retrieve a follower**

The follower's rules and what the engine reports about it.

Scope: `copiers:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |
| `follower_id` (required) | path | string | The follower's ID (not its account's). |

Responses:

- `200` The follower.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## PATCH /v1/copiers/{copier_id}/followers/{follower_id}

**Update a follower**

Change rules, or pause and resume this follower. Changes apply to what's copied from then on.

Scope: `copiers:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |
| `follower_id` (required) | path | string | The follower's ID (not its account's). |

Request body (`application/json`, required): UpdateFollower

| Field | Type | Description |
|---|---|---|
| `is_active` | boolean | Pause with `false`. Resuming copies only what the master opens from then on, also with `copy_existing`. |
| `lot_mode` | "multiplier" \| "fixed" \| "balance_ratio" \| "equity_ratio" \| "risk_percent" | How each copy is sized. `multiplier`: the master's lots times `lot_value`. `fixed`: `lot_value` lots on every copy. `balance_ratio` / `equity_ratio`: the master's lots scaled by this account's balance or equity against the master's, times `lot_value`. `risk_percent`: sized so the master's stop loss risks `lot_value` percent of this account's equity; trades without a stop loss are skipped. |
| `lot_value` | number | The multiplier, the lot size or the risk percentage, by `lot_mode`. Above 0 and up to 1000; up to 100 for `risk_percent`. Up to four decimal places. |
| `adjust_contract_size` | boolean | Even out different contract sizes between the brokers (gold can be 100 oz a lot at one broker and 10 oz at another). Applies to `multiplier` and the ratio modes. |
| `below_minimum` | "minimum" \| "skip" | When a copy comes out below the broker's minimum lot: trade the minimum, or skip it. |
| `max_lot` | number \| null | The most lots one copy may have; null for no limit. |
| `reverse` | boolean | Sell when the master buys and the other way round; stop loss and take profit swap. |
| `copy_stops` | boolean | Copy stop loss and take profit, including later changes. |
| `max_slippage_pips` | number \| null | The slippage allowed per order; null uses the broker's default. |
| `max_delay_seconds` | integer | How late a master trade may still be copied, for example after a reconnect; older ones are skipped. |
| `copy_existing` | boolean | Also copy the positions and pending orders the master already has when the follower is attached. Nothing the master opens or places during a pause is ever copied. |
| `copy_pending` | boolean | Place the master's pending orders on the follower as pending orders, and follow their changes, rather than copying them once they fill. Needs both terminals on the current version. |
| `symbol_filter` | "all" \| "include" \| "exclude" | `all` copies every symbol, `include` only those in `symbols`, `exclude` all but them. |
| `symbols` | array of string | The master's symbol names for `symbol_filter`, which needs at least one unless it is `all`. `XAUUSD` also covers decorated names such as `XAUUSD.m`. |
| `sessions` | array of Session | When new trades are copied; empty means any time. Closes and changes to copies already open are always copied. |
| `timezone` | string | The clock `sessions` are read on: `broker` (the master broker's server time, as its MetaTrader charts show it) or a time zone name such as `Europe/London`. |
| `symbol_map` | object | Master symbol to follower symbol, for names that don't match by themselves. Matching tries this map, then the same name, the name without the broker's decorations (`EURUSD.m`, `US30.cash`), then known aliases (`XAUUSD` and `GOLD`); anything still ambiguous is skipped, never guessed. |

Responses:

- `200` The follower as it is now.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## DELETE /v1/copiers/{copier_id}/followers/{follower_id}

**Remove a follower**

Detaches the account from the copier and deletes its activity log. The account stays connected. With `close_positions=true` its copies are closed first.

Scope: `copiers:write`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |
| `follower_id` (required) | path | string | The follower's ID (not its account's). |
| `close_positions` | query | boolean | `true` pauses copying and closes every copy at market before deleting; if a terminal refuses, nothing is deleted (`close_failed`). `false` leaves the copies open on the followers as ordinary positions. |

Responses:

- `204` Done. There is no body.
- `400`
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.
- `502`
  - `close_failed`: The copies couldn't all be closed. Nothing was deleted; the followers handled up to the failure were paused.
- `503`
  - `close_failed`: The copies couldn't all be closed. Nothing was deleted; the followers handled up to the failure were paused.
- `504`
  - `close_failed`: The copies couldn't all be closed. Nothing was deleted; the followers handled up to the failure were paused.

## GET /v1/copiers/{copier_id}/events

**List copy events**

What the copier engine did, or decided not to do, on the copier's followers, newest first. Events are kept for 90 days.

Scope: `copiers:read`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `copier_id` (required) | path | string | The copier's ID. |
| `follower_id` | query | string | Only this follower's events. |
| `limit` | query | integer | Items per page, 1 to 100 (default 50). |
| `cursor` | query | string | The `next_cursor` of the previous page. Leave it out for the first page. |

Responses:

- `200` A page of events.
- `400`
  - `invalid_cursor`: The cursor wasn't one this API returned.
  - `validation_failed`: A field is missing or invalid; see errors.
- `401`
  - `authentication_required`: No API key was sent.
  - `invalid_api_key`: The API key is unknown or malformed.
  - `api_key_revoked`: The API key was revoked.
- `403`
  - `insufficient_scope`: The API key lacks the scope this endpoint needs.
- `404`
  - `not_found`: No such resource or endpoint.
- `429`
  - `rate_limited`: Wait for the number of seconds in Retry-After.

## Objects

### Account

A MetaTrader 4 or 5 account, connected through its own hosted terminal.

| Field | Type | Description |
|---|---|---|
| `id` (required) | string | The account's ID. |
| `object` (required) | "account" | What this object is. |
| `name` (required) | string | Its nickname, or the platform and login. |
| `nickname` (required) | string | Empty when none was given. |
| `platform` (required) | "mt4" \| "mt5" |  |
| `server` (required) | string | The broker's server. |
| `login` (required) | integer | The MetaTrader account number. |
| `credential_kind` (required) | "investor" \| "master" | Which password it was connected with: `master` can trade, `investor` only reads. |
| `can_trade` (required) | boolean | Whether it can place trades, so it can be a follower. Any account can be a master. |
| `status` (required) | "connecting" \| "connected" \| "error" \| "disconnected" | `connecting` while its terminal starts and logs in (two to three minutes), `connected` once it's ready, `error` when the login failed (see `error`), `disconnected` when its terminal lost the broker or was released. |
| `error` (required) | string \| null | Why it's in `error` or `disconnected`, if known. |
| `released` (required) | boolean | Its terminal was released after the subscription lapsed. The account and its history are kept: reconnect it to resume. |
| `proxy_type` (required) | "socks5" \| "socks4" \| "http" \| null | The proxy's protocol; null without one. |
| `proxy_address` (required) | string \| null | The proxy its terminal reaches the broker through, as `host:port`; null for a direct connection. |
| `role` (required) | "master" \| "follower" \| null | Whether a copier uses it, and how. An account is never both. |
| `figures` (required) | AccountFigures or null |  |
| `created_at` (required) | string |  |

### AccountFigures

The account's freshest figures.

| Field | Type | Description |
|---|---|---|
| `balance` (required) | number |  |
| `equity` (required) | number |  |
| `profit` (required) | number | Floating profit or loss of the open positions. |
| `margin` (required) | number |  |
| `free_margin` (required) | number |  |
| `margin_level` (required) | number \| null | In percent; null while no margin is used. |
| `currency` (required) | string | As the broker reports it: usually ISO 4217, but also codes such as `USC` (US cents) or `USDT`. |
| `open_positions` (required) | integer |  |
| `as_of` (required) | string | When the terminal reported these figures. |
| `source` (required) | "live" \| "snapshot" | `live`: streamed by the copier engine, at most 30 seconds old. `snapshot`: the last stored snapshot, taken every few minutes. |

### AccountRef

An account as other objects show it. Get the account for the rest.

| Field | Type | Description |
|---|---|---|
| `id` (required) | string | The account's ID. |
| `name` (required) | string | Its nickname, or the platform and login. |
| `platform` (required) | "mt4" \| "mt5" |  |
| `server` (required) | string | The broker's server. |
| `login` (required) | integer | The MetaTrader account number. |
| `status` (required) | "connecting" \| "connected" \| "error" \| "disconnected" | Whether its terminal is connected. |

### AccountList

| Field | Type | Description |
|---|---|---|
| `object` (required) | "list" | Always `list`. |
| `data` (required) | array of Account | Newest first. |
| `has_more` (required) | boolean | Whether there's another page: pass `next_cursor` as `cursor`. |
| `next_cursor` (required) | string \| null | The cursor of the next page; null on the last one. |

### ConnectAccount

| Field | Type | Description |
|---|---|---|
| `platform` (required) | "mt4" \| "mt5" | MetaTrader 4 or 5. |
| `server` (required) | string | The broker's server, exactly as MetaTrader shows it. |
| `login` (required) | integer | The MetaTrader account number. |
| `password` (required) | string | The master (trading) or investor (read-only) password. It's stored encrypted with the account's terminal and never shown again. |
| `credential_kind` (required) | "investor" \| "master" | Which password this is. Followers need `master`; a master can be either. |
| `nickname` | string | A name to tell it apart. |
| `proxy_type` | "socks5" \| "socks4" \| "http" \| null | The proxy's protocol: `socks5` when there's a proxy but no type. |
| `proxy_address` | string \| null | A proxy for the account's terminal to reach the broker through, as `host:port`, so the broker sees the proxy's IP address instead of ours. It's tried before the terminal starts. Left out, empty or null: a direct connection. |
| `proxy_username` | string | The proxy's username, if it needs one. Stored encrypted with the terminal, like the password, and never shown again. |
| `proxy_password` | string | The proxy's password, if it needs one. Like the username, stored encrypted and never shown again. |

### ReconnectAccount

| Field | Type | Description |
|---|---|---|
| `password` (required) | string | The master (trading) or investor (read-only) password. It's stored encrypted with the account's terminal and never shown again. |
| `credential_kind` (required) | "investor" \| "master" | Which password this is. Followers need `master`; a master can be either. |
| `proxy_type` | "socks5" \| "socks4" \| "http" \| null | Left out: the account's own, or `socks5` for a new proxy. |
| `proxy_address` | string \| null | Left out: the account keeps its proxy. A new `host:port` changes it; empty or null connects directly. |
| `proxy_username` | string | The proxy's username, if it needs one. It wasn't stored, so send it again. |
| `proxy_password` | string | The proxy's password, if it needs one. It wasn't stored either. |

### Copier

One master account and the followers that copy it.

| Field | Type | Description |
|---|---|---|
| `id` (required) | string | The copier's ID. |
| `object` (required) | "copier" | What this object is. |
| `name` (required) | string |  |
| `is_active` (required) | boolean | False while the copier is paused. |
| `master_id` (required) | string | The account that is copied. |
| `master` (required) | AccountRef | An account as other objects show it. Get the account for the rest. |
| `followers` (required) | array of Follower | Oldest first. |
| `open_copies` (required) | integer | Copies open across all followers now. |
| `created_at` (required) | string |  |
| `updated_at` (required) | string |  |

### CopierList

| Field | Type | Description |
|---|---|---|
| `object` (required) | "list" | Always `list`. |
| `data` (required) | array of Copier | Oldest first. |
| `has_more` (required) | boolean | Whether there's another page: pass `next_cursor` as `cursor`. |
| `next_cursor` (required) | string \| null | The cursor of the next page; null on the last one. |

### CreateCopier

| Field | Type | Description |
|---|---|---|
| `name` (required) | string | A name to tell it apart. |
| `master_id` (required) | string | One of your connected accounts that doesn't follow anything. Investor-password accounts can be masters. Several copiers can share a master. |
| `is_active` | boolean | Start paused with `false`. |

### UpdateCopier

Only the fields you send change. The master can't change.

| Field | Type | Description |
|---|---|---|
| `name` | string |  |
| `is_active` | boolean | Pause every follower with `false`. Copies already open stay and still follow the master's closes and changes. Resuming copies only what the master opens from then on. |

### Follower

An account that copies a copier's master, with its own rules.

| Field | Type | Description |
|---|---|---|
| `id` (required) | string | The follower's ID. |
| `object` (required) | "follower" | What this object is. |
| `copier_id` (required) | string | Its copier. |
| `account_id` (required) | string | The account that copies. |
| `account` (required) | AccountRef | An account as other objects show it. Get the account for the rest. |
| `is_active` (required) | boolean | False while this follower is paused. |
| `magic` (required) | integer | The magic number on every order placed for this follower. Each copy's comment is `att:` and the master's ticket in base 36. |
| `lot_mode` (required) | "multiplier" \| "fixed" \| "balance_ratio" \| "equity_ratio" \| "risk_percent" | How each copy is sized. `multiplier`: the master's lots times `lot_value`. `fixed`: `lot_value` lots on every copy. `balance_ratio` / `equity_ratio`: the master's lots scaled by this account's balance or equity against the master's, times `lot_value`. `risk_percent`: sized so the master's stop loss risks `lot_value` percent of this account's equity; trades without a stop loss are skipped. |
| `lot_value` (required) | number | The multiplier, the lot size or the risk percentage, by `lot_mode`. Above 0 and up to 1000; up to 100 for `risk_percent`. Up to four decimal places. |
| `adjust_contract_size` (required) | boolean | Even out different contract sizes between the brokers (gold can be 100 oz a lot at one broker and 10 oz at another). Applies to `multiplier` and the ratio modes. |
| `below_minimum` (required) | "minimum" \| "skip" | When a copy comes out below the broker's minimum lot: trade the minimum, or skip it. |
| `max_lot` (required) | number \| null | The most lots one copy may have; null for no limit. |
| `reverse` (required) | boolean | Sell when the master buys and the other way round; stop loss and take profit swap. |
| `copy_stops` (required) | boolean | Copy stop loss and take profit, including later changes. |
| `max_slippage_pips` (required) | number \| null | The slippage allowed per order; null uses the broker's default. |
| `max_delay_seconds` (required) | integer | How late a master trade may still be copied, for example after a reconnect; older ones are skipped. |
| `copy_existing` (required) | boolean | Also copy the positions and pending orders the master already has when the follower is attached. Nothing the master opens or places during a pause is ever copied. |
| `copy_pending` (required) | boolean | Place the master's pending orders on the follower as pending orders, and follow their changes, rather than copying them once they fill. Needs both terminals on the current version. |
| `symbol_filter` (required) | "all" \| "include" \| "exclude" | `all` copies every symbol, `include` only those in `symbols`, `exclude` all but them. |
| `symbols` (required) | array of string | The master's symbol names for `symbol_filter`, which needs at least one unless it is `all`. `XAUUSD` also covers decorated names such as `XAUUSD.m`. |
| `sessions` (required) | array of Session | When new trades are copied; empty means any time. Closes and changes to copies already open are always copied. |
| `timezone` (required) | string | The clock `sessions` are read on: `broker` (the master broker's server time, as its MetaTrader charts show it) or a time zone name such as `Europe/London`. |
| `symbol_map` (required) | object | Master symbol to follower symbol, for names that don't match by themselves. Matching tries this map, then the same name, the name without the broker's decorations (`EURUSD.m`, `US30.cash`), then known aliases (`XAUUSD` and `GOLD`); anything still ambiguous is skipped, never guessed. |
| `status` (required) | FollowerStatus or null |  |
| `created_at` (required) | string |  |
| `updated_at` (required) | string |  |

### FollowerStatus

What the copier engine reports about a follower, as of `heartbeat_at`.

| Field | Type | Description |
|---|---|---|
| `state` (required) | "starting" \| "copying" \| "paused" \| "error" | `starting` while the engine connects, `copying`, `paused` (the follower or its copier is paused, or Equity Guard holds copying back), or `error` (see `message`). |
| `message` (required) | string | Why it needs attention, or `Paused by Equity Guard.` while Equity Guard holds copying back; empty otherwise. |
| `master_connected` (required) | boolean | The engine's stream from the master's terminal is up. |
| `follower_connected` (required) | boolean | The engine's stream from this account's terminal is up. |
| `open_copies` (required) | integer | Copies open on this account now. |
| `last_event_at` (required) | string \| null | When the engine last did something here. |
| `balance` (required) | number \| null | This account's balance as the engine last saw it. |
| `equity` (required) | number \| null | This account's equity as the engine last saw it. |
| `currency` (required) | string |  |
| `heartbeat_at` (required) | string | When the engine last reported; at least every 15 seconds. |
| `engine_online` (required) | boolean | False when the engine hasn't reported for 60 seconds. |

### FollowerList

| Field | Type | Description |
|---|---|---|
| `object` (required) | "list" | Always `list`. |
| `data` (required) | array of Follower | Oldest first. |
| `has_more` (required) | boolean | Whether there's another page: pass `next_cursor` as `cursor`. |
| `next_cursor` (required) | string \| null | The cursor of the next page; null on the last one. |

### AddFollower

| Field | Type | Description |
|---|---|---|
| `account_id` (required) | string | One of your connected accounts: connected with its master password, not this copier's master, not a master anywhere and not already copying this master. |
| `is_active` | boolean | Start paused with `false`. |
| `lot_mode` | "multiplier" \| "fixed" \| "balance_ratio" \| "equity_ratio" \| "risk_percent" | How each copy is sized. `multiplier`: the master's lots times `lot_value`. `fixed`: `lot_value` lots on every copy. `balance_ratio` / `equity_ratio`: the master's lots scaled by this account's balance or equity against the master's, times `lot_value`. `risk_percent`: sized so the master's stop loss risks `lot_value` percent of this account's equity; trades without a stop loss are skipped. |
| `lot_value` | number | The multiplier, the lot size or the risk percentage, by `lot_mode`. Above 0 and up to 1000; up to 100 for `risk_percent`. Up to four decimal places. |
| `adjust_contract_size` | boolean | Even out different contract sizes between the brokers (gold can be 100 oz a lot at one broker and 10 oz at another). Applies to `multiplier` and the ratio modes. |
| `below_minimum` | "minimum" \| "skip" | When a copy comes out below the broker's minimum lot: trade the minimum, or skip it. |
| `max_lot` | number \| null | The most lots one copy may have; null for no limit. |
| `reverse` | boolean | Sell when the master buys and the other way round; stop loss and take profit swap. |
| `copy_stops` | boolean | Copy stop loss and take profit, including later changes. |
| `max_slippage_pips` | number \| null | The slippage allowed per order; null uses the broker's default. |
| `max_delay_seconds` | integer | How late a master trade may still be copied, for example after a reconnect; older ones are skipped. |
| `copy_existing` | boolean | Also copy the positions and pending orders the master already has when the follower is attached. Nothing the master opens or places during a pause is ever copied. |
| `copy_pending` | boolean | Place the master's pending orders on the follower as pending orders, and follow their changes, rather than copying them once they fill. Needs both terminals on the current version. |
| `symbol_filter` | "all" \| "include" \| "exclude" | `all` copies every symbol, `include` only those in `symbols`, `exclude` all but them. |
| `symbols` | array of string | The master's symbol names for `symbol_filter`, which needs at least one unless it is `all`. `XAUUSD` also covers decorated names such as `XAUUSD.m`. |
| `sessions` | array of Session | When new trades are copied; empty means any time. Closes and changes to copies already open are always copied. |
| `timezone` | string | The clock `sessions` are read on: `broker` (the master broker's server time, as its MetaTrader charts show it) or a time zone name such as `Europe/London`. |
| `symbol_map` | object | Master symbol to follower symbol, for names that don't match by themselves. Matching tries this map, then the same name, the name without the broker's decorations (`EURUSD.m`, `US30.cash`), then known aliases (`XAUUSD` and `GOLD`); anything still ambiguous is skipped, never guessed. |

### UpdateFollower

Only the fields you send change. The account can't change.

| Field | Type | Description |
|---|---|---|
| `is_active` | boolean | Pause with `false`. Resuming copies only what the master opens from then on, also with `copy_existing`. |
| `lot_mode` | "multiplier" \| "fixed" \| "balance_ratio" \| "equity_ratio" \| "risk_percent" | How each copy is sized. `multiplier`: the master's lots times `lot_value`. `fixed`: `lot_value` lots on every copy. `balance_ratio` / `equity_ratio`: the master's lots scaled by this account's balance or equity against the master's, times `lot_value`. `risk_percent`: sized so the master's stop loss risks `lot_value` percent of this account's equity; trades without a stop loss are skipped. |
| `lot_value` | number | The multiplier, the lot size or the risk percentage, by `lot_mode`. Above 0 and up to 1000; up to 100 for `risk_percent`. Up to four decimal places. |
| `adjust_contract_size` | boolean | Even out different contract sizes between the brokers (gold can be 100 oz a lot at one broker and 10 oz at another). Applies to `multiplier` and the ratio modes. |
| `below_minimum` | "minimum" \| "skip" | When a copy comes out below the broker's minimum lot: trade the minimum, or skip it. |
| `max_lot` | number \| null | The most lots one copy may have; null for no limit. |
| `reverse` | boolean | Sell when the master buys and the other way round; stop loss and take profit swap. |
| `copy_stops` | boolean | Copy stop loss and take profit, including later changes. |
| `max_slippage_pips` | number \| null | The slippage allowed per order; null uses the broker's default. |
| `max_delay_seconds` | integer | How late a master trade may still be copied, for example after a reconnect; older ones are skipped. |
| `copy_existing` | boolean | Also copy the positions and pending orders the master already has when the follower is attached. Nothing the master opens or places during a pause is ever copied. |
| `copy_pending` | boolean | Place the master's pending orders on the follower as pending orders, and follow their changes, rather than copying them once they fill. Needs both terminals on the current version. |
| `symbol_filter` | "all" \| "include" \| "exclude" | `all` copies every symbol, `include` only those in `symbols`, `exclude` all but them. |
| `symbols` | array of string | The master's symbol names for `symbol_filter`, which needs at least one unless it is `all`. `XAUUSD` also covers decorated names such as `XAUUSD.m`. |
| `sessions` | array of Session | When new trades are copied; empty means any time. Closes and changes to copies already open are always copied. |
| `timezone` | string | The clock `sessions` are read on: `broker` (the master broker's server time, as its MetaTrader charts show it) or a time zone name such as `Europe/London`. |
| `symbol_map` | object | Master symbol to follower symbol, for names that don't match by themselves. Matching tries this map, then the same name, the name without the broker's decorations (`EURUSD.m`, `US30.cash`), then known aliases (`XAUUSD` and `GOLD`); anything still ambiguous is skipped, never guessed. |

### Session

A window in which new trades are copied.

| Field | Type | Description |
|---|---|---|
| `days` (required) | array of integer | 0 is Monday, 6 Sunday. |
| `start` (required) | string | `HH:MM`, 24-hour. |
| `end` (required) | string | `HH:MM`. An end before its start runs past midnight. |

### CopyEvent

One thing the copier engine did, or decided not to do, on a follower.

| Field | Type | Description |
|---|---|---|
| `id` (required) | integer |  |
| `object` (required) | "copy_event" | What this object is. |
| `follower_id` (required) | string | The follower it happened on. |
| `account_id` (required) | string | That follower's account. |
| `master_ticket` (required) | integer | The master position it's about. |
| `action` (required) | "open" \| "close" \| "partial_close" \| "modify" \| "skip" \| "adopt" \| "place" \| "cancel" | `open`, `close`, `partial_close`, `modify` (stop loss or take profit, or a pending order moved), `skip` (decided not to copy; see `message`), `adopt` (took over a copy it found on the follower after a restart), `place` (placed a pending order for the master's) or `cancel` (cancelled one). |
| `ok` (required) | boolean | Whether it worked. |
| `message` (required) | string |  |
| `detail` (required) | object | The request and the terminal's answer. Keys vary by action, for example `symbol`, `side`, `volume`, `price`, `ticket`, `stopLoss`, `takeProfit`, `retcode` and `outcome`. |
| `received_at` (required) | string | When the engine heard about the master's change. |
| `engine_ms` (required) | number \| null | The engine's own time to the order going out. |
| `execution_ms` (required) | number \| null | The follower broker's time to answer the order. |
| `total_ms` (required) | number \| null | Both together. |

### CopyEventList

| Field | Type | Description |
|---|---|---|
| `object` (required) | "list" | Always `list`. |
| `data` (required) | array of CopyEvent | Newest first. |
| `has_more` (required) | boolean | Whether there's another page: pass `next_cursor` as `cursor`. |
| `next_cursor` (required) | string \| null | The cursor of the next page; null on the last one. |

### Problem

RFC 9457 problem details, served as `application/problem+json`.

| Field | Type | Description |
|---|---|---|
| `type` (required) | string | Where this code is documented. |
| `title` (required) | string | A short summary of the code. |
| `status` (required) | integer | The HTTP status, repeated. |
| `code` (required) | "validation_failed" \| "invalid_json" \| "invalid_cursor" \| "invalid_credentials" \| "server_not_found" \| "already_connected" \| "proxy_unreachable" \| "connect_rejected" \| "authentication_required" \| "invalid_api_key" \| "api_key_revoked" \| "insufficient_scope" \| "plan_limit_reached" \| "not_found" \| "method_not_allowed" \| "not_acceptable" \| "account_in_use" \| "account_not_released" \| "unsupported_media_type" \| "rate_limited" \| "internal_error" \| "close_failed" \| "disconnect_failed" \| "terminal_unreachable" \| "terminal_unavailable" \| "connect_unavailable" \| "terminal_timeout" | What went wrong. Branch on this, not on `title` or `detail`. |
| `detail` (required) | string | What went wrong in this case. |
| `errors` | array of object | Only for `validation_failed`: one entry per problem. |
| `request_id` (required) | string |  |
| `doc_url` (required) | string | Same as `type`. |
