# Connect MT5 accounts by login, password and server

URL: https://docs.fxapis.com/guides/connect-accounts

> How to connect a MetaTrader 5 account to the fxapis API: finding the exact server name, trading versus investor password, two-factor sign-in, handling…



Connecting an MT5 account takes three things from its owner — the **login**,
the **trading password** and the **server name** — and one API call. Getting
those three right, and handling the password with care on the way, is most of
what makes a connect screen work. This guide covers both.

```bash
curl -sS -X POST "https://api.fxapis.com/v1/accounts" \
  -H "Authorization: Bearer $FXAPIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "login": "81234567",
    "server": "VantageMarkets-Live",
    "password": "the-trading-password",
    "mode": "warm_on_demand",
    "label": "member_4821"
  }'
```

**201** returns the account with its `id` and `state: "created"`. It does not
log in yet — that happens when the account is first brought online, which is
why you should [check the credentials straight away](#check-the-credentials-immediately).
Reference: [Connect an MT5 account](/api-reference/accounts/connect-an-mt5-account).

## The three things you need [#the-three-things-you-need]

### Login [#login]

The MT5 account number, digits only, sent as a **string**: `"81234567"`. A
login with anything other than digits is refused with **400**
`INVALID_REQUEST`. It is not an email address and not the user's name at the
broker's client portal — many brokers have both, and people confuse them.

### Password: trading, not investor [#password-trading-not-investor]

Every MT5 account has two passwords:

| Password                      | What it allows          | Works with fxapis?                             |
| ----------------------------- | ----------------------- | ---------------------------------------------- |
| **Trading** (master) password | Log in, read, and trade | **Yes** — this is the one to send              |
| **Investor** password         | Log in and read only    | No — the login succeeds but trading is refused |

The investor password is the classic mistake, because nothing fails at first.
The account reaches `ready` and its first order is rejected. Ask for
the trading password by that name on your connect screen, and say why.

### Server: exactly as MetaTrader shows it [#server-exactly-as-metatrader-shows-it]

The server name identifies the broker's trade server the account lives on —
`VantageMarkets-Demo`, `VantageMarkets-Live`, `ICMarkets-Live02`. It must match
**exactly**, including capitals, hyphens and any number at the end.

Where the account's owner can find it:

* **MetaTrader 5 desktop:** the *Navigator* window lists each account with its server; the login
  dialog (*File → Login to Trade Account*) shows it too.
* **MetaTrader 5 mobile:** the account screen in the app's settings shows the server under the
  account number.
* **The broker's account email:** brokers send the login and server when an account is opened.

Naming tips worth putting on your connect screen:

* **Live and demo are different servers.** `…-Demo` and `…-Live` accounts cannot be swapped.
* **Numbered servers are separate servers.** `…-Live02` is not `…-Live`, and an account exists on
  exactly one of them.
* **One broker can run several brands and regions**, each with its own servers. The account's
  server is the one it was opened on, not the one on the broker's homepage.
* **Trim what the user types.** A trailing space from copy-paste is a different server name. We store
  the value exactly as sent.
* **Prefer a free-text field with examples** over a fixed dropdown; brokers add and rename servers.

<Callout type="warn" title="Test with demo accounts first">
  API keys labelled `fx_test_` still reach real brokers. Build your connect flow against broker
  **demo** accounts before connecting anybody's real money.
</Callout>

## Choosing a mode [#choosing-a-mode]

`mode` decides when the account is online. The default is `cold`, so set it:

| Mode             | Use it for                                                                                                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `warm_on_demand` | Most integrations. Comes online for an order (or when you prepare it) and goes offline after 15 idle minutes.            |
| `always_on`      | Accounts that must act instantly at any time — masters of a copier, accounts under live risk monitoring. A plan feature. |
| `cold`           | Accounts you bring online yourself, on a schedule.                                                                       |

You can change it later with
[`POST /v1/accounts/{id}/mode`](/api-reference/lifecycle/change-how-eagerly-the-account-stays-connected).
See [Accounts → Modes](/accounts#modes).

## Handling credentials in your app [#handling-credentials-in-your-app]

The MT5 password is the key to someone's money. fxapis encrypts it on arrival
with a key unique to that account, never returns it from any endpoint, and
erases it when the account is disconnected — see [Security](/security). Your
side of the job is to make sure it passes through your systems without leaving
a trace:

* **Send it from your backend, straight to fxapis, in the same request that received it.** Your
  API key must never be in a browser or mobile app, so the form posts to your server and your
  server calls `POST /v1/accounts`.
* **Never store it** — not in your database, a cache, a queue job, an analytics event or a support
  ticket. Store the fxapis account `id` instead; it is all you need from then on.
* **Never log it.** Turn off request-body logging for the connect route, and make sure error
  reporting tools do not capture the body when the call fails.
* **Only over HTTPS**, and never in a URL or query string, where proxies and browsers keep copies.
* **Do not retry the connect call from a background job.** That would mean storing the password to
  retry later. If fxapis answers **503** `SECRET_STORE_UNAVAILABLE`, nothing was created: retry a
  couple of times in the same request, then ask the user to try again.

```python
# fxapis = httpx.Client(base_url="https://api.fxapis.com", headers={"Authorization": f"Bearer {KEY}"})
@app.post("/connect-mt5")
def connect_mt5(form: ConnectForm, user: User):
    response = fxapis.post("/v1/accounts", json={
        "login": form.login.strip(),
        "server": form.server.strip(),
        "password": form.password,          # used once, here, and never kept
        "mode": "warm_on_demand",
        "label": f"user_{user.id}",
    }, timeout=30)
    if response.status_code == 409:         # ACCOUNT_EXISTS: already connected in your workspace
        return {"error": "This account is already connected."}
    response.raise_for_status()
    account = response.json()["data"]
    user.fxapis_account_id = account["id"]  # the id, not the password
    fxapis.post(f"/v1/accounts/{account['id']}/warm")
    return {"accountId": account["id"]}
```

A login and server can be connected **once per workspace**. A second attempt
returns **409** `ACCOUNT_EXISTS`: two connections would mean two places able to
trade the same money. If two of your users claim the same MT5 account, that is
a question for your support team, not a retry.

Connecting more accounts than your plan allows returns **402**
`QUOTA_EXCEEDED`.

## Check the credentials immediately [#check-the-credentials-immediately]

A new account has not logged in yet. If you wait for its first trade to find
out that the password is wrong, the failure arrives at the worst moment. Bring
it online while the user is still on the connect screen:

1. `POST /v1/accounts/{id}/warm` — answers **202** at once.
2. Poll `GET /v1/accounts/{id}/status` every two seconds until `state` is `ready` or a
   needs-attention state. About 10 seconds in our measurement; allow up to two minutes.
3. Show the result.

```ts
const NEEDS_ATTENTION = ["invalid_credentials", "trading_disabled", "needs_2fa", "needs_certificate"];

async function checkCredentials(accountId: string): Promise<string> {
  const deadline = Date.now() + 120_000;
  while (Date.now() < deadline) {
    const res = await fetch(`${API}/v1/accounts/${accountId}/status`, { headers: { Authorization: `Bearer ${KEY}` } });
    const { data } = await res.json();
    if (data.state === "ready" || NEEDS_ATTENTION.includes(data.state)) return data.state;
    await new Promise((r) => setTimeout(r, 2000));
  }
  return "timeout";
}
```

In your UI, poll your own backend from the browser, and let the backend call
fxapis. Once the check is done, a `warm_on_demand` account goes offline again
by itself after 15 minutes; nothing else is needed.

## The needs-attention states [#the-needs-attention-states]

| State                 | What it means                                   | Tell the user                                                                                                 |
| --------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `invalid_credentials` | The broker rejected the login.                  | The login, password or server is wrong. Check the **trading** password and the exact server name.             |
| `trading_disabled`    | The broker has disabled trading for this login. | Ask the broker to enable trading. (An investor password instead reaches `ready` and its orders are rejected.) |
| `needs_2fa`           | The account requires a second factor at login.  | Two-factor sign-in must be turned off for this account; it cannot be completed by an API.                     |
| `needs_certificate`   | The broker requires a client certificate.       | Contact support.                                                                                              |
| `error`               | Something temporary went wrong.                 | Try again; bringing the account online again is safe.                                                         |

The first four will not fix themselves, and **repeated failed logins are how a
broker locks an account**. So fxapis stops trying: bringing such an account
online returns **409** `NEEDS_OPERATOR`, and `prepare` reports it as
`needs_operator`, without contacting the broker. Do not put these in a retry
loop. All states are listed in [Accounts → States](/accounts#states).

## Correcting credentials [#correcting-credentials]

When a password was wrong, or the member changed it at the broker, replace it on the existing
account — its id, history and settings stay:

```bash
curl -sS -X POST "https://api.fxapis.com/v1/accounts/$ACCOUNT_ID/password" \
  -H "Authorization: Bearer $FXAPIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "password": "the-members-corrected-password" }'
```

Add `"server"` to correct the server name at the same time. The account is taken offline first
(refused with **409** `ACCOUNT_EXECUTING` while an order is in flight), the new password is stored,
and only then is the old one erased. The account lands in `created`: bring it online straight away
and poll its status, exactly as on the connect screen, so the member learns at once whether the new
password works.

A login that was **disconnected** can also simply be connected again with `POST /v1/accounts`: the
same account comes back with the new password, and the answer is **200** instead of **201**.

## Disconnecting [#disconnecting]

```bash
curl -sS -X POST "https://api.fxapis.com/v1/accounts/$ACCOUNT_ID/disconnect" \
  -H "Authorization: Bearer $FXAPIS_KEY"
```

`disconnect` takes the account offline and **erases the stored password** at
once; the response says `"credentialsRemoved": true`. The account record and
its whole trading history remain, in state `offline`, for your records and any
dispute. Call it when a user removes their account or leaves your product: a
password you no longer need is one nobody should keep holding.

If you only want the account offline for now, use
[`POST /v1/accounts/{id}/cool`](/api-reference/lifecycle/take-an-account-offline) instead — it keeps
the password so the account can come back. Open positions are untouched either
way; stop losses and take profits live at the broker and keep working.

## Connecting many accounts [#connecting-many-accounts]

* Connect accounts one call each, as users sign up.
* Bring up to 200 online in one call with
  [`POST /v1/accounts/prepare`](/api-reference/lifecycle/bring-several-accounts-online-at-once); each
  gets its own result (`starting`, `already_running`, `needs_operator`…).
* `GET /v1/accounts` lists every account with its `state`, so a nightly job can find those needing
  attention and prompt their owners.

## Related [#related]

* [Signals and click-to-trade](/signals) — a complete member-facing integration.
* [Accounts](/accounts) — modes, states and every lifecycle endpoint.
* [Security](/security) — how passwords are stored and erased.
* Language guides: [Python](/guides/python), [Node.js](/guides/nodejs), [PHP](/guides/php),
  [C#](/guides/csharp), [Go](/guides/go), [Java](/guides/java), [Ruby](/guides/ruby).
