Endpoints
Five operations, all bound to the single prop account your key was issued against. Every request must be signed. See Authentication.
Conventions
These hold for every endpoint below.
The base URL is https://app.vantatrading.io. Successful responses are HTTP 200 with the envelope { "success": true, "data": … }. Failures return a 4xx or 5xx status and an error object:
{
"error": "This API key is only valid for a different account"
}Read endpoints accept an optional ?accountId= query parameter. It is never required, because the key already identifies the account. If it is supplied it must match the bound account, or the request is rejected with 403. Remember that the query string is part of the signed path, so adding it changes the signature.
Index
Jump to an endpoint.
/api/v1/trading/accountAccount snapshot
Consolidated account summary, current equity, status and evaluation, challenge progress, drawdown and performance. These are the same values shown on the dashboard.
Required scope trade:read
- meta.stale is true when the upstream validator was unavailable and values fell back to placeholders. Retry when you see it.
- challenge.drawdownCriteria is "trailing" (legacy HWM rules) or "static" (Rule 1 measured against the starting account balance, Rule 2 against start-of-day equity). For static accounts, challenge.drawdownBreakdown.highWaterMark carries the starting balance and the breakdown values reflect Rule 1, the 5% Static Loss Limit (equity vs the starting balance).
- proTrack, drawdownRules, elimination, isPro and tradingDays are additive fields (v1.2.0). Every field that existed before is unchanged.
- drawdownRules describes both loss limits by rule. drawdownRules.criteria is the rule set the account is on: "trailing", "static" or "pro". dailyLoss is always the daily loss limit: equity against start-of-day equity (dayStartEquity). maxLoss is the account's other limit, named by maxLoss.rule: "eod_trailing" (trailing and pro accounts: end-of-day equity against the end-of-day high-water mark, checked once a day at 00:00 UTC, so an intraday dip that recovers before then does not breach it) or "static" (static accounts: live equity against the starting balance). anchorEquity is what the limit is measured against and measuredEquity is the equity it is compared with, both in USD. usedPct and limitPct are percents. currentBalance is the balance in USD. drawdownRules is null when the validator sent no drawdown data.
- elimination is null unless the account is eliminated. elimination.reason is the network's reason code (or null when none was recorded), elimination.atMs is when the network eliminated the account (Unix ms, or null when unknown), elimination.drawdownPct is the drawdown at elimination as a percent (5.2 means 5.2%, or null when none was recorded) and forInactivity is true for the no-trade inactivity rule.
- proTrack and tradingDays are null for accounts that are not on the pro track. tradingDays is the number of full trading days the network has tracked.
- proTrack.phase is "transition" (the standard account winding down until the network starts the pro account at transitionEndsAtMs, the next Monday 00:00 UTC in Unix ms), "challenge" or "funded". transitionEndsAtMs is null outside the transition phase. standardAccountSize and proAccountSize are USD, or null when not reported. hasStats is false when the network sent no pro statistics on this read. evaluated turns true once the network has tracked the first full trading day.
- proTrack.objectives lists each promotion objective with key, label, value, threshold, higherIsBetter and met. The "return" value is the account return as a ratio (0.06 means 6%), "calmar" is the Calmar ratio, "consistency" is the best day's share of the total return as a ratio (0.2 means 20%; lower is better) and "days" is a count of full trading days. met is null while either the value or the threshold is unknown. The calmar and consistency values (and so their met) are also null until evaluated is true, because the network seeds them with placeholders before the first full trading day; return and days report met from day one.
- proTrack.maxDrawdownPercent is the all-time maximum drawdown as a percent (2.4 means 2.4%), unlike the objective ratios. softBreachApplies is true only for funded pro accounts, where a week with calmar or consistency below target has its reward held. softBreach is true while that is happening this week.
Example request
# Replace with your key and secret (secret is shown only when you create the key).
# The API key is bound to a single prop account, so no accountId is required.
export VANTA_KEY_ID="YOUR_KEY_ID"
export VANTA_SECRET="YOUR_SECRET"
export BASE_URL="https://app.vantatrading.io"
PATH_REQ="/api/v1/trading/account"
TS=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
# GET has an empty body, so this is the sha256 of the empty string
BODY_HASH=$(printf '' | openssl dgst -sha256 -binary | xxd -p -c 256)
CANONICAL=$(printf 'v1\nGET\n%s\n%s\n%s\n%s' "$PATH_REQ" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$CANONICAL" | openssl dgst -sha256 -hmac "$VANTA_SECRET" -binary | base64 | tr -d '\n')
curl -s "$BASE_URL$PATH_REQ" \
-H "X-Vanta-Key-Id: $VANTA_KEY_ID" \
-H "X-Vanta-Timestamp: $TS" \
-H "X-Vanta-Nonce: $NONCE" \
-H "X-Vanta-Signature: v1=$SIG"# pip install requests
import base64
import hashlib
import hmac
import os
import secrets
import time
import requests
# The secret is shown once, when the key is created. Keep it out of source
# control. Read it from the environment or a secret manager.
KEY_ID = os.environ["VANTA_KEY_ID"]
SECRET = os.environ["VANTA_SECRET"]
BASE_URL = "https://app.vantatrading.io"
def signed_headers(method: str, path: str, body: str = "") -> dict:
"""Build the four auth headers for one request.
`path` must be the request target exactly as it is sent: the pathname plus
the query string, if any. Signing a different string than you request is
the most common cause of a 401.
"""
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
canonical = "\n".join(
[
"v1",
method.upper(),
path,
timestamp,
nonce,
hashlib.sha256(body.encode("utf-8")).hexdigest(),
]
)
signature = base64.b64encode(
hmac.new(
SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).digest()
).decode("ascii")
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": f"v1={signature}",
}
path = "/api/v1/trading/account"
response = requests.get(
BASE_URL + path, headers=signed_headers("GET", path), timeout=30
)
print(response.status_code, response.json())// Node 18 or newer. No dependencies: `fetch` and `node:crypto` are built in.
import { createHash, createHmac, randomBytes } from "node:crypto";
// The secret is shown once, when the key is created. Keep it out of source
// control. Read it from the environment or a secret manager.
const KEY_ID = process.env.VANTA_KEY_ID;
const SECRET = process.env.VANTA_SECRET;
const BASE_URL = "https://app.vantatrading.io";
/**
* Build the four auth headers for one request. `path` must be the request
* target exactly as it is sent: pathname plus query string, if any.
*/
function signedHeaders(method, path, body = "") {
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const canonical = [
"v1",
method.toUpperCase(),
path,
timestamp,
nonce,
createHash("sha256").update(body, "utf8").digest("hex"),
].join("\n");
const signature = createHmac("sha256", SECRET)
.update(canonical, "utf8")
.digest("base64");
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": `v1=${signature}`,
};
}
const path = "/api/v1/trading/account";
const response = await fetch(`${BASE_URL}${path}`, {
headers: signedHeaders("GET", path),
});
console.log(response.status, await response.json());Example response
{
"success": true,
"data": {
"accountId": "6f1c2e34-9a4b-4c1d-8e2f-1a2b3c4d5e6f",
"assetClass": "crypto",
"marketName": "Crypto",
"accountSize": 25000,
"formattedAccountSize": "25K",
"evaluation": {
"title": "Crypto 25K Evaluation",
"status": "evaluation",
"isEliminated": false,
"accountSize": "$25,000",
"activeAccounts": "1/1",
"effectiveAccountSizeNumeric": 25000
},
"account": {
"currentBalance": 25120.5,
"currentEquity": 25180.25,
"balanceChange": 120.5,
"balanceChangePercent": 0.48,
"totalPnL": 180.25,
"totalPnLPercent": 0.72,
"openPnL": 59.75,
"openPnLPercent": 0.24,
"openPositions": 1,
"portfolioBalance": 25120.5,
"portfolioBalanceChangePercent": 0.48,
"portfolioBalanceBreakdown": {
"currentBalance": 25120.5,
"marginLeverage": 5,
"sumPositionValue": 7202.5
},
"leverage": "Current - 0.2870x / Max - 10x",
"capitalUsed": 7202.5,
"totalRealizedPnl": 120.5,
"isPassed": false
},
"challenge": {
"variant": "default",
"bucket": "SUBACCOUNT_CHALLENGE",
"drawdownCriteria": "trailing",
"profitTarget": 2500,
"profitTargetPercent": 10,
"remaining": 2319.75,
"maxLeverage": "10x",
"trailingDrawdownPercent": 5,
"maxDrawdown": -1250,
"daysRemaining": 27,
"totalDays": 90,
"drawdownBreakdown": {
"highWaterMark": 25180.25,
"allowedDrawdown": 1259.01,
"currentDrawdown": 0,
"remainingDrawdown": 1259.01,
"remainingDrawdownPercentHWM": 5
}
},
"performance": {
"totalTrades": 4,
"winRate": 75,
"totalWins": 3,
"avgTradePnL": 45.06,
"tradeDuration": "63h 12m",
"challengeStartMs": 1717200000000,
"dailyReturns": [
{
"date": "2026-06-28",
"value": 0.31
},
{
"date": "2026-06-29",
"value": 0.17
}
]
},
"meta": {
"generatedAtMs": 1717718400000,
"stale": false
},
"proTrack": null,
"drawdownRules": {
"criteria": "trailing",
"dailyLoss": {
"usedPct": 0.16,
"limitPct": 5,
"dayStartEquity": 25221.1
},
"maxLoss": {
"rule": "eod_trailing",
"usedPct": 0.16,
"limitPct": 5,
"anchorEquity": 25261.4,
"measuredEquity": 25221.1
},
"currentBalance": 25120.5
},
"elimination": null,
"isPro": false,
"tradingDays": null
}
}/api/v1/trading/positionsOpen positions
Open positions in the current challenge bucket, including entry price, leverage, unrealized PnL and any attached TP/SL.
Required scope trade:read
- netQuantity is the position size in the pair's lot unit, the same unit an order's quantity is submitted in: base units for crypto, equities, indices and Hyperliquid commodities; lots of 100,000 units for forex; 100 oz for XAU/USD; 5,000 oz for XAG/USD. It is signed like netLeverage, so a SHORT reports a negative number.
- netQuantity is null only when the validator did not publish it and it cannot be derived from the position's fills. Treat null as unknown, never as zero.
- unrealizedPnl is the validator's live unrealized PnL in USD.
Example request
# Replace with your key and secret (secret is shown only when you create the key).
# The API key is bound to a single prop account, so no accountId is required.
export VANTA_KEY_ID="YOUR_KEY_ID"
export VANTA_SECRET="YOUR_SECRET"
export BASE_URL="https://app.vantatrading.io"
PATH_REQ="/api/v1/trading/positions"
TS=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
# GET has an empty body, so this is the sha256 of the empty string
BODY_HASH=$(printf '' | openssl dgst -sha256 -binary | xxd -p -c 256)
CANONICAL=$(printf 'v1\nGET\n%s\n%s\n%s\n%s' "$PATH_REQ" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$CANONICAL" | openssl dgst -sha256 -hmac "$VANTA_SECRET" -binary | base64 | tr -d '\n')
curl -s "$BASE_URL$PATH_REQ" \
-H "X-Vanta-Key-Id: $VANTA_KEY_ID" \
-H "X-Vanta-Timestamp: $TS" \
-H "X-Vanta-Nonce: $NONCE" \
-H "X-Vanta-Signature: v1=$SIG"# pip install requests
import base64
import hashlib
import hmac
import os
import secrets
import time
import requests
# The secret is shown once, when the key is created. Keep it out of source
# control. Read it from the environment or a secret manager.
KEY_ID = os.environ["VANTA_KEY_ID"]
SECRET = os.environ["VANTA_SECRET"]
BASE_URL = "https://app.vantatrading.io"
def signed_headers(method: str, path: str, body: str = "") -> dict:
"""Build the four auth headers for one request.
`path` must be the request target exactly as it is sent: the pathname plus
the query string, if any. Signing a different string than you request is
the most common cause of a 401.
"""
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
canonical = "\n".join(
[
"v1",
method.upper(),
path,
timestamp,
nonce,
hashlib.sha256(body.encode("utf-8")).hexdigest(),
]
)
signature = base64.b64encode(
hmac.new(
SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).digest()
).decode("ascii")
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": f"v1={signature}",
}
path = "/api/v1/trading/positions"
response = requests.get(
BASE_URL + path, headers=signed_headers("GET", path), timeout=30
)
print(response.status_code, response.json())// Node 18 or newer. No dependencies: `fetch` and `node:crypto` are built in.
import { createHash, createHmac, randomBytes } from "node:crypto";
// The secret is shown once, when the key is created. Keep it out of source
// control. Read it from the environment or a secret manager.
const KEY_ID = process.env.VANTA_KEY_ID;
const SECRET = process.env.VANTA_SECRET;
const BASE_URL = "https://app.vantatrading.io";
/**
* Build the four auth headers for one request. `path` must be the request
* target exactly as it is sent: pathname plus query string, if any.
*/
function signedHeaders(method, path, body = "") {
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const canonical = [
"v1",
method.toUpperCase(),
path,
timestamp,
nonce,
createHash("sha256").update(body, "utf8").digest("hex"),
].join("\n");
const signature = createHmac("sha256", SECRET)
.update(canonical, "utf8")
.digest("base64");
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": `v1=${signature}`,
};
}
const path = "/api/v1/trading/positions";
const response = await fetch(`${BASE_URL}${path}`, {
headers: signedHeaders("GET", path),
});
console.log(response.status, await response.json());Example response
{
"success": true,
"data": [
{
"positionUuid": "0f9d1a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b",
"tradePair": "BTCUSDC",
"tradePairDisplay": "BTC/USDC",
"positionType": "LONG",
"netLeverage": 0.287,
"netQuantity": 0.11533,
"averageEntryPrice": 62450.12,
"currentReturn": 0.0024,
"openMs": 1717490000000,
"unrealizedPnl": 59.75,
"realizedPnl": 0,
"netValue": 7262.25,
"cumulativeEntryValue": 7202.5,
"stopLoss": 61000,
"takeProfit": 65000
}
]
}/api/v1/trading/ordersPending orders
Unfilled limit orders and per-position bracket legs (TP/SL) that will execute automatically.
Required scope trade:read
Example request
# Replace with your key and secret (secret is shown only when you create the key).
# The API key is bound to a single prop account, so no accountId is required.
export VANTA_KEY_ID="YOUR_KEY_ID"
export VANTA_SECRET="YOUR_SECRET"
export BASE_URL="https://app.vantatrading.io"
PATH_REQ="/api/v1/trading/orders"
TS=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
# GET has an empty body, so this is the sha256 of the empty string
BODY_HASH=$(printf '' | openssl dgst -sha256 -binary | xxd -p -c 256)
CANONICAL=$(printf 'v1\nGET\n%s\n%s\n%s\n%s' "$PATH_REQ" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$CANONICAL" | openssl dgst -sha256 -hmac "$VANTA_SECRET" -binary | base64 | tr -d '\n')
curl -s "$BASE_URL$PATH_REQ" \
-H "X-Vanta-Key-Id: $VANTA_KEY_ID" \
-H "X-Vanta-Timestamp: $TS" \
-H "X-Vanta-Nonce: $NONCE" \
-H "X-Vanta-Signature: v1=$SIG"# pip install requests
import base64
import hashlib
import hmac
import os
import secrets
import time
import requests
# The secret is shown once, when the key is created. Keep it out of source
# control. Read it from the environment or a secret manager.
KEY_ID = os.environ["VANTA_KEY_ID"]
SECRET = os.environ["VANTA_SECRET"]
BASE_URL = "https://app.vantatrading.io"
def signed_headers(method: str, path: str, body: str = "") -> dict:
"""Build the four auth headers for one request.
`path` must be the request target exactly as it is sent: the pathname plus
the query string, if any. Signing a different string than you request is
the most common cause of a 401.
"""
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
canonical = "\n".join(
[
"v1",
method.upper(),
path,
timestamp,
nonce,
hashlib.sha256(body.encode("utf-8")).hexdigest(),
]
)
signature = base64.b64encode(
hmac.new(
SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).digest()
).decode("ascii")
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": f"v1={signature}",
}
path = "/api/v1/trading/orders"
response = requests.get(
BASE_URL + path, headers=signed_headers("GET", path), timeout=30
)
print(response.status_code, response.json())// Node 18 or newer. No dependencies: `fetch` and `node:crypto` are built in.
import { createHash, createHmac, randomBytes } from "node:crypto";
// The secret is shown once, when the key is created. Keep it out of source
// control. Read it from the environment or a secret manager.
const KEY_ID = process.env.VANTA_KEY_ID;
const SECRET = process.env.VANTA_SECRET;
const BASE_URL = "https://app.vantatrading.io";
/**
* Build the four auth headers for one request. `path` must be the request
* target exactly as it is sent: pathname plus query string, if any.
*/
function signedHeaders(method, path, body = "") {
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const canonical = [
"v1",
method.toUpperCase(),
path,
timestamp,
nonce,
createHash("sha256").update(body, "utf8").digest("hex"),
].join("\n");
const signature = createHmac("sha256", SECRET)
.update(canonical, "utf8")
.digest("base64");
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": `v1=${signature}`,
};
}
const path = "/api/v1/trading/orders";
const response = await fetch(`${BASE_URL}${path}`, {
headers: signedHeaders("GET", path),
});
console.log(response.status, await response.json());Example response
{
"success": true,
"data": [
{
"orderUuid": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"tradePair": "ETHUSDC",
"tradePairDisplay": "ETH/USDC",
"orderType": "LONG",
"executionType": "LIMIT",
"processedMs": 1717500000000,
"limitPrice": 3200,
"leverage": 1,
"value": 1500,
"quantity": null,
"stopLoss": 3100,
"takeProfit": 3500,
"bracketPct": null,
"trailingPercent": null,
"trailingValue": null
}
]
}/api/v1/trading/tradesTrade history
Closed/filled trades with entry & close price, realized PnL, return at close and fees.
Required scope trade:read
- quantity is the total size opened over the life of the trade (every fill in the trade's direction, adds included), in the same lot unit and with the same sign convention as netQuantity on GET /api/v1/trading/positions. It is null when a fill predates quantity tracking on the network. positionSize is a display string and may be an estimate; reconcile against quantity.
Example request
# Replace with your key and secret (secret is shown only when you create the key).
# The API key is bound to a single prop account, so no accountId is required.
export VANTA_KEY_ID="YOUR_KEY_ID"
export VANTA_SECRET="YOUR_SECRET"
export BASE_URL="https://app.vantatrading.io"
PATH_REQ="/api/v1/trading/trades"
TS=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
# GET has an empty body, so this is the sha256 of the empty string
BODY_HASH=$(printf '' | openssl dgst -sha256 -binary | xxd -p -c 256)
CANONICAL=$(printf 'v1\nGET\n%s\n%s\n%s\n%s' "$PATH_REQ" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$CANONICAL" | openssl dgst -sha256 -hmac "$VANTA_SECRET" -binary | base64 | tr -d '\n')
curl -s "$BASE_URL$PATH_REQ" \
-H "X-Vanta-Key-Id: $VANTA_KEY_ID" \
-H "X-Vanta-Timestamp: $TS" \
-H "X-Vanta-Nonce: $NONCE" \
-H "X-Vanta-Signature: v1=$SIG"# pip install requests
import base64
import hashlib
import hmac
import os
import secrets
import time
import requests
# The secret is shown once, when the key is created. Keep it out of source
# control. Read it from the environment or a secret manager.
KEY_ID = os.environ["VANTA_KEY_ID"]
SECRET = os.environ["VANTA_SECRET"]
BASE_URL = "https://app.vantatrading.io"
def signed_headers(method: str, path: str, body: str = "") -> dict:
"""Build the four auth headers for one request.
`path` must be the request target exactly as it is sent: the pathname plus
the query string, if any. Signing a different string than you request is
the most common cause of a 401.
"""
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
canonical = "\n".join(
[
"v1",
method.upper(),
path,
timestamp,
nonce,
hashlib.sha256(body.encode("utf-8")).hexdigest(),
]
)
signature = base64.b64encode(
hmac.new(
SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).digest()
).decode("ascii")
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": f"v1={signature}",
}
path = "/api/v1/trading/trades"
response = requests.get(
BASE_URL + path, headers=signed_headers("GET", path), timeout=30
)
print(response.status_code, response.json())// Node 18 or newer. No dependencies: `fetch` and `node:crypto` are built in.
import { createHash, createHmac, randomBytes } from "node:crypto";
// The secret is shown once, when the key is created. Keep it out of source
// control. Read it from the environment or a secret manager.
const KEY_ID = process.env.VANTA_KEY_ID;
const SECRET = process.env.VANTA_SECRET;
const BASE_URL = "https://app.vantatrading.io";
/**
* Build the four auth headers for one request. `path` must be the request
* target exactly as it is sent: pathname plus query string, if any.
*/
function signedHeaders(method, path, body = "") {
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const canonical = [
"v1",
method.toUpperCase(),
path,
timestamp,
nonce,
createHash("sha256").update(body, "utf8").digest("hex"),
].join("\n");
const signature = createHmac("sha256", SECRET)
.update(canonical, "utf8")
.digest("base64");
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": `v1=${signature}`,
};
}
const path = "/api/v1/trading/trades";
const response = await fetch(`${BASE_URL}${path}`, {
headers: signedHeaders("GET", path),
});
console.log(response.status, await response.json());Example response
{
"success": true,
"data": [
{
"id": "c34d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"tradePair": "BTCUSDC",
"tradePairDisplay": "BTC/USDC",
"positionType": "LONG",
"leverage": "0.50x",
"positionSize": "$1,000.00",
"quantity": 0.01663,
"entryPrice": "$60,120.00",
"closePrice": "$61,540.00",
"unrealizedPnl": 0,
"realizedPnl": 23.62,
"returnAtClose": 0.0236,
"status": "Filled",
"openTimeMs": 1717200000000,
"closeTimeMs": 1717230000000,
"stopLoss": 59000,
"takeProfit": 62000,
"totalFees": 1.18
}
]
}/api/v1/trading/payoutsPayouts
Payout history for the account, newest first, plus the live estimate for the cycle in progress. items are the same payout records behind the dashboard's rewards history: amount is your share actually paid or payable after the profit split — the same figure as the Rewards page's Reward column — and weeks with no payout record (the page's $0 or held weeks) are not listed.
Required scope trade:read
- The query string is part of the signed path: sign /api/v1/trading/payouts?limit=100&cursor=... exactly as you send it.
- Amounts are USD. amount is the trader share paid (or payable) for the row after the profit split, including any earlier weeks that were rolled into it. profitSplit is the split applied, as a percent label such as "100%" or "200%".
- items covers closed cycles only. The cycle in progress is not a row; it appears as accrual.
- accrualStatus is "ok" when accrual carries a live estimate, "not_applicable" when the account does not earn payouts in its current phase or is not yet set up on the network, "unavailable" when the validator could not be reached (items is still returned; retry for the estimate) and "omitted" on every page after the first.
- accrual.estimatedAmount is null while the current window is still empty or while the network has no payout data for the account yet (for example an account that has not traded since it entered its current phase). priorUnpaidAmount is the total of closed cycles that have not been paid yet.
- Paginate by passing nextCursor back as cursor until nextCursor is null. The accrual is computed on the first page only.
Query parameters
limitOptional. Rows per page, 1 to 200 (default 50). Values above 200 are treated as 200; anything that is not a positive integer is rejected with 400.cursorOptional. The nextCursor value from the previous page, passed back unchanged. Omit it for the first page.
Example request
# Replace with your key and secret (secret is shown only when you create the key).
# The API key is bound to a single prop account, so no accountId is required.
export VANTA_KEY_ID="YOUR_KEY_ID"
export VANTA_SECRET="YOUR_SECRET"
export BASE_URL="https://app.vantatrading.io"
PATH_REQ="/api/v1/trading/payouts"
TS=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
# GET has an empty body, so this is the sha256 of the empty string
BODY_HASH=$(printf '' | openssl dgst -sha256 -binary | xxd -p -c 256)
CANONICAL=$(printf 'v1\nGET\n%s\n%s\n%s\n%s' "$PATH_REQ" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$CANONICAL" | openssl dgst -sha256 -hmac "$VANTA_SECRET" -binary | base64 | tr -d '\n')
curl -s "$BASE_URL$PATH_REQ" \
-H "X-Vanta-Key-Id: $VANTA_KEY_ID" \
-H "X-Vanta-Timestamp: $TS" \
-H "X-Vanta-Nonce: $NONCE" \
-H "X-Vanta-Signature: v1=$SIG"# pip install requests
import base64
import hashlib
import hmac
import os
import secrets
import time
import requests
# The secret is shown once, when the key is created. Keep it out of source
# control. Read it from the environment or a secret manager.
KEY_ID = os.environ["VANTA_KEY_ID"]
SECRET = os.environ["VANTA_SECRET"]
BASE_URL = "https://app.vantatrading.io"
def signed_headers(method: str, path: str, body: str = "") -> dict:
"""Build the four auth headers for one request.
`path` must be the request target exactly as it is sent: the pathname plus
the query string, if any. Signing a different string than you request is
the most common cause of a 401.
"""
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
canonical = "\n".join(
[
"v1",
method.upper(),
path,
timestamp,
nonce,
hashlib.sha256(body.encode("utf-8")).hexdigest(),
]
)
signature = base64.b64encode(
hmac.new(
SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).digest()
).decode("ascii")
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": f"v1={signature}",
}
path = "/api/v1/trading/payouts"
response = requests.get(
BASE_URL + path, headers=signed_headers("GET", path), timeout=30
)
print(response.status_code, response.json())// Node 18 or newer. No dependencies: `fetch` and `node:crypto` are built in.
import { createHash, createHmac, randomBytes } from "node:crypto";
// The secret is shown once, when the key is created. Keep it out of source
// control. Read it from the environment or a secret manager.
const KEY_ID = process.env.VANTA_KEY_ID;
const SECRET = process.env.VANTA_SECRET;
const BASE_URL = "https://app.vantatrading.io";
/**
* Build the four auth headers for one request. `path` must be the request
* target exactly as it is sent: pathname plus query string, if any.
*/
function signedHeaders(method, path, body = "") {
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const canonical = [
"v1",
method.toUpperCase(),
path,
timestamp,
nonce,
createHash("sha256").update(body, "utf8").digest("hex"),
].join("\n");
const signature = createHmac("sha256", SECRET)
.update(canonical, "utf8")
.digest("base64");
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": `v1=${signature}`,
};
}
const path = "/api/v1/trading/payouts";
const response = await fetch(`${BASE_URL}${path}`, {
headers: signedHeaders("GET", path),
});
console.log(response.status, await response.json());Example response
{
"success": true,
"data": {
"items": [
{
"id": "e56f7a8b-9c0d-4e1f-8a3b-4c5d6e7f8a9b",
"periodStartMs": 1757894400000,
"periodEndMs": 1758499200000,
"amount": 412.5,
"currency": "USD",
"profitSplit": "100%",
"status": "completed",
"rail": "crypto",
"provider": "rise",
"alphaAmount": null,
"alphaDestination": null,
"requestedAtMs": 1758502800000,
"paidAtMs": 1758585600000,
"createdAtMs": 1758499260000
}
],
"nextCursor": null,
"accrualStatus": "ok",
"accrual": {
"estimatedAmount": 88.25,
"windowStartMs": 1758499200000,
"asOfMs": 1758800000000,
"priorUnpaidAmount": 0,
"earliestUnpaidPeriodStartMs": null,
"deferredBalance": null
}
}
}/api/v1/trading/ordersPlace an order
Submit a market, limit or bracket order for the key's bound account. Also used to edit/cancel limit orders and flatten positions.
Required scope trade:place
- execution_type accepts MARKET, LIMIT, BRACKET, LIMIT_CANCEL, LIMIT_EDIT and FLAT_ALL.
- data.order is the validator's order record parsed into typed fields. data.order_json is the same record as the raw string the validator returned and is kept for existing clients; prefer data.order.
- For a MARKET order, order.filled is true and order.quantity, order.value and order.price are the executed size and fill price, after any cap clamp and lot-grid rounding. Compare them with what you sent. When the network filled less than you asked for, order.bindingCap names the limit that applied.
- For LIMIT, STOP_LIMIT and BRACKET orders the record is the accepted resting order: order.filled is false and only the size field you sent is populated. Read the fill from netQuantity on GET /api/v1/trading/positions or quantity on GET /api/v1/trading/trades once it executes.
- order.quantity uses the same lot unit and sign convention as netQuantity on GET /api/v1/trading/positions.
- order.bracketOrders lists the TP/SL legs the network attached from stop_loss, take_profit or bracket_orders on the request, or null when there are none. Each leg carries orderUuid (what you would pass to LIMIT_CANCEL), stopLoss, takeProfit, quantity, value, bracketPct, trailingPercent and trailingValue, unused ones null.
- LIMIT_EDIT returns the edited resting order (order.filled is false).
- LIMIT_CANCEL and FLAT_ALL return data.order as null: a cancel has no order record, and FLAT_ALL closes several positions in one request. The cancelled id is data.order_uuid; read FLAT_ALL fills from GET /api/v1/trading/trades.
- Mutating endpoints cannot be run from the browser. Copy the signed script and run it from your terminal.
Request body
{
"accountId": "6f1c2e34-9a4b-4c1d-8e2f-1a2b3c4d5e6f",
"trade": {
"execution_type": "MARKET",
"trade_pair": "BTCUSDC",
"order_type": "LONG",
"value": 1000
}
}Example request
# Replace with your key and secret (secret is shown only when you create the key).
export VANTA_KEY_ID="YOUR_KEY_ID"
export VANTA_SECRET="YOUR_SECRET"
export ACCOUNT_ID="YOUR_PROP_ACCOUNT_UUID"
export BASE_URL="https://app.vantatrading.io"
BODY="{\"accountId\":\"YOUR_PROP_ACCOUNT_UUID\",\"trade\":{\"execution_type\":\"MARKET\",\"trade_pair\":\"BTCUSDC\",\"order_type\":\"LONG\",\"value\":1000}}"
PATH_REQ="/api/v1/trading/orders"
TS=$(($(date +%s) * 1000))
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -binary | xxd -p -c 256)
CANONICAL=$(printf 'v1\nPOST\n%s\n%s\n%s\n%s' "$PATH_REQ" "$TS" "$NONCE" "$BODY_HASH")
SIG=$(echo -n "$CANONICAL" | openssl dgst -sha256 -hmac "$VANTA_SECRET" -binary | base64 | tr -d '\n')
curl -s -X POST "$BASE_URL$PATH_REQ" \
-H "Content-Type: application/json" \
-H "X-Vanta-Key-Id: $VANTA_KEY_ID" \
-H "X-Vanta-Timestamp: $TS" \
-H "X-Vanta-Nonce: $NONCE" \
-H "X-Vanta-Signature: v1=$SIG" \
-d "$BODY"# pip install requests
import base64
import hashlib
import hmac
import json
import os
import secrets
import time
import requests
# The secret is shown once, when the key is created. Keep it out of source
# control. Read it from the environment or a secret manager.
KEY_ID = os.environ["VANTA_KEY_ID"]
SECRET = os.environ["VANTA_SECRET"]
BASE_URL = "https://app.vantatrading.io"
# The key is bound to one prop account; the body must name that same account.
ACCOUNT_ID = os.environ["VANTA_ACCOUNT_ID"]
def signed_headers(method: str, path: str, body: str = "") -> dict:
"""Build the four auth headers for one request.
`path` must be the request target exactly as it is sent: the pathname plus
the query string, if any. Signing a different string than you request is
the most common cause of a 401.
"""
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
canonical = "\n".join(
[
"v1",
method.upper(),
path,
timestamp,
nonce,
hashlib.sha256(body.encode("utf-8")).hexdigest(),
]
)
signature = base64.b64encode(
hmac.new(
SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).digest()
).decode("ascii")
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": f"v1={signature}",
}
path = "/api/v1/trading/orders"
# Serialise ONCE, then sign and send those exact bytes. Passing `json=` to
# requests would re-encode the payload after signing and the signature would no
# longer match what arrives. Always send the signed string with `data=`.
body = json.dumps({
"accountId": ACCOUNT_ID,
"trade": {
"execution_type": "MARKET",
"trade_pair": "BTCUSDC",
"order_type": "LONG",
"value": 1000,
},
}, separators=(",", ":"))
headers = signed_headers("POST", path, body)
headers["Content-Type"] = "application/json"
response = requests.post(BASE_URL + path, headers=headers, data=body, timeout=30)
print(response.status_code, response.json())// Node 18 or newer. No dependencies: `fetch` and `node:crypto` are built in.
import { createHash, createHmac, randomBytes } from "node:crypto";
// The secret is shown once, when the key is created. Keep it out of source
// control. Read it from the environment or a secret manager.
const KEY_ID = process.env.VANTA_KEY_ID;
const SECRET = process.env.VANTA_SECRET;
const BASE_URL = "https://app.vantatrading.io";
// The key is bound to one prop account; the body must name that same account.
const ACCOUNT_ID = process.env.VANTA_ACCOUNT_ID;
/**
* Build the four auth headers for one request. `path` must be the request
* target exactly as it is sent: pathname plus query string, if any.
*/
function signedHeaders(method, path, body = "") {
const timestamp = String(Date.now());
const nonce = randomBytes(16).toString("hex");
const canonical = [
"v1",
method.toUpperCase(),
path,
timestamp,
nonce,
createHash("sha256").update(body, "utf8").digest("hex"),
].join("\n");
const signature = createHmac("sha256", SECRET)
.update(canonical, "utf8")
.digest("base64");
return {
"X-Vanta-Key-Id": KEY_ID,
"X-Vanta-Timestamp": timestamp,
"X-Vanta-Nonce": nonce,
"X-Vanta-Signature": `v1=${signature}`,
};
}
const path = "/api/v1/trading/orders";
// Serialise ONCE, then sign and send those exact bytes. Re-stringifying the
// object for the request would change the hash and the signature no longer
// matches what arrives.
const body = JSON.stringify({
"accountId": ACCOUNT_ID,
"trade": {
"execution_type": "MARKET",
"trade_pair": "BTCUSDC",
"order_type": "LONG",
"value": 1000
}
});
const response = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: {
...signedHeaders("POST", path, body),
"Content-Type": "application/json",
},
body,
});
console.log(response.status, await response.json());Example response
{
"success": true,
"data": {
"success": true,
"order_uuid": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90",
"message": "Order successfully processed by Taoshi validator",
"error_message": "",
"processing_time": 1.23,
"order_json": "{'trade_pair_id': 'BTCUSDC', 'trade_pair': ['BTCUSDC', 'BTC/USDC'], 'order_type': 'LONG', 'leverage': 0.04, 'value': 1000.0, 'quantity': 0.01601, 'price': 62450.12, ...}",
"order": {
"orderUuid": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90",
"tradePair": "BTCUSDC",
"tradePairDisplay": "BTC/USDC",
"orderType": "LONG",
"executionType": "MARKET",
"filled": true,
"quantity": 0.01601,
"value": 1000,
"leverage": 0.04,
"price": 62450.12,
"slippage": 0.0001,
"limitPrice": null,
"stopPrice": null,
"stopCondition": null,
"stopLoss": null,
"takeProfit": null,
"bracketPct": null,
"processedMs": 1717718400000,
"realizedPnl": 0,
"bindingCap": null,
"bracketOrders": null
}
}
}Machine-readable spec
The same reference as OpenAPI 3.1, generated from the definitions above.
Import /docs/openapi.json into Postman, Insomnia or a client generator. Note that request signing is not something a generated client will do for you. The spec describes the headers, but you still supply the signature.