Skip to main content

Internal Transfer (Perp ↔ Spot)

Atomic move of USDT between the user's perp margin account and the spot wallet. No on-chain transaction is involved — both sides are maintained by the backend.

Authentication Required

JWT only. API-key callers receive 403 API Key permission denied.

Authorization: Bearer <JWT>
POST /spot/transfer

Request Body

{
"direction": "perp_to_spot",
"token": "USDT",
"amount": "100"
}
FieldTypeRequiredDescription
directionstringYesperp_to_spot or spot_to_perp.
tokenstringYesMVP only supports USDT.
amountstring / numberYesDecimal amount (e.g. "100" = 100 USDT). Must be positive.

Response — 200 OK

{
"direction": "perp_to_spot",
"token": "USDT",
"amount": "100",
"perp_balance_after": "4900",
"spot_balance_after": "5100"
}
FieldDescription
perp_balance_afterPerp available balance after the transfer.
spot_balance_afterSpot available balance after the transfer.

Atomicity

The transfer happens inside a single DB transaction with FOR UPDATE row locks on both sides. Either both balances move or neither does — concurrent transfers from the same user serialize.

Error Responses

HTTPerrorCause
400invalid direction: <value>direction not one of the two allowed values.
400unsupported token: <symbol>Token other than USDT requested.
400amount must be positiveamount ≤ 0.
400insufficient balanceThe source side does not have amount available.
403API Key permission denied: transfers not allowedCaller authenticated via API Key.
500internal errorDatabase error — see server logs.

Code Example

import requests

BASE = "https://api-sepolia.p99.world/api/v1"
JWT = "your_jwt_token"

resp = requests.post(
f"{BASE}/spot/transfer",
headers={"Authorization": f"Bearer {JWT}", "Content-Type": "application/json"},
json={"direction": "perp_to_spot", "token": "USDT", "amount": "100"},
)
print(resp.status_code, resp.json())