Skip to main content

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

NameTypeRequiredDescription
tokenSTRINGYESToken to transfer. MVP: USDT only.
directionENUMYESperp_to_spot moves USDT from perp margin into the spot wallet. spot_to_perp is the reverse.
amountDECIMALYESDecimal 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"
}
FieldNotes
directionEchoes the request field.
tokenEchoes the request field.
amountDecimal string of the transferred amount, trailing zeros stripped.
perp_balance_afterPerp available balance after the transfer (decimal string).
spot_balance_afterSpot available balance after the transfer (decimal string).

Error Responses

HTTPerror
400INVALID_DIRECTIONdirection is not perp_to_spot or spot_to_perp.
400AMOUNT_NON_POSITIVEamount ≤ 0.
400INSUFFICIENT_BALANCE — source side does not have amount available.
400UNSUPPORTED_TOKEN — token other than USDT requested.
403API Key permission denied — caller authenticated via API Key.
500internal 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']}"
)