Set up an MT5 trade copier through the API, step by step

9 min readMetaKit

A trade copier is the one place in an MT5 integration where a wrong config costs real money on an account you might not even be watching. Set the lot mode to multiplier when you meant fixed and a 2-lot master trade becomes a 2-lot follower trade on a $1,000 account. The copier did exactly what it was told.

The conceptual post explains the pipeline. This one is the how-to: create the copier over the REST API, size it correctly, map symbols, guard slippage, and confirm the first fill before you walk away.

It's also the way to make trades happen on several accounts at once without writing execution code. If you'd rather place orders yourself, the order endpoints do that directly. A copier is for the other shape: you trade one source account, by hand or through the API, and every follower gets the same trades without a line of code per account.

Prerequisites

Two connected accounts, and the follower has to be a full account. A read-only account is logged in with the investor password, which can't place orders, so it can't follow. Try it and you get a 400 invalid_request with "Follower must be a full account". The source can be either tier; watching with an investor password is fine.

Both accounts must be connected when you create the copier, and you need an API key with the full scope (a readonly key can't POST).

export METAKIT_KEY="stk_live_..."
curl -s https://api.metakit.cloud/v1/accounts -H "Authorization: Bearer $METAKIT_KEY" \
  | python -c "import sys,json; [print(a['id'], a['type'], a['status'], a['currency'], a['balance']) for a in json.load(sys.stdin)['data']]"

Note the ids, types, and balances. You'll want the balances for the sizing maths in a moment.

Create the trade copier over the API

curl -s -X POST https://api.metakit.cloud/v1/copiers \
  -H "Authorization: Bearer $METAKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "source_account_id": 3,
        "follower_account_id": 5,
        "state": "off",
        "lot_mode": "multiplier",
        "lot_value": 0.5,
        "reverse": false,
        "copy_existing": false,
        "filters": { "symbols": ["EURUSD", "XAUUSD"], "max_open_positions": 10 }
      }'

Two deliberate choices there. "state": "off" creates it paused so you can finish configuring before anything replicates; you flip it to on at the end. And copy_existing: false means positions already open on the source are ignored. Set it to true and the copier opens follower copies of the source's current open positions on creation, at whatever price is available now, which is rarely what you want on a live account.

The response is the copier object with an id, and every field echoed back in snake_case.

Pick a sizing mode, with the numbers

lot_mode decides how a source volume becomes a follower volume. Assume the source has a $10,000 balance, the follower has $2,500, and the master opens 0.80 lots of EURUSD.

fixed: the follower always trades lot_value lots, regardless of what the master did. With lot_value: 0.10, the 0.80 becomes 0.10. So does a 0.01 scalp and a 5.00 punt. Use it when follower risk has to be constant and the master's sizing is not to be trusted.

multiplier: source volume times lot_value. With lot_value: 0.5, 0.80 becomes 0.40. Simple and predictable, and it stays correct only as long as the two balances keep the same ratio; if the follower draws down 30% the multiplier doesn't know.

proportional: source volume scaled by follower balance divided by source balance, read live at copy time. Here that's 2,500 / 10,000 = 0.25, so 0.80 becomes 0.20. The lot value isn't part of this formula. This is the balance- ratio mode, and it's the right default for "equal relative risk" across followers of different sizes.

Whatever the mode produces still has to be a valid lot at the follower's broker. 0.03 lots on the master under proportional at 0.25 gives 0.0075, which is below any broker's minimum. Set filters.min_lot (say 0.01) and copies that scale below it are skipped with a copier.trade_skipped event rather than rounded up into more risk than you asked for. On the other side, filters.max_open_positions stops the follower stacking more positions than you want, and filters.max_lot caps the volume after scaling so one oversized master trade can't wreck the follower.

You can change all of this later without recreating the copier:

curl -s -X PUT https://api.metakit.cloud/v1/copiers/7/risk \
  -H "Authorization: Bearer $METAKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "lot_mode": "proportional", "lot_value": 1, "filters": { "min_lot": 0.01, "max_lot": 2, "max_open_positions": 10 } }'

Map symbols with the bulk endpoint

Brokers name the same instrument differently. Gold is XAUUSD at one and GOLD at another; the follower's broker may suffix everything with .pro or .r. The copier matches names exactly, so a source XAUUSD trade with no GOLD at the follower is skipped, not guessed. You tell it the mapping:

curl -s -X PUT https://api.metakit.cloud/v1/copiers/7/symbol-map \
  -H "Authorization: Bearer $METAKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "mode": "merge",
        "map": { "XAUUSD": "GOLD", "EURUSD": "EURUSD.pro", "US30": "US30.cash" }
      }'

Keys are source symbols, values are follower symbols. mode: "merge" upserts into the existing map; sending an empty string as a value removes that key. mode: "replace" swaps the whole map for what you sent. The map also works at creation as symbol_map in the POST body, but the bulk endpoint is what you want once the copier exists.

