Trading
Place, modify, and cancel account-scoped orders with authorization and idempotency.
After a user grants trading access, you can place, modify, and cancel orders through Finatic's account-scoped routes. The route accountId is the financial account ID returned by account APIs, not the tenant companyAccountId.
Requirements
Every trading command requires:
- An active account grant with
canTrade: true. - A valid financial
accountId. - Broker-specific fields from the account's order schema.
- An idempotency key for the write command.
Important: Read access does not authorize trading. If
canTradeis false, send the user back through Connect and request trading access for that account.
Basic workflow
- Confirm the selected account grant has
canTrade: true. - Fetch
GET /api/v1/accounts/{accountId}/order-schemas?action=place. - Submit
{ order: { ... } }with an idempotency key. - Check
acceptedand the embedded order snapshot. - Track state with
listOrdersor webhooks. - Read fills and events from the same account-scoped order route family.
- Modify or cancel only while the provider and returned schema permit it.
Request and response shape
1{
2 "broker": "robinhood",
3 "order": {
4 "orderType": "market",
5 "assetType": "equity",
6 "action": "buy",
7 "timeInForce": "day",
8 "symbol": "AAPL",
9 "orderQty": 1
10 }
11}
12broker is optional. When present, it must match the account grant's broker.
A successful command returns an FDXBrokerOrderCommandResult:
1{
2 "action": "PLACE",
3 "accepted": true,
4 "executionStrategy": "CANCEL_REPLACE",
5 "order": {
6 "orderId": "ord_123",
7 "status": "PENDING",
8 "legs": []
9 },
10 "clientOrderId": "partner-order-123",
11 "supersededOrderId": null,
12 "message": null
13}
14executionStrategy describes how Finatic handled the command:
| Value | Meaning |
|---|---|
CANCEL_REPLACE | The provider contract requires cancel plus replacement, for example Robinhood |
NATIVE_MODIFY | The provider supports same-ID modification, for example TradeStation or eToro position SL/TP |
ASYNC_COMMAND | MT4/MT5 queues the command to the customer's EA and waits for an acknowledgement |
For ASYNC_COMMAND, the embedded order can receive the EA ticket as brokerOrderId after acknowledgement. Handle MT_COMMAND_TIMEOUT and MT_COMMAND_REJECTED explicitly; accepted: true by itself is not final execution proof.
Order field schemas
Fetch the provider schema at runtime instead of hard-coding a matrix:
1const schema = await finatic.v1.getAccountOrderSchema({
2 accountId: 'account_123',
3 action: 'place',
4});
5Supported actions are place, modify, and cancel. The response includes supportedOrderTypesByAssetClass and provider-specific field requirements.
1{
2 "supportedOrderTypesByAssetClass": {
3 "equity": ["market", "limit", "stop", "stop_limit", "trailing_stop"],
4 "option": ["market", "limit"]
5 }
6}
7Event Contract eligibility
Use the generated Client SDK contract for Event Contracts. Do not reuse an equity enum, infer support from a provider-wide trading flag, or treat catalog visibility as permission to trade.
1import {
2 FDXAssetType,
3 type AccountOrderSchemaResponse,
4 type FinaticAPIErrorResponse,
5} from '@finatic/client';
6
7const response = await finatic.v1.getAccountOrderSchema<AccountOrderSchemaResponse>({
8 accountId,
9 action: 'place',
10});
11const capability = response.success?.data.eventContract;
12const canOpen =
13 capability?.publicEnabled === true &&
14 capability.accountEntitled === true &&
15 capability.closingOnly !== true;
16
17if (canOpen) {
18 await finatic.v1.createAccountOrder({
19 accountId,
20 idempotencyKey: crypto.randomUUID(),
21 body: {
22 order: {
23 assetType: FDXAssetType.EventContract,
24 symbol: selectedEventContractSymbol,
25 },
26 },
27 });
28}
29Treat a missing capability, disabled public access, missing account entitlement, or closingOnly: true as a hard block on opening orders. A closing-only account can close an existing position only when the returned account schema permits that exact action.
For a FinaticAPIErrorResponse, show the attempted operation, stable error code, and safe trace or correlation reference. Never render raw provider bodies, stack traces, tokens, credentials, or account identifiers.
Provider restrictions
| Broker | Entry order types | Modify behavior |
|---|---|---|
| Alpaca, Webull, Tastytrade, NinjaTrader, Trading 212 | Use supportedOrderTypesByAssetClass | Use the returned provider capability and schema; do not infer behavior from transport type |
| Robinhood | Use supportedOrderTypesByAssetClass | Cancel-replace (CANCEL_REPLACE) |
| TradeStation | Use supportedOrderTypesByAssetClass | Native same-ID modify (NATIVE_MODIFY) |
| eToro | market and limit by asset class | Position SL/TP uses modifyTarget: position_sl_tp; other modifies use cancel-replace |
| MT4 / MT5 | market, limit, and stop only | Async EA commands (ASYNC_COMMAND); a registered connector is required |
For eToro, fetch the place schema because payloads use instrumentId and amount or units instead of a universal symbol and orderQty pair. MT commands can wait up to 90 seconds for a ticketed acknowledgement.
Place an order
1const result = await finatic.v1.createAccountOrder({
2 accountId: 'account_123',
3 idempotencyKey: 'partner-order-123',
4 body: {
5 order: {
6 orderType: 'market',
7 assetType: 'equity',
8 action: 'buy',
9 timeInForce: 'day',
10 symbol: 'AAPL',
11 orderQty: 1,
12 },
13 },
14});
15
16if (!result.success?.data?.accepted) {
17 console.error(result.error, result._id);
18}Select an exact instrument
When a read returns instrument.finaticInstrumentId, send that opaque value as finaticInstrumentId inside the provider order payload. instrumentId remains the provider-native identifier for providers that require it. If both are sent, they must resolve to the same instrument within the authorized account and provider.
Never derive a canonical ID from a product root such as MGC, parse MGCZ6, or substitute another contract month. Fetch the descriptor through the same authorized account/provider path used for placement.
Modify an order
Pass only changed fields inside order and use a new idempotency key for this distinct command.
1const result = await finatic.v1.modifyAccountOrder({
2 accountId: 'account_123',
3 orderId: 'order_123',
4 idempotencyKey: 'partner-order-123-modify-1',
5 body: {
6 order: {
7 orderQty: 2,
8 limitPrice: 185.25,
9 },
10 },
11});For eToro position stop-loss or take-profit updates, include modifyTarget: position_sl_tp and positionId. Native modifies return NATIVE_MODIFY; cancel-replace can return supersededOrderId.
Cancel an order
1const result = await finatic.v1.cancelAccountOrder({
2 accountId: 'account_123',
3 orderId: 'order_123',
4 idempotencyKey: 'partner-order-123-cancel-1',
5});Track the order
1const orders = await finatic.v1.listOrders({
2 accountId: 'account_123',
3 limit: 50,
4});
5
6const fills = await finatic.v1.getAccountOrderFills({
7 accountId: 'account_123',
8 orderId: 'order_123',
9});
10Error handling
- Missing trading permission: The grant does not have
canTrade: true. - Idempotency conflict: Reuse a key only for the exact same command; preserve it while reconciling a timeout.
- Broker rejection: Use stable error details without exposing raw provider bodies.
- Market restriction: The market, instrument, or asset is not currently tradable.
Log traceId with your own order ID and idempotency key. Never blindly retry a write with a new key.
Next steps
- Getting Data — read orders, fills, and events.
- Webhooks — subscribe to order lifecycle events.
- Error Handling — handle terminal and retryable failures.