# Accounts

URL: https://docs.fxapis.com/accounts

> Connecting MT5 accounts, keeping them online, and what each state means.



An account is an MT5 login you have connected to fxapis. Its **state** tells
you whether it can trade right now; its **mode** decides when it is online.

## Modes [#modes]

Set when you connect the account, and changed any time with
[`POST /mode`](#post-v1accountsidmode).

| Mode             | Online                                                                                                                                                                            | First order               | Best for                                                                             |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------ |
| `always_on`      | Once brought online, it stays online — never taken offline for being idle, and brought back by itself within minutes if its terminal stops (a refused login or 2FA waits for you) | Immediate                 | Accounts that must act the instant something happens — copied signals, a market open |
| `warm_on_demand` | When you trade, then offline by itself when idle                                                                                                                                  | After a short connect     | Most integrations, and signal services                                               |
| `cold`           | Only when you ask                                                                                                                                                                 | After you bring it online | Accounts you trade on a schedule                                                     |

`warm_on_demand` is the sensible default (the API's default is `cold`, so set
it). The first order after a quiet spell waits while the account connects —
about 10 seconds in our measurement — and every order
after that is immediate. To avoid even that wait, bring accounts online ahead of
time with [`POST /v1/accounts/prepare`](/signals).

A mode change applies the next time the account comes online. To apply
`always_on` straight away, bring the account online after switching.

## States [#states]

Your code can act on four groups. The exact values are what the API returns.

**Online — can trade**

| State       | Meaning                                     |
| ----------- | ------------------------------------------- |
| `ready`     | Online and accepting orders.                |
| `executing` | Online; an order is being placed right now. |

**Connecting — wait**

| State                                    | Meaning                                                                                                                  |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `provisioning`, `starting`, `connecting` | Coming online and logging in at the broker.                                                                              |
| `synchronizing`                          | Logged in; prices are still arriving. Orders are refused until `ready`, so none goes out against incomplete prices.      |
| `reconnecting`                           | The broker connection dropped and is being restored.                                                                     |
| `degraded`                               | Online but unhealthy — slow or stale prices. Usually recovers by itself; [restart](#post-v1accountsidrestart) it if not. |

**Offline — not connected to the broker right now**

| State     | Meaning                                                                                                                                |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | Connected, never brought online yet.                                                                                                   |
| `standby` | Offline and known to work. A `warm_on_demand` account comes online on its next order; any account comes online when you ask (`/warm`). |
| `cooling` | Going offline.                                                                                                                         |
| `offline` | Disconnected (the stored password has been erased), or its terminal stopped answering (the password is kept — bring it online again).  |

**Needs attention — a person must fix something**

| State                 | Meaning                                                                                            | What to do                                                                                                                                                                 |
| --------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_credentials` | The broker rejected the login — a wrong password, *or a login that does not exist on that server*. | Check `problem.otherServers` and try the likely one with `POST /v1/accounts/{id}/server`; or send the corrected **trading** password to `POST /v1/accounts/{id}/password`. |
| `trading_disabled`    | The broker has disabled trading for this login.                                                    | Ask the broker, or the account's owner, to enable trading. (An investor password instead reaches `ready` and its orders are rejected.)                                     |
| `needs_2fa`           | The account requires a second factor.                                                              | Turn two-factor off for this account, or use another.                                                                                                                      |
| `needs_certificate`   | The broker requires a client certificate.                                                          | Contact us.                                                                                                                                                                |
| `error`               | Something temporary went wrong.                                                                    | Retryable — bring it online again.                                                                                                                                         |

The first four will not improve with retries, and repeated failed logins are how
a broker locks an account. So we stop trying: bringing one of these online
returns **409** until the cause is fixed.

### What is wrong, in words: `problem` [#what-is-wrong-in-words-problem]

Every account carries `problem`: `null` while it is fine or on its way, and
otherwise what is wrong and how to fix it — the same thing the console shows.

```json
"problem": {
  "code": "LOGIN_REFUSED",
  "title": "The broker refused the login",
  "explanation": "JustMarkets-Demo3 did not accept login 2002126135 with this password. Either the password is not the account's trading password …, or this login does not exist on JustMarkets-Demo3 …",
  "detail": "the broker refused the login on mt5ldn5.srip.net:8750: -6 Terminal: Authorization failed",
  "otherServers": ["JustMarkets-Demo", "JustMarkets-Demo2", "JustMarkets-Live"],
  "actions": [
    { "kind": "switch_server", "server": "JustMarkets-Demo" },
    { "kind": "switch_server", "server": "JustMarkets-Demo2" },
    { "kind": "replace_credentials" }
  ]
}
```

A broker answers "authorization failed" both for a wrong password and for a
login that lives on a different server — and brokers run several servers with
near-identical names. `otherServers` lists the broker's other servers, most
likely first: servers accounts here have logged in to, then the broker's main
server, then the closest names. Show them to your user, or try the first
with `POST /v1/accounts/{id}/server` — it reuses the stored password.

When a terminal cannot find the account's server — a name that is not an MT5
server, such as a broker's label for an account type — an account that has
tried and never logged in carries `"code": "SERVER_NOT_FOUND"`, with the
broker's real servers in `otherServers` and a `switch_server` action for each.
`POST /v1/accounts/{id}/server` with the right one keeps the stored password.
Write the server exactly as MetaTrader's File → Login dialog or the broker's
welcome email shows it.

When accounts cannot come online because of a problem on our side, accounts
on their way online (`created`, `standby`, `starting`, `offline`, `error`)
carry `"code": "PLATFORM_ISSUE"` with no `actions`: nothing about the account
needs changing, and retrying will not help. A failed
`POST /v1/accounts/{id}/warm` says the same in its message. It clears by
itself once it is fixed; [status.fxapis.com](https://status.fxapis.com) has
updates. A refused login, 2FA, a certificate or trading disabled keep their own
codes: those come from the broker.

### Alerts in the console [#alerts-in-the-console]

When an account enters one of the states above, the console opens an alert:
the bell at the top of every console page counts the ones you have not read,
**Notifications** lists them with what the broker said and the one fix (on the
account's page, where the problem card has it), and the account's page shows
the same card. An alert closes by itself once the account leaves that state;
resolved ones stay listed for a week. An `error` is shown only if it lasts two
minutes — most clear in seconds. Everyone in the workspace sees them; only
owners and admins can make the fix. In-app only: nothing is emailed, and there
is no API for them — integrations use the `account.state_changed` webhook or
`problem`.

### `POST /v1/accounts/{id}/server` [#post-v1accountsidserver]

Moves the account to another server, **keeping the stored password** — for a
login added under the wrong server name, so nobody types the password again.
With `connect: true` (the default) it is brought online at once and the answer
is back in seconds; poll the account or its events. **409** `ACCOUNT_EXISTS`
when that login is already connected on that server; **409** `NO_PASSWORD`
when no password is stored (use `/password`).

```bash
curl -X POST https://api.fxapis.com/v1/accounts/$ID/server \
  -H "Authorization: Bearer $FXAPIS_KEY" -H "content-type: application/json" \
  -d '{"server":"JustMarkets-Demo"}'
```

### `GET /v1/brokers/servers` [#get-v1brokersservers]

A server picker for your own connect form, so the people connecting choose
their server instead of typing it exactly. Search by what they type — a
broker's name (`vantage`), the start of a server (`JustMarkets-De`), with or
without dashes and spaces — from two characters, debounced:

```bash
curl "https://api.fxapis.com/v1/brokers/servers?q=justmarkets" -H "Authorization: Bearer $FXAPIS_KEY"
```

```json
{ "data": [
  { "name": "JustMarkets-Demo", "company": "Just Global Markets Ltd.", "kind": "demo", "proven": true },
  { "name": "JustMarkets-Live", "company": "Just Global Markets Ltd.", "kind": "live", "proven": false }
] }
```

Servers accounts on fxapis have logged in to come first (`proven`), then names
starting with what was typed, then the kind asked for, then the broker's main
server. A broker nobody here has used yet is found too: with fewer than five matches
and at least three characters typed, the broker search is asked once (that
answer can take a second or two), and a search that found nothing is not
repeated for 24 hours. `limit` sets how many come back (1–50, default 20). **Always
let people use exactly what they typed** — a server that is not listed can
still be connected. Without `q`, the servers used most here.

### `GET /v1/accounts/{id}/events` [#get-v1accountsidevents]

The account's history in words, newest first — added, logging in, refused by
the broker, online, moved to another server, taken offline — each with a
`tone` (`success`, `info`, `warning`, `danger`), a `title`, an optional
`detail`, and who did it (`key`, `member`, `staff`, `system`). The console's
activity list is this endpoint. `limit` up to 200 (default 50).

## Endpoints [#endpoints]

Full request and response shapes are in the [API reference](/api-reference).
What follows is what the reference cannot tell you.

### `POST /v1/accounts` [#post-v1accounts]

Connects the account and stores its password, encrypted. It does not log in yet.

The same `login` and `server` can be connected only once per workspace — a
second copy would mean two places able to trade the same money — so a duplicate
is refused with **409** while it is still connected. A login you disconnected
earlier is reconnected instead: see [`/disconnect`](#post-v1accountsiddisconnect).

If the password cannot be stored safely, the account is **not** created and you
get **503**, rather than an account that can never come online.

### `POST /v1/accounts/{id}/warm` [#post-v1accountsidwarm]

Brings the account online. Returns **202** straight away with a `pollUrl`; poll
it until the state is `ready`.

Safe to call repeatedly: for an account that is already online it returns
`alreadyRunning: true` and does nothing else.

### `POST /v1/accounts/{id}/cool` [#post-v1accountsidcool]

Takes the account offline. It stays connected, in `standby`, and comes back on
the next order or when you ask. It works from any state: an account in
`created` or a needs-attention state that still has a terminal running has it
stopped, and keeps the state that says what it still needs.

**Refused with 409 while an order is being placed** — stopping mid-order would
leave its result uncertain. Wait a moment and try again.

Open positions are not affected. Stop losses and take profits live at the
broker and keep working while the account is offline.

### `POST /v1/accounts/{id}/restart` [#post-v1accountsidrestart]

Takes the account offline and brings it back — for an account that is online
but misbehaving (`degraded`, stale prices). Same **409** while an order is in
flight.

### `POST /v1/accounts/{id}/disconnect` [#post-v1accountsiddisconnect]

Takes the account offline **and erases the stored password.** The account and
its history remain, in `offline`. To reconnect, send the same login and server
to `POST /v1/accounts` with the password: the same account comes back, with
**200**.

Use this when a customer leaves. A password you no longer need is one we should
no longer hold.

### `DELETE /v1/accounts/{id}` [#delete-v1accountsid]

Deletes the account for good: its terminal stops, its password is erased, and
the record goes with its orders, deals and positions. **It cannot be undone** —
disconnect instead if you want the history to stay readable. The response says
how much went with it.

Refused with **409** while an order is in flight (`ACCOUNT_EXECUTING`), while a
multi-account order that includes it has not finished (`ACCOUNT_IN_WAVE`), and
when our last look found open positions (`ACCOUNT_HAS_POSITIONS`). Open
positions stay open at the broker with their stops, but once the account is
deleted they cannot be shown or closed here: close them first, or send
`?force=true` to delete anyway.

Alert hooks stop sending to the account, and a hook left with no accounts is
switched off. This month's usage is unaffected. The same login and server can
be connected again afterwards, as a new account with a new id.

### `POST /v1/accounts/{id}/password` [#post-v1accountsidpassword]

Replaces the stored password — after it changed at the broker, or when the
account is in `invalid_credentials`. Add `server` to correct the server name.
Any running terminal is stopped first, and waited for (**409** while an order
is in flight); the account lands in `created`. Then bring it online with
`POST /v1/accounts/{id}/warm` and poll the status: that is a fresh login with
the new password. Nothing needs taking offline in between.

### `POST /v1/accounts/{id}/mode` [#post-v1accountsidmode]

Changes the mode. Applies the next time the account comes online.

### `GET /v1/accounts/{id}/status` [#get-v1accountsidstatus]

The cheap one, made for polling. Every two seconds is fine.

### `GET /v1/accounts/{id}/figures` [#get-v1accountsidfigures]

Balance, equity, margin, free margin, margin level, credit and open profit, as
decimal strings in the account's currency, read from its MetaTrader terminal.

```bash
curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/figures" -H "Authorization: Bearer $FXAPIS_KEY"
# → { "data": { "accountId": "…", "balance": "10000.50000000", "equity": "10037.75000000",
#      "margin": "120.00000000", "freeMargin": "9917.75000000", "marginLevel": "8364.79166667",
#      "credit": "0.00000000", "profit": "37.25000000", "currency": "USD", "leverage": 500,
#      "marginMode": "hedging", "observedAt": "2026-10-15T12:00:00.120Z", "ageMs": 4210,
#      "source": "snapshot" } }
```

* **Without `fresh`, it answers the last read** (`source: "snapshot"`). While
  the account is online that read is taken every 15 seconds, with `observedAt`
  saying when. `GET /v1/accounts/{id}` and the account list carry the same
  figures, with `figuresObservedAt`. An account never read has `null` figures.
* **With `fresh=true`, the terminal is asked now** (`source: "live"`). The
  account must be `ready`: offline, `executing` an order or `degraded` is
  `409 ACCOUNT_NOT_READY`. A terminal still running an older worker answers
  `501 CAPABILITY_UNAVAILABLE` instead — rare in practice; leave out `fresh`
  for that account meanwhile. Callers within the same second share one read,
  and only a read that reaches the terminal is charged to the account's live
  budget ([Errors](/errors#rate-limits)).
* **`marginLevel` is `null` while no margin is used.** Equity over no margin is
  undefined, not zero.
* **`credit` and `profit` are filled for every online account**; `null` only
  for one never read.
* **`marginMode`** is `netting` (one position per symbol) or `hedging` (one per
  order), `null` until the terminal has reported it.
* **Webhooks do not carry figures.** `account.state_changed` fires on a change
  of state, and balance and equity change without one. Read them here.

## Polling well [#polling-well]

```python
import time, httpx

def wait_until_ready(client, account_id, timeout=120):
    """Polls until the account is ready, or until it is clear it will not be."""
    NEEDS_ATTENTION = {"invalid_credentials", "needs_2fa", "needs_certificate", "trading_disabled"}
    deadline = time.monotonic() + timeout

    while time.monotonic() < deadline:
        state = client.get(f"/v1/accounts/{account_id}/status").json()["data"]

        if state["state"] == "ready":
            return state
        if state["state"] in NEEDS_ATTENTION:
            # Not retryable: report the reason now instead of timing out later.
            raise RuntimeError(f"{account_id} needs attention: {state['state']} — {state['detail']}")

        time.sleep(2)

    raise TimeoutError(f"{account_id} did not reach ready within {timeout}s (last: {state['state']})")
```

Two habits that save support tickets:

**Stop on a "needs attention" state.** Polling for two minutes on
`invalid_credentials` tells you nothing the first response did not.

**Allow more time than feels necessary.** About 10 seconds in our measurement,
and a busy broker can take longer. A 120-second timeout costs nothing when things go
well.
