Skip to main content

fxapis

The official Python SDK for fxapis — the hosted MetaTrader 5 REST API

Website · Docs · API Reference · Status · Support

License: MIT ci PyPI release Python types fxapis status


Connect an MT5 account once, then place market and pending orders, close and modify positions, read deals and positions, and send one trade to many accounts at once — from Python, over HTTPS.

You run no MetaTrader terminal, no Windows VPS and no EA. fxapis runs the MT5 terminals in its cloud (EU, Amsterdam) and gives you an API in front of them. That makes it a fit for MT5 Python automation on Linux or macOS, copy trading and trade copiers, click-to-trade signal apps, and anything that manages many MT5 accounts.

  • Sync (Fxapis) and async (AsyncFxapis) clients, built on httpx
  • Fully typed (py.typed, TypedDict responses with the API's exact field names), checked with mypy --strict
  • Automatic Idempotency-Key on every order, with an override for your own keys
  • Typed exceptions for every API error code — and safe retries that never resend an unresolved order
  • Helpers: wait_until_ready(), wait_until_resolved(), wait_until_settled(), pagination iterators
  • fxapis.webhooks.verify() checks every webhook delivery's signature

MT5 only (MT4 is not supported).

Table of contents

Installation

pip install fxapis

Python 3.10+. Create an API key in the console at fxapis.com and export it:

export FXAPIS_API_KEY="fx_test_..."

Quickstart

from fxapis import Fxapis

client = Fxapis()  # reads FXAPIS_API_KEY

# 1. Connect an MT5 account (once). Use the trading password, not the investor password.
account = client.accounts.connect(
    login="26177561",
    server="VantageMarkets-Demo",
    password="your-mt5-trading-password",
    mode="warm_on_demand",  # online when needed, offline after 15 idle minutes
    label="demo — strategy A",
)

# 2. Bring it online and wait (about 10 seconds for a typical broker).
client.accounts.warm(account["id"])
client.accounts.wait_until_ready(account["id"])

# 3. Trade. Volumes and prices are strings: "0.01", not 0.01.
order = client.orders.market(
    account["id"],
    symbol="EURUSD",
    side="buy",
    volume="0.01",
    stop_loss="1.12900",
    take_profit="1.14200",
)
print(order["state"], order["filledPrice"])

# 4. Read positions, then close one.
for position in client.positions.list(account["id"]):
    print(position["symbol"], position["side"], position["volume"], position["profit"], position["observedAt"])
    client.positions.close(account["id"], position["brokerPositionId"])

The with Fxapis() as client: form closes the connection pool for you.

Handling order outcomes

An order can end three ways that must be told apart. The SDK raises a different exception for each:

from fxapis import (
    Fxapis,
    OrderRejectedError,
    OrderUnresolvedError,
    SendFailedError,
    AccountNotReadyError,
)

client = Fxapis()
key = f"signal_{signal_id}:member_{member_id}"  # your own idempotency key

try:
    order = client.orders.market(account_id, symbol="XAUUSD", side="buy", volume="0.05", idempotency_key=key)
except OrderRejectedError as err:
    # The broker refused it; nothing opened. err.retryable says whether the reason was
    # transient (a requote). A new attempt needs a NEW key — this one now answers with the rejection.
    print("rejected:", err.message)
except OrderUnresolvedError as err:
    # Nobody knows yet whether it reached the broker. NEVER resend it. Poll until fxapis
    # has confirmed the result with the broker:
    order = client.orders.wait_until_resolved(err.order_id)
except (SendFailedError, AccountNotReadyError) as err:
    # Nothing was sent. The SDK already retried with the same key (max_retries);
    # retrying later with err.idempotency_key is still safe.
    print("not sent:", err.code)
Error class API code Retried automatically?
SendFailedError SEND_FAILED (503) Yes, same key
AccountNotReadyError / NoRuntimeError ACCOUNT_NOT_READY / NO_RUNTIME (409) Yes, same key
IdempotencyInFlightError IDEMPOTENCY_IN_FLIGHT (409) Yes, same key
RateLimitedError RATE_LIMITED (429) Yes, after Retry-After
OrderUnresolvedError ORDER_UNRESOLVED (503) Never — poll the order
OrderRejectedError ORDER_REJECTED (422) Never — needs a new key
IdempotencyKeyReusedError IDEMPOTENCY_KEY_REUSED (409) Never
QuotaExceededError / FeatureNotInPlanError 402 Never
AuthenticationError, PermissionDeniedError, InvalidRequestError, NotFoundError 401 / 403 / 400 / 404 Never
APIConnectionError / APITimeoutError no HTTP answer Reads, and requests with an idempotency key

Every API error is an APIStatusError with .status, .code, .message, .request_id (quote it to support), .details, .order_id, .idempotency_key and .retryable. Tune retries with Fxapis(max_retries=0..n).

Idempotency keys. Market orders, pending orders, closes and multi-account orders always carry an Idempotency-Key — a UUID unless you pass idempotency_key=. The API honours a key for 24 hours: the same key with the same body returns the first answer and never places a second order. Derive your own key from something stable (a signal and a member, a strategy tick) when a double click or a restarted worker must not trade twice.

Click-to-trade and signals

For apps where each member approves a signal with a click: prepare the member's account when they open the signal, then place the order with a key made from the signal and the member.

client.accounts.prepare([member.fxapis_account_id])  # up to 200 accounts per call; returns at once

order = client.orders.market(
    member.fxapis_account_id,
    symbol=signal.symbol,
    side=signal.side,
    volume=member.lot_size,
    stop_loss=signal.stop_loss,
    take_profit=signal.take_profit,
    client_order_id=f"signal_{signal.id}",
    idempotency_key=f"signal_{signal.id}:member_{member.id}",
)

The full walkthrough is in the signals guide.

Copy trading: one trade on many accounts

A multi-account order ("execution wave") brings every account online, then sends the orders together:

wave = client.waves.create(
    account_ids=follower_ids,  # up to 1,000, within your plan's limit
    symbol="EURUSD",
    side="buy",
    volume="0.10",
    weights={big_account_id: "0.50"},  # per-account volume, optional
    barrier_policy="release-ready",  # or "all-or-nothing", "wait"
    client_wave_id="master-deal-123456",
)
wave = client.waves.wait_until_settled(wave["id"])
print(wave["summary"], wave["dispatchSpreadMs"])

When the accounts are at different brokers, name an instrument instead of a broker symbol, and each account gets its own broker's symbol (from 0.1.4; pass exactly one of symbol and instrument):

wave = client.waves.create(account_ids=follower_ids, instrument="XAUUSD", side="buy", volume="0.10")

client.multi_account_orders is the same resource under a descriptive name. To follow a master account, poll its deals (client.deals.list(master_id, since=...)) as copy_trader.py does, or subscribe a webhook to position.opened and position.closed.

Webhooks

Register an HTTPS endpoint — in the console under Webhooks, or with POST /v1/webhooks — and fxapis POSTs a signed event to it when something happens: an order filled, rejected or left unresolved, a position opened or closed, an account changing state, a multi-account order finishing. The endpoint's whsec_… secret is shown once, when you create it.

Check every delivery before trusting it. Pass the body exactly as it arrived (bytes or str), before any JSON parsing:

from fxapis import WebhookVerificationError, webhooks

@app.post("/fxapis/webhooks")
def receive(request):
    try:
        event = webhooks.verify(request.body, request.headers.get("fxapis-signature"), FXAPIS_WEBHOOK_SECRET)
    except WebhookVerificationError:
        return Response(status=400)
    if already_handled(event["id"]):  # deliveries can repeat: deduplicate on the event id
        return Response(status=200)
    handle(event)  # event["type"], event["data"] — the resource as the API returns it
    return Response(status=200)

verify() rejects a signature more than 5 minutes old (tolerance= to change it), and during a secret rotation accepts either secret. Answer within 10 seconds; failed deliveries are retried for about two days. Event types, payloads, retries and rotation: the webhooks guide.

Catch up after downtime

From 0.1.5, the event log lets a receiver that was down pick up what it missed. Store the id of the last event you processed from a list page (never a webhook's id: webhooks can arrive out of order), and on startup read everything after it:

for event in client.events.iter(after=last_listed_id):  # oldest first
    if not already_handled(event["id"]):
        handle(event)  # the same shape a webhook delivers
    last_listed_id = event["id"]

Events appear in the list a few seconds after they happen, and are kept 7, 30 or 90 days depending on your plan. client.events.list(delivery_status="failed") shows events an endpoint did not receive, and client.webhooks.replay(endpoint_id, since=...) re-sends them (failed ones only, unless include="all"). client.webhooks.deliveries() and client.webhooks.delivery() read an endpoint's delivery log.

Async

import asyncio
from fxapis import AsyncFxapis


async def main() -> None:
    async with AsyncFxapis() as client:
        accounts = await client.accounts.list()
        async for order in client.orders.iter(state="unknown"):
            print(order["id"], order["symbol"])


asyncio.run(main())

Reference

Resource Methods
client.workspace get()
client.accounts connect(), list(), get(), status(), warm(), prepare(), cool(), restart(), disconnect(), delete(), set_mode() (alias mode()), replace_credentials(), reconcile(), wait_until_ready(), bring_online(), symbols(), instruments(), sessions() (market hours), sync_symbols()
client.instruments list() — markets named once (XAUUSD, US30…); pass one as instrument= to orders.market() / orders.pending() to trade each account's own broker symbol for it
client.orders market(), pending(), modify(), cancel(), get(), list(), iter(), deals(), wait_until_resolved()
client.positions list(), close(), modify()
client.deals list(), iter(), for_order()
client.calculate margin(), profit()
client.waves (= client.multi_account_orders) create(), get(), list(), cancel(), wait_until_settled()
client.alert_hooks create(), list(), get(), update(), rotate(), enable(), disable(), delete(), deliveries() — TradingView alerts to MT5
client.events list(), iter(), get() — the event log, for catching up after downtime
client.webhooks deliveries(), delivery(), replay()
client.admin.keys list(), create(), get(), update(), revoke() — needs an admin key
client.usage get(), daily()
client.plans list()

Notes:

  • orders.list() and deals.list() return a Page (.data, .has_more, .next_cursor); iter() walks every page for you.
  • positions.modify(..., stop_loss=None) removes the stop loss; leaving the argument out keeps it. The same holds for orders.modify.
  • Times accept datetime (sent as RFC 3339 UTC) or strings. Numbers accept str, Decimal, int or float (rounded to 8 places) and are always sent as strings.
  • Fxapis(base_url=..., timeout=..., max_retries=..., http_client=httpx.Client(...)) for proxies, custom transports or tests.

Admin keys: manage API keys from code

From 0.1.6, client.admin.keys lists, creates, changes and revokes your workspace's ordinary API keys. It needs an admin key (fx_admin_…), which the workspace owner makes in the console under API keys → Admin keys, optionally limited to IP addresses and given an expiry. An admin key cannot trade or read account data itself, but the keys it creates can.

from fxapis import Fxapis

admin = Fxapis(api_key=FXAPIS_ADMIN_KEY)

created = admin.admin.keys.create(
    name="trader-42",
    scopes=["trading:execute", "trading:read"],
    limits={"maxVolume": "1.00", "accountIds": [account_id]},
)
# created["key"] is shown once: hand it to the trader's integration now.

admin.admin.keys.revoke(created["id"])  # when the evaluation ends

An ordinary key used on these methods raises PermissionDeniedError (ADMIN_KEY_REQUIRED). Managing your team (members and invitations) is done in the console only.

Everything else in the API — including billing reads — is in the API reference, with samples in 13 languages.

Good to know

  • Accounts on demand (warm_on_demand) come online for an order or a prepare and go offline after 15 idle minutes. Stop losses and take profits live at the broker and keep working while an account is offline.
  • Positions are a snapshot. Each has observedAt; call accounts.reconcile() for a fresh read while the account is online.
  • Scoped keys. Keys can be read-only or reduce-only (can close, cannot open) — give each service the least it needs.
  • Wrong password? accounts.replace_credentials(id, password=...) fixes it on the same account; connecting a disconnected login again brings the same account back. accounts.delete(id) removes an account and its history for good.
  • Events or polling. Webhooks tell you when an order fills or an account changes state; polling status, orders and deals still works everywhere. (Incoming TradingView alerts are a separate feature — see client.alert_hooks.)
Repository What it is
fxapis-examples Runnable examples using this SDK (Python, Node/TypeScript, curl)
fxapis-typescript The official TypeScript/Node.js SDK, same conventions
fxapis-mcp-examples Connect AI agents (Claude, Cursor, VS Code) over MCP
fxapis-integrations Postman collection, TradingView payloads, automation templates

Contributing

What changed in each version is in CHANGELOG.md, and how a version is released in RELEASING.md.

pip install -e ".[dev]"
ruff check .
mypy
pytest -q

These are exactly what the ci workflow runs, on Python 3.10 through 3.14. Pull requests that fix a bug, improve a docstring, or add a test are welcome. A behaviour change to a method's signature or return shape should match the TypeScript SDK's equivalent method — both are meant to stay in step.

Support

License

MIT — see LICENSE.

© 2026 El Wizard

Metadata

Release files for fxapis 0.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fxapis 0.1.7
File Size Uploaded
fxapis-0.1.7.tar.gz 53.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fxapis 0.1.7
File Interpreter ABI Platform
fxapis-0.1.7-py3-none-any.whl Python 3 none any Details

Total release size: 100.3 kB

Release files / fxapis-0.1.7.tar.gz

Download URL fxapis-0.1.7.tar.gz
Size 53.0 kB
Tags Source
SHA-256 checksum
How to use checksums
71f7ab063dfd7d02c6bd2a638d0b7d8e5a662584780c94de0c9bf3f048942795
BLAKE2b-256 checksum
How to use checksums
5e8a2aa08b73dcb597811a61f867754b692d0ba37cb29a0f5f07e99012a945f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / fxapis-0.1.7-py3-none-any.whl

Download URL fxapis-0.1.7-py3-none-any.whl
Size 47.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8446560aa9f00ec6d25e969ca0e30e7a8dd00d03ed38526d00dd17a8c22428cb
BLAKE2b-256 checksum
How to use checksums
1f784957d2f2ab7ef64beeecd11edc17b55b42badf81013199244bbb6d1c12a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.7 This release

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page