# Quickstart

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

> From an API key to a filled order, in about five minutes.



You need an API key ([create one in the console](https://fxapis.com/dashboard/keys)),
an MT5 account number, its **trading** password, and the server name exactly as
MetaTrader shows it — `VantageMarkets-Demo`, `ICMarkets-Demo`. Use a demo
account the first time.

<Callout type="warn">
  The investor password will not work. It can read an account but cannot trade, and the login
  succeeds — so the failure arrives later, as a rejected order.
</Callout>

<Steps>
  <Step>
    ### Set your key [#set-your-key]

    <CodeBlockTabs defaultValue="cURL">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="cURL">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Python">
          Python
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Node.js">
          Node.js
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="cURL">
        ```bash
        export FXAPIS_KEY="fx_live_7f3a9c21_your-secret-here"
        export FXAPIS_API="https://api.fxapis.com"
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Python">
        ```python
        import os, uuid, requests

        API = "https://api.fxapis.com"
        session = requests.Session()
        session.headers["Authorization"] = f"Bearer {os.environ['FXAPIS_KEY']}"
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Node.js">
        ```js
        const API = "https://api.fxapis.com";
        const headers = {
          Authorization: `Bearer ${process.env.FXAPIS_KEY}`,
          "Content-Type": "application/json",
        };
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Keys are server-to-server credentials. Keep them out of browsers, apps and
    repositories. See [Authentication](/authentication).
  </Step>

  <Step>
    ### Connect the account [#connect-the-account]

    <CodeBlockTabs defaultValue="cURL">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="cURL">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Python">
          Python
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Node.js">
          Node.js
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="cURL">
        ```bash
        # Keeps the new account's id for the next steps (uses jq; or copy data.id by hand).
        export FXAPIS_ACCOUNT=$(curl -sS -X POST "$FXAPIS_API/v1/accounts" \
          -H "Authorization: Bearer $FXAPIS_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "login": "26177561",
            "server": "VantageMarkets-Demo",
            "password": "your-mt5-trading-password",
            "mode": "warm_on_demand",
            "label": "Demo — strategy A"
          }' | jq -r .data.id)
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Python">
        ```python
        account = session.post(f"{API}/v1/accounts", json={
            "login": "26177561",
            "server": "VantageMarkets-Demo",
            "password": "your-mt5-trading-password",
            "mode": "warm_on_demand",
            "label": "Demo — strategy A",
        }).json()["data"]
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Node.js">
        ```js
        const { data: account } = await fetch(`${API}/v1/accounts`, {
          method: "POST",
          headers,
          body: JSON.stringify({
            login: "26177561",
            server: "VantageMarkets-Demo",
            password: "your-mt5-trading-password",
            mode: "warm_on_demand",
            label: "Demo — strategy A",
          }),
        }).then((r) => r.json());
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    This stores the account and does **not** log in yet. The password is encrypted
    under a key bound to this one account and is never returned by any endpoint.
  </Step>

  <Step>
    ### Bring it online [#bring-it-online]

    <CodeBlockTabs defaultValue="cURL">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="cURL">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Python">
          Python
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Node.js">
          Node.js
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="cURL">
        ```bash
        curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/warm" \
          -H "Authorization: Bearer $FXAPIS_KEY"

        until curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/status" \
                -H "Authorization: Bearer $FXAPIS_KEY" | grep -q '"ready"'
        do sleep 2; done
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Python">
        ```python
        import time

        session.post(f"{API}/v1/accounts/{account['id']}/warm")
        while session.get(f"{API}/v1/accounts/{account['id']}/status").json()["data"]["state"] != "ready":
            time.sleep(2)
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Node.js">
        ```js
        await fetch(`${API}/v1/accounts/${account.id}/warm`, { method: "POST", headers });
        while ((await fetch(`${API}/v1/accounts/${account.id}/status`, { headers }).then((r) => r.json())).data.state !== "ready") {
          await new Promise((r) => setTimeout(r, 2000));
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    `warm` answers **202** at once, while the account logs in at the broker. Its
    state moves through `starting`, `connecting` and `synchronizing` to `ready`, in
    about 10 seconds in our measurement. Only `ready` accepts orders.
  </Step>

  <Step>
    ### Place an order [#place-an-order]

    <CodeBlockTabs defaultValue="cURL">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="cURL">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Python">
          Python
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Node.js">
          Node.js
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="cURL">
        ```bash
        curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/orders/market" \
          -H "Authorization: Bearer $FXAPIS_KEY" \
          -H "Idempotency-Key: $(uuidgen)" \
          -H "Content-Type: application/json" \
          -d '{ "symbol": "EURUSD", "side": "buy", "volume": "0.01" }'
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Python">
        ```python
        order = session.post(
            f"{API}/v1/accounts/{account['id']}/orders/market",
            headers={"Idempotency-Key": str(uuid.uuid4())},
            json={"symbol": "EURUSD", "side": "buy", "volume": "0.01"},
        ).json()["data"]
        print(order["state"], order["filledPrice"])
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Node.js">
        ```js
        const { data: order } = await fetch(`${API}/v1/accounts/${account.id}/orders/market`, {
          method: "POST",
          headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
          body: JSON.stringify({ symbol: "EURUSD", side: "buy", volume: "0.01" }),
        }).then((r) => r.json());
        console.log(order.state, order.filledPrice);
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ```json title="201 Created"
    {
      "data": {
        "id": "b1c7…9e2a",
        "symbol": "EURUSD",
        "side": "buy",
        "volume": "0.01",
        "state": "filled",
        "filledPrice": "1.13426",
        "brokerOrderId": "2109584418"
      }
    }
    ```

    Volumes and prices are strings, never floats. Keep the `Idempotency-Key` if you
    retry: the same key never places a second order. An order ends `filled`,
    `rejected`, or `unknown` while it is reconciled against the broker — see
    [Trading](/trading).
  </Step>

  <Step>
    ### Close it [#close-it]

    <CodeBlockTabs defaultValue="cURL">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="cURL">
          cURL
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="cURL">
        ```bash
        # Positions refresh by themselves about every 15 s; reconcile refreshes them now.
        curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/reconcile" -H "Authorization: Bearer $FXAPIS_KEY"
        export POSITION_TICKET=$(curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/positions" \
          -H "Authorization: Bearer $FXAPIS_KEY" | jq -r '.data[0].brokerPositionId')

        curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/positions/$POSITION_TICKET/close" \
          -H "Authorization: Bearer $FXAPIS_KEY" -H "Idempotency-Key: $(uuidgen)" \
          -H "Content-Type: application/json" -d '{}'
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    A `warm_on_demand` account goes offline by itself after a quiet spell with no
    orders; `POST /cool` takes it offline now.
    Stop losses and take profits live at the broker and keep working while the
    account is offline.
  </Step>
</Steps>

## If it does not reach `ready` [#if-it-does-not-reach-ready]

| State                 | Meaning                                  | What to do                                                                                                                                                    |
| --------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_credentials` | The broker rejected the login            | Fix the password and reconnect. Do not retry — repeated failures are how a broker locks an account.                                                           |
| `needs_2fa`           | The account requires a second factor     | Not supported headlessly. Disable 2FA or use another account.                                                                                                 |
| `needs_certificate`   | The broker requires a client certificate | Contact us.                                                                                                                                                   |
| `trading_disabled`    | Login worked, trading is off             | The broker has disabled trading for this login. (An investor password instead reaches `ready` and its orders are rejected.) Ask the broker to enable trading. |
| `error`               | Something temporary                      | Retryable. Bring it online again.                                                                                                                             |

## Next [#next]

* **Use an SDK.** The official [TypeScript and Python SDKs](/sdks) wrap these same calls, with
  typed errors and idempotency keys built in — walkthroughs for
  [TypeScript](https://fxapis.com/blog/mt5-api-typescript-sdk) and [Python](https://fxapis.com/blog/mt5-api-python-sdk).
* **Run a complete example.** [fxapis-examples](https://github.com/FXapis/fxapis-examples) has this
  quickstart, click-to-trade and a trade copier in Python, Node/TypeScript and curl, and the
  [Next.js starter](https://github.com/FXapis/fxapis-nextjs-starter) is a small working app
  ([how to run the examples](https://fxapis.com/blog/mt5-api-examples-python-node-curl),
  [how the starter is built](https://fxapis.com/blog/nextjs-mt5-trading-dashboard-starter)).
* **Stop polling.** [Webhooks](/guides/webhooks) send a signed `POST` to your server when an order
  fills, a position closes or an account changes state.
* **Go deeper.** [Trading](/trading) covers every outcome an order can have, and the
  [guides](/guides) have complete programs in seven languages.
