Internal Transfer (Perp ↔ Spot)
Description
Atomically moves 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 in a single database transaction with FOR UPDATE row locks. Either both balances move or neither does; concurrent transfers from the same user serialize.
MVP supports only USDT.
HTTP Request
POST /spot/transfer (JWT only)
API-key callers receive 403 API Key permission denied. See General Info → Authentication.
Weight
0 — no per-IP weight limit today (MVP).
Request Parameters
| Name | Type | Required | Description |
|---|---|---|---|
token | STRING | YES | Token to transfer. MVP: USDT only. |
direction | ENUM | YES | perp_to_spot moves USDT from perp margin into the spot wallet. spot_to_perp is the reverse. |
amount | DECIMAL | YES | Decimal amount as a string (e.g. "100"). Must be positive. |
Response Example
200 OK
{
"direction": "perp_to_spot",
"token": "USDT",
"amount": "100",
"perp_balance_after": "4900",
"spot_balance_after": "5100"
}
| Field | Notes |
|---|---|
direction | Echoes the request field. |
token | Echoes the request field. |
amount | Decimal string of the transferred amount, trailing zeros stripped. |
perp_balance_after | Perp available balance after the transfer (decimal string). |
spot_balance_after | Spot available balance after the transfer (decimal string). |
Error Responses
| HTTP | error |
|---|---|
400 | INVALID_DIRECTION — direction is not perp_to_spot or spot_to_perp. |
400 | AMOUNT_NON_POSITIVE — amount ≤ 0. |
400 | INSUFFICIENT_BALANCE — source side does not have amount available. |
400 | UNSUPPORTED_TOKEN — token other than USDT requested. |
403 | API Key permission denied — caller authenticated via API Key. |
500 | internal error — database error; see server logs. |
Full list: Error Codes.
Code Examples
cURL (JWT)
JWT="your_jwt_token"
curl -s -X POST "https://api-sepolia.p99.world/api/v1/spot/transfer" \
-H "Authorization: Bearer ${JWT}" \
-H "Content-Type: application/json" \
-d '{
"token": "USDT",
"direction": "perp_to_spot",
"amount": "100"
}'
Python
import requests
BASE_URL = "https://api-sepolia.p99.world/api/v1"
JWT = "your_jwt_token"
resp = requests.post(
f"{BASE_URL}/spot/transfer",
headers={
"Authorization": f"Bearer {JWT}",
"Content-Type": "application/json",
},
json={
"token": "USDT",
"direction": "perp_to_spot",
"amount": "100",
},
timeout=5,
)
resp.raise_for_status()
data = resp.json()
print(
f"transferred {data['amount']} {data['token']} "
f"perp_after={data['perp_balance_after']} "
f"spot_after={data['spot_balance_after']}"
)