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" (limits measured against the starting account balance). For static accounts, challenge.drawdownBreakdown.highWaterMark carries the starting balance and the breakdown values reflect the static rules (5% balance / 5% EOD equity vs the starting balance).
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
}
}
}/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
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,
"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
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",
"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/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.
- 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": {
"status": "accepted",
"order_uuid": "d45e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90"
}
}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.