Check the follower's actual symbol names first rather than assuming: GET /v1/accounts/5/symbols lists them, and GET /v1/accounts/5/symbols/GOLD returns the full spec including min_lot and lot_step, which is also where you find out that gold's contract size is 100 and not the 100,000 you'd assume from FX. The symbol suffixes post is about exactly this.

Slippage guards and SL/TP

Between the master's fill and the follower's fill, price moves. The follower pays that difference on top of whatever the master already paid. MT5 has no pre-trade slippage tolerance on market orders, so the guard works after the fact: set a maximum slippage in pips on the copier, and a copy that fills further from the master's price than that is closed straight back out and reported as a copier.trade_skipped with ok: false. You eat the spread on a round trip instead of holding a position at a price the master never had. One honest note: the slippage guard and the SL/TP toggle below live in the copier's settings in the dashboard for now, not on the /v1 risk endpoint, which only takes lot mode, lot value and filters.

Positive slippage in the copier's reporting always means worse for the follower, in both directions. Don't set the tolerance to zero; even a good broker will fill a pip away at the London open.

The SL/TP toggle controls whether the master's stop loss and take profit are mirrored onto the copy. It defaults to on and you almost always want it. The exception is a follower running its own hard risk rules, for example a funded account with a daily loss cap, where you'd rather an equity monitor manage the downside.

Turn it on and verify the first fill

curl -s -X PATCH https://api.metakit.cloud/v1/copiers/7 \
  -H "Authorization: Bearer $METAKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "state": "on" }'

Now open a small trade on the source and watch. The copier emits a webhook for every decision, and the one you're waiting for is copier.trade_copied:

{
  "event": "copier.trade_copied",
  "timestamp": "2026-08-16T09:41:00.000Z",
  "copier_id": 7,
  "source_account_id": 3,
  "follower_account_id": 5,
  "action": "open",
  "message": "Copied EURUSD buy 0.40",
  "ok": true,
  "latency": { "a": 120, "b": 45, "c": 30, "total": 195 }
}

latency is in milliseconds: a is detection, b dispatch, c execution at the follower's broker. Master fill to follower fill is consistently under a second. If instead you get copier.trade_skipped, the message says why: not in the symbol filter, below min_lot, max open positions reached, or slippage exceeded. copier.error means the follower rejected the order (insufficient margin, market closed, terminal down). copier.orphan means the master closed something the follower never had a copy of, which is the one that tells you the two accounts have drifted apart.

A short receiver that watches one copier, assuming you've already got signature verification in place:

type CopierEvent = {
  event: "copier.trade_copied" | "copier.trade_skipped" | "copier.error" | "copier.orphan";
  copier_id: number;
  action: string;
  message: string;
  ok: boolean;
  latency?: { a: number; b: number; c: number; total: number };
};
 
export function onCopierEvent(e: CopierEvent) {
  if (e.copier_id !== 7) return; // every webhook you own gets every event
  const tag = e.ok ? "ok " : "FAIL";
  const ms = e.latency ? `${e.latency.total}ms` : "";
  console.log(`[${tag}] ${e.event} ${e.action} ${e.message} ${ms}`);
  if (e.event === "copier.orphan" || e.event === "copier.error") {
    // page someone; the accounts are no longer mirrors of each other
  }
}

After a few trades, GET /v1/copiers/7/performance?range=1m gives you fidelity_pct, latency percentiles, slippage_by_symbol, and copied_pnl attributed by follower ticket, so the follower's own manual trades don't leak into the number.

Pause with monitor, not off

Three states, and the middle one is the useful one.

on replicates everything: opens, closes, SL/TP changes, pending orders. off stops all of it, including closes, so if the master exits a position while the copier is off, the follower keeps holding it. monitor opens nothing new but still mirrors closes, which winds the link down cleanly: the follower's copied positions exit when the master's do, and nothing fresh appears.

curl -s -X PATCH https://api.metakit.cloud/v1/copiers/7 \
  -H "Authorization: Bearer $METAKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "state": "monitor" }'

When you want to stop copying a strategy, monitor is almost always what you mean.

Deleting a copier leaves the positions open

DELETE /v1/copiers/7 returns 204 and removes the link. It does not touch the follower account. Every position the copier opened is still open, now with nothing watching the master to close it. People find this out on a Monday.

The safe sequence is monitor, wait until the follower's copied positions have all closed with the master's (or close them by hand), then delete. If you need the follower flat now, close its positions on the follower's own terminal or app first, then delete.

The copier fields above are the whole set; they're listed under "Copiers" in llms.txt and the docs. Start with state: "off", one symbol, and a 0.01-lot test trade on a demo follower. The fastest way to learn what multiplier does is not on a funded account.