curl --request POST \
--url https://external-api.kalshi.com/trade-api/v2/margin/fcm/positions/transfers \
--header 'Content-Type: application/json' \
--header 'KALSHI-ACCESS-KEY: <api-key>' \
--header 'KALSHI-ACCESS-SIGNATURE: <api-key>' \
--header 'KALSHI-ACCESS-TIMESTAMP: <api-key>' \
--data '
{
"source_subtrader_id": "3f2a8c1e-5b7d-4e9a-8c6f-1d2b3a4e5f60_CUST123",
"target_subtrader_id": "9b8c7d6e-5f4a-4b3c-9d2e-1f0a9b8c7d6e_ERR1",
"ticker": "KXBTCPERP",
"side": "long",
"count": "10",
"price": "63000.00",
"client_transfer_id": "6d1f0c5a-2b3e-4f7a-9c8d-0e1f2a3b4c5d"
}
'{
"position_transfer_id": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}Create Margin FCM Position Transfer
Endpoint for FCM members to move a perps position in one market from one of their subtraders into a subtrader under their FCM’s error account.
This endpoint must be enabled for your FCM by Kalshi. Kalshi enables it by configuring the
error account that receives transferred positions and gives you that account’s user id, which
prefixes target_subtrader_id. Contact Kalshi to enable it; until then, requests return 400.
Position transfers are also switched on per exchange; while they are off, requests return 409.
- The destination is always your FCM’s error account.
target_subtrader_idpicks the subtrader under it, which is created on first use. - Each request moves one market for one source subtrader. To move many positions, send one request per subtrader and market.
- No cash moves when a transfer is booked. Both sides’ positions and cost basis change as for a
fee-free trade at
price: the source closes atpriceand the error account opens atprice. Both settle in the next settlement cycle: the source realizes the difference betweenpriceand the market’s last settlement mark, and the error account’s variation margin runs frompriceto the next settlement mark. To make a customer whole, use the price of the fill being reversed. sideandcountdescribe the position being moved:longmoves a long ofcountcontracts,shortmoves a short.countmust be a whole number of contracts. The source’s current position is not checked; moving more than it holds, or the other side, flips or grows its position.client_transfer_idmakes the request idempotent per source subtrader: once a transfer with that id has been booked fromsource_subtrader_id, a later request with the same id from that subtrader, to any target, books nothing unless the exchange has restarted in between. The exchange checks the id only after it re-checks the transfer switch, trading status and both accounts’ current risk, so a repeat returns 409 with error codetransfer_already_appliedonly when it passes those checks. Otherwise it gets the error for the check it failed, such as 400insufficient_balanceonce the earlier transfer has used the error account’s margin, or 409 while trading is paused.- After a timeout or 5xx, retry with the same id.
transfer_already_appliedconfirms that the earlier attempt was booked. Any other error refuses only the retry and leaves the earlier attempt’s outcome unknown: check the source subtrader’s position before you send the transfer again under a new id. - The exchange does not keep transfer ids across a restart, such as a maintenance window, so a retry sent after one can book the transfer a second time. If the exchange may have restarted since the first attempt, check the source subtrader’s position before retrying, even with the same id.
- The market must have traded at least once and trading must not be paused; otherwise the
request returns 409. Both accounts must also pass the exchange’s risk checks for their
post-transfer positions (available margin, FCM initial-margin caps, notional risk limits);
a failed risk check returns 400 with the exchange’s reason, such as
insufficient_balance, and a blocked account returns 409. - Transfers appear on the daily statement and in the
position_transfer_auditreport. They never appear as fills or intrade_audit.
Example. A customer subtrader holds 10 long in KXBTCPERP, filled at 63,000.Send‘side‘‘long‘,‘count‘‘10‘,‘price‘‘63000.00‘:thecustomergoesfrom10longtoflatandtheerroraccountsubtraderfromflatto10long,bothat63,000, with no cash moving.
curl --request POST \
--url https://external-api.kalshi.com/trade-api/v2/margin/fcm/positions/transfers \
--header 'Content-Type: application/json' \
--header 'KALSHI-ACCESS-KEY: <api-key>' \
--header 'KALSHI-ACCESS-SIGNATURE: <api-key>' \
--header 'KALSHI-ACCESS-TIMESTAMP: <api-key>' \
--data '
{
"source_subtrader_id": "3f2a8c1e-5b7d-4e9a-8c6f-1d2b3a4e5f60_CUST123",
"target_subtrader_id": "9b8c7d6e-5f4a-4b3c-9d2e-1f0a9b8c7d6e_ERR1",
"ticker": "KXBTCPERP",
"side": "long",
"count": "10",
"price": "63000.00",
"client_transfer_id": "6d1f0c5a-2b3e-4f7a-9c8d-0e1f2a3b4c5d"
}
'{
"position_transfer_id": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}{
"code": "<string>",
"message": "<string>",
"details": "<string>"
}Authorizations
Your API key ID
Base64 signature of the pre-sign text (timestamp + method + path) made with the API key's algorithm - RSA-PSS with SHA-256 for RSA keys, Ed25519 for Ed25519 keys
Request timestamp in milliseconds
Body
Full id of the FCM subtrader giving up the position, {user_id}_{subtrader_suffix}, as returned by POST /margin/fcm/subtraders.
Subtrader under your error account that receives the position, {error_account_id}_{suffix} with a 1-16 character alphanumeric suffix. Created if it does not exist.
Side of the position being moved. long moves a long position, short moves a short one.
long, short Contracts to move, as a whole number of exchange contracts (e.g. "10").
"10.00"
Transfer price in dollars per contract with 2-4 decimal places (e.g. "63000.00"), greater than zero. Both sides' cost basis moves at this price.
"0.5600"
Client-chosen UUID that makes the request idempotent for the source subtrader. A repeat from the same source, to any target, returns 409 with code transfer_already_applied.
Market ticker. Supply ticker or market_id; ticker takes precedence when both are provided.
Stable exchange market UUID. May be supplied instead of ticker.
Response
Position transferred
Id of the executed transfer, as it appears in the position_transfer_audit report.