Docs

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

Set when you connect the account, and changed any time with POST /mode.

ModeOnlineFirst orderBest for
always_onOnce 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)ImmediateAccounts that must act the instant something happens — copied signals, a market open
warm_on_demandWhen you trade, then offline by itself when idleAfter a short connectMost integrations, and signal services
coldOnly when you askAfter you bring it onlineAccounts 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.

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

States

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

Online — can trade

StateMeaning
readyOnline and accepting orders.
executingOnline; an order is being placed right now.

Connecting — wait

StateMeaning
provisioning, starting, connectingComing online and logging in at the broker.
synchronizingLogged in; prices are still arriving. Orders are refused until ready, so none goes out against incomplete prices.
reconnectingThe broker connection dropped and is being restored.
degradedOnline but unhealthy — slow or stale prices. Usually recovers by itself; restart it if not.

Offline — not connected to the broker right now

StateMeaning
createdConnected, never brought online yet.
standbyOffline and known to work. A warm_on_demand account comes online on its next order; any account comes online when you ask (/warm).
coolingGoing offline.
offlineDisconnected (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

StateMeaningWhat to do
invalid_credentialsThe 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_disabledThe 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_2faThe account requires a second factor.Turn two-factor off for this account, or use another.
needs_certificateThe broker requires a client certificate.Contact us.
errorSomething 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

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.

"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 has updates. A refused login, 2FA, a certificate or trading disabled keep their own codes: those come from the broker.

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

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).

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

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:

curl "https://api.fxapis.com/v1/brokers/servers?q=justmarkets" -H "Authorization: Bearer $FXAPIS_KEY"
{ "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

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

Full request and response shapes are in the API reference. What follows is what the reference cannot tell you.

POST /v1/accounts

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.

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

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

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

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

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}

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

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

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

GET /v1/accounts/{id}/status

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

GET /v1/accounts/{id}/figures

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

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).
  • 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

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.

On this page