Vest API

General API Information

The base URL endpoints are available at:

When sending requests, the header must contain:

xrestservermm: restserver{account_group}

Contract Addresses

Production

ROUTER=0x919386306C47b2Fe1036e3B4F7C40D22D2461a23

Development

ROUTER=0x8E4D87AEf4AC4D5415C35A12319013e34223825B

Format Conversions

  • All decimals are strings.

  • All integers are numbers.

  • All timestamps are integers denoting milliseconds since epoch.

  • All ambiguous monetary values like margin requirements are ubiquitously USDC. i.e. USDC is the only numéraire.

ENUM Definitions

  • Symbol status: TRADING, HALT.

  • Order status: NEW, PARTIALLY_FILLED, FILLED, CANCELLED, REJECTED.

  • Order type: MARKET, LIMIT, STOP_LOSS, TAKE_PROFIT, LIQUIDATION (only in response).

  • LP type: DEPOSIT, IMMEDIATE_WITHDRAW, SCHEDULE_WITHDRAW.

  • Transfer type: DEPOSIT, WITHDRAW.

Order Lifecycle

Docs coming soon.

Order Type

Docs coming soon.

Error Codes

// General
UNAUTHORIZED = 1002
TOO_MANY_REQUESTS = 1003
INVALID_SIGNATURE = 1022
INVALID_NONCE = 1023
ACCOUNT_NOT_FOUND = 1099

// Invalid request
BAD_DECIMALS = 1111
INVALID_ORDER_TYPE = 1116
BAD_SYMBOL = 1121
INVALID_LISTEN_KEY = 1125
INVALID_PARAMETER = 1130
BAD_RECV_WINDOW = 1131
ORDER_EXPIRED = 1132

// Order rejection
PRICE_CHECK_FAILED = 3001
MARGIN_CHECK_FAILED = 3002
PRICE_STALE = 3003
ORDER_STALE = 3004
INCREASE_WITH_SL_TP = 3006
INCREASE_DURING_INSOLVENCY = 3007
DELETED_BY_LIQ = 3008
OI_CAP_EXCEEDED = 3010
INVALID_LIMIT_PRICE = 3011
DUPLICATE = 3015
ORDER_NOT_FOUND = 3017
REDUCE_ONLY_INCREASES = 3018
REDUCE_ONLY_EXCEEDS_SIZE = 3019
ORDER_CHANGES_LEVERAGE = 3020

// LP order rejection
LP_INSUFFICIENT_BALANCE = 4001
LP_MARGIN_CHECK_FAILED = 4002
LP_PRICE_STALE = 4003
LP_ORDER_STALE = 4004
LP_WITHDRAW_WITHOUT_SHARES = 4007 
LP_DUPLICATE = 4010
LP_WITHDRAW_EXCEEDS_BALANCE = 4012

// Transfer rejection 
TRANSFER_MARGIN_CHECK_FAILED = 5002
TRANSFER_WITHDRAW_CAP_EXCEEDED = 5009
TRANSFER_DUPLICATE = 5010

Public REST API

GET /exchangeInfo

Parameters

  • symbols (optional: returns all by default; should be comma-separated e.g. BTC-PERP,ETH-PERP,SOL-PERP).

Notes

  • The minimum contract size and contract size is 1e(-sizeDecimals).

Example Response

{
    "symbols": [{
        "symbol": "BTC-PERP",
        "displayName": "BTC-PERP",
        "base": "BTC",
        "quote": "USDC",
        "sizeDecimals": 4,
        "priceDecimals": 2,
        "initMarginRatio": "0.1",
        "maintMarginRatio": "0.05",
        "takerFee": "0.0001",
    }],
    "exchange": {
        "lp": "100000.000000",
        "insurance": "10000000000.000000",
        "collateralDecimals": 6,
    },
}

GET /ticker/latest

Parameters

  • symbols (optional: returns all by default; should be comma-separated, e.g. BTC-PERP,ETH-PERP,SOL-PERP).

Example Response

{
    "tickers": [{
        "symbol": "BTC-PERP",
        "markPrice": "34000.1",
        "indexPrice": "34000.0",
        "imbalance": "123.12",
        "oneHrFundingRate": "0.0001",
        "cumFunding": "1.23",
        "status": "TRADING",
    }, ...],
}

GET /ticker/24hr

Parameters

  • symbols (optional: returns all by default; should be comma-separated e.g. BTC-PERP,ETH-PERP,SOL-PERP).

Example Response

{
    "tickers": [{
        "symbol": "BTC-PERP",
        "openPrice": "34000.1",
        "closePrice": "33000.1",
        "highPrice": "35000.1",
        "lowPrice": "30000.1",
        "quoteVolume": "900000.12",
        "volume": "32.001",
        "priceChange": "-94.99999800", // NOTE: defined as price change from previous day's close price
        "priceChangePercent": "-95.960",
        "openTime": 1499783499040,
        "closeTime": 1499869899040,
    }, ...],
}

GET /funding/history

Parameters

  • symbol (e.g. BTC-PERP).

  • startTime (optional).

  • endTime (optional: defaults to current).

  • limit (optional: defaults to 1000; maximum 1000 entries from endTime).

  • interval (optional: defaults to 1m; supports 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M).

Example Response

[{
        "symbol": "ETH-PERP",
        "time": 1683849600076,
        "oneHrFundingRate": "0.001000",
    },
    {
        "symbol": "ETH-PERP",
        "time": 1683849600076,
        "oneHrFundingRate": "0.001000",
    }, ...
]

GET /klines

Parameters

  • symbol (e.g. BTC-PERP).

  • startTime (optional).

  • endTime (optional: defaults to current).

  • limit (optional: defaults to 1000; maximum 1000 entries from endTime).

  • interval (optional: defaults to 1m; supports 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M).

Example Response

[
    [
        1725315120174, // open time
        "0.098837", // o
        "0.098837", // h
        "0.098769", // l
        "0.098769", // c
        1725315151174, // close time
        "20000", // v
        "10000.000000", // quote v
        34, // num of trades
    ], ...
]

GET /trades

Parameters

  • symbol (e.g. BTC-PERP).

  • startTime (optional).

  • endTime (optional: defaults to current).

  • limit (optional: defaults to 1000; maximum 1000 entries from endTime).

Example Response

[{
    "id": 28457,
    "price": "4.00",
    "qty": "12.0000",
    "quoteQty": "48.000012",
    "time": 1499865549590,
}, ...]

GET /depth

Parameters

  • symbol (e.g. BTC-PERP).

  • limit (optional: defaults to 20; maximum 100).

Example Response

{
    "bids": [
        [
            "4.00", // PRICE
            "431.0000" // QTY (BASE)
        ]
    ],
    "asks": [
        [
            "4.02",
            "12.0000"
        ]
    ]
}

Private REST API

Authentication

To authenticate, the client must have access to or create the following variables:

  • primaryAddr, the public key of your primary account which holds balances.

  • signingAddr, a signing key generated by the client that acts as a delegate to sign transactions on behalf of the primary account.

  • apiKey, an API key returned as the response to the POST /register endpoint.

To connect to private endpoints, the client then:

  • Sends apiKey in the header of the request as X-API-KEY.

  • Attaches a signature by a valid signing key (or primary key).

    • For each POST request, create a signature based on order parameters as specified below. It is generally recommended to use time as nonce, unless you want to create identical orders (orders whose fields are the same except for nonce).

The registration process will look like this:

  1. Generate a random key/pair for signing; this will be your signingAddr.

from eth_account import Account
import secrets
priv = secrets.token_hex(32)
private_key = "0x" + priv
acct = Account.from_key(private_key)
print(f"My signingAddr is: {acct.address}")
  1. Make a request to the POST /register endpoint by creating a valid signature.

import time
from eth_account import Account as EthAccount
from eth_account.messages import encode_defunct

expiry = int(time.time()) * 1000 + 7 * 24 * 3600000  # 7 days
domain = {
    "name": 'VestRouterV2',
    "version": '0.0.1',
    "verifyingContract": ROUTER, // see contract addresses
}
types = {
    'SignerProof': [
        {'name': "approvedSigner", 'type': "address"},
        {'name': "signerExpiry", 'type': "uint256"},
    ],
}
proofArgs = {
    'approvedSigner': signing_public_key,
    'signerExpiry': expiry,
}
proofSignature = encode_typed_data(domain, types, proofArgs)
signature = EthAccount.sign_message(proofSignature, primary_private_key).signature.hex()

POST /register

Request Body Example

{
    "signingAddr": "0x", // lower-cased
    "primaryAddr": "0x", // lower-cased
    "signature": "0x0", // = sign(signingAddr, primaryKey)
    "expiryTime": 1222222334000, // expiry time in milliseconds
}

Example Response

{
    "apiKey": abcde,
    "accGroup": 0
}

GET /account

Parameters

  • time (e.g. 1713593340000).

Example Response

{
    "address": "0x3d4fcE3C4b9435C2aB6A880D37F346fa10C844e1",
    "balances": [{
        "asset": "USDC",
        "total": "4723846.892089",
        "locked": "0.000000"
    }, ],
    "collateral": "1333.000000",
    "withdrawable": "111222.120000",
    "totalAccountValue": "200000.120000",
    "openOrderMargin": "12222.340000", 
    "totalMaintMargin": "123232.120000",
    "positions": [{
        "symbol": "BTC-PERP",
        "isLong": true,
        "size": "0.1000",
        "entryPrice": "30.00",
        "entryFunding": "1.230000000000",
        "unrealizedPnl": "-30.000000", // includes funding
        "settledFunding": "30.000000",
        "markPrice": "30.00",
        "indexPrice": "34.00",
        "liqPrice": "20.00",
        "initMargin": "1200.000000",
        "maintMargin": "600.000000",
        "initMarginRatio": "0.2000"
    }],
    "lp": {
        "balance": "100.000000",
        "shares": "100.000000",
        "unrealizedPnl": "1.230000"
    },
    "time": 1233333333333
}

GET /account/nonce

Parameters

  • time, e.g. 1713593340000.

Example Response

{
    "lastNonce": 0
}

POST /orders

Example Signature

from eth_abi import encode
from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3

args = Web3.keccak(
    encode(
        ["uint256", "uint256", "string", "string", "bool", "string", "string", "bool"],
        [time, nonce, orderType, symbol, isBuy, size, limitPrice, reduceOnly]
    )
)
signable_msg = encode_defunct(args)
signature = EthAccount.sign_message(
    signable_msg, SIGNING_PRIVATE_KEY
).signature.hex()

Request Body Example

{
    "order": {
        "time": 1683849600076, // in milliseconds
        "nonce": 0,
        "symbol": "SOL-PERP",
        "isBuy": true,
        "size": "3.1200",
        "orderType": "MARKET",
        "limitPrice": "30.03",
        "reduceOnly": false,
        "initMarginRatio": "0.1250", // (optional str: defaults to initMarginRatio)
        "timeInForce": "GTC", // (optional str: only accepted when orderType == LIMIT, must be GTC or FOK)
    },
    "recvWindow": 60000, // (optional int: defaults to 5000; server will discard if server ts > time + recvWindow)
    "signature": "0x0", // NOTE: make sure this starts with 0x
}

Example Response

{
    "id": "0x0"
}

Example Error Response

{
    "code": 429,
    "msg": "Rate limited"
}

POST /orders/cancel

Example Signature

from eth_abi import encode
from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3

args = Web3.keccak(
    encode(
        ["uint256", "uint256", "string"],
        [time, nonce, id]
    )
)
signable_msg = encode_defunct(args)
signature = EthAccount.sign_message(
    signable_msg, SIGNING_PRIVATE_KEY
).signature.hex()

Request Body Example

{
    "order": {
        "time": 1683849600076,
        "nonce": 0,
        "id": "0x",
    },
    "recvWindow": 60000, // (optional int: defaults to 5000; server will discard if server ts > time + recvWindow)
    "signature": "0x0",
}

Example Response

{
    "id": "0x0"
}

GET /orders

Parameters

  • id (optional: returns all orders by default).

  • nonce (optional: returns all orders by default).

  • symbol (optional: returns all symbols by default).

  • orderType (optional: returns all orders by default).

  • status (optional: returns all statuses by default).

  • startTime (optional).

  • endTime (optional: defaults to current).

  • limit (optional: defaults to 1000; maximum 1000 entires from endTime).

  • time (e.g. 1713593340000).

Example Response

[{
    "id": "0x0",
    "nonce": 0,
    "symbol": "BTC-PERP",
    "isBuy": true,
    "orderType": "LIMIT",
    "limitPrice": "30000",
    "size": "0.1000",
    "status": "FILLED",
    "reduceOnly": false,
    "initMarginRatio": "0.1250",
    "code": null, // error code, specified only if status is REJECTED
    "lastFilledSize": "0.1000", // null if status != FILLED
    "lastFilledPrice": "30000.00", // null if status != FILLED
    "lastFilledTime": 1683849600076, // null if status != FILLED
    "avgFilledPrice": "30000.00", // null if status != FILLED
    "settledFunding": "0.100000", // null if status != FILLED, positive means trader received
    "fees": "0.010000", // includes premium (and liquidation penalty if applicable)
    "realizedPnl": "0.200000", // null if status != FILLED, includes funding and fees
    "postTime": 1683849600076
}]

POST /lp

Example Signature

from eth_abi import encode
from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3

args = Web3.keccak(
    encode(
        ["uint256", "uint256", "string", "string"],
        [time, nonce, orderType, size]
    )
)
signable_msg = encode_defunct(args)
signature = EthAccount.sign_message(
    signable_msg, SIGNING_PRIVATE_KEY
).signature.hex()

Request Body Example

{
    "order": {
        "time": 1683849600076,
        "nonce": 0,
        "orderType": "DEPOSIT",
        "size": "100",
    },
    "recvWindow": 60000,
    "signature": "0x0",
}

Example Response

{
    "id": "0x0"
}

GET /lp

Parameters

  • id (optional: returns all orders by default).

  • nonce (optional: returns all orders by default).

  • orderType (optional: returns all orders by default).

  • status (optional: returns all statuses by default).

  • startTime (optional).

  • endTime (optional: defaults to current).

  • limit (optional: defaults to 1000; maximum 1000 entires from endTime).

  • time (e.g. 1713593340000).

Example Response

[{
    "id": "0x0",
    "nonce": 0,
    "size": "100",
    "orderType": "DEPOSIT",
    "status": "FILLED",
    "code": null, // error code, specified only if status is REJECTED
    "fees": "0",
    "postTime": 1683849600076
}]

POST /transfer/withdraw

Example Signature

from eth_abi import encode
from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3

args = Web3.keccak(
    encode(
        ["uint256", "uint256", "bool", "address", "address", "address", "uint256", "uint256"],
        [time, nonce, False, account, recipient, token, size, chainId]
    )
)
signable_msg = encode_defunct(args)
signature = EthAccount.sign_message(
    signable_msg, SIGNING_PRIVATE_KEY
).signature.hex()

Request Body Example

{
    "order": {
        "time": 1683849600076,
        "nonce": 0,
        "recipient": "0x0",
        "token": "USDC",
        "size": 100000000, // uint256: should be multiplied by 1e6 for USDC
        "chainId": 324,
    },
    "recvWindow": 60000, // (optional int: defaults to 5000; server will discard if server ts > time + recvWindow)
    "signature": "0x0"
}

Example Response

{
    "id": "0x0"
}

GET /transfer

Parameters

  • id (optional: returns all orders by default).

  • nonce (optional: returns all orders by default).

  • orderType (optional: returns all orders by default).

  • startTime (optional).

  • endTime (optional: defaults to current).

  • limit (optional: defaults to 1000; maximum 1000 entires from endTime).

  • time (e.g. 1713593340000).

Example Response

[{
    "id": "0x0",
    "nonce": 0,
    "size": "100",
    "orderType": "DEPOSIT",
    "status": "FILLED",
    "code": null, // error code, specified only if status is REJECTED
    "chainId": 1,
    "postTime": 1683849600076
}]

Public WS Endpoints

The base endpoints are available at:

When sending requests, this query parameter is required:

xwebsocketserver=restserver{account_group}

Ping/Pong

To check the connection, the client must send {"method": "PING", "params": [], "id": 0} where id can be any integer. The server will respond with {"data": "PONG"}.

Subscription

For each subscription, the client must attach an integer-valued id which will be unique per request. e.g. {"method": "SUBSCRIBE", "params": [channel1, channel2, ...], "id": subscription_id}.

The server will respond with {"result": null, "id": subscription_id}after successful subscription/un-subscription.

Tickers

Channel name: tickers.

Example Response

{
    "channel": "tickers",
    "data": [{
        "symbol": "kBONK-PERP",
        "oneHrFundingRate": "0.001250",
        "cumFunding": "1.230000000000",
        "imbalance": "10029",
        "indexPrice": "0.017414",
        "markPrice": "0.017446",
        "priceChange": "-94.999998",
        "priceChangePercent": "-95.96",
    }, ]
}

Klines

Channel name: {symbol}@kline_{intervals} (e.g. DOGE-PERP@kline_1m).

Supported intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M.

Example Response

{
    "channel": "DOGE-PERP@kline_1m",
    "data": [
        1725315120174, // open time
        "0.098837", // o
        "0.098837", // h
        "0.098769", // l
        "0.098769", // c
        1725315151174, // close time
        "20000", // v
        "10000", // quote v
        "34", // num of trades
    ]
}

Depth

Channel name: {symbol}@depth (e.g. DOGE-PERP@depth).

Example Response

{
    "channel": "DOGE-PERP@depth",
    "data": {
        "bids": [
            ["0.102000", "1234"],
        ],
        "asks": [
            ["0.103000", "1234"],
        ]
    }
}

Private WS Endpoints

Authentication

To establish a connection to private endpoints, the client must obtain listenKey. For all methods, include the X-API-Key in the header obtained via /register.

POST /account/listenKey

Returns a listen key that is valid for 60 minutes. If a client has an active listenKey, it will return the existing listenKey and extend its validity by 60 minutes.

Parameters

  • None

Example Response

{
    "listenKey": "579e1280448d4843b190c50fbd170078"
}

PUT /account/listenKey

Extends the expiry of the current listen key by 60 minutes. If the existing key has already expired or there is no listen key, it will return an error response.

Parameters

  • None

Example Response:

{
    "listenKey": "579e1280448d4843b190c50fbd170078"
}

Example Error Response:

{
    "code": 1125,
    "msg": "Listen key expired. Make a POST request to create a new key."
}

DELETE /account/listenKey

Closes the current listen key.

Parameters

  • None

Example Response

{}

Subscription

Uses the same base url as the public endpoint but the client must pass in an additional query parameter, listenKey when sending a request for subscription.

e.g.

wss://wsprod.vest.exchange/ws-api?version=1.0&websocketserver=restserver0&listenKey=e4787535db6e4f12b5570f5a0f17b7ed

Channel name: account_private.

Each response will be in the following format:

{
    "channel": "account_private",
    "data": {
        "event": event_name,
        "args": payload
    }
}

Order

event_name: ORDER.

Example Payload for New Order

{
    "id": "0x0",
    "symbol": "BTC-PERP",
    "isBuy": true,
    "orderType": "MARKET",
    "limitPrice": "30000.00",
    "size": "0.1000",
    "reduceOnly": false,
    "initMarginRatio": "0.1250",
    "status": "NEW",
    "postTime": 1683849600076,
    "nonce": 0
}

Example Payload for Order Execution

{
    "id": "0x0",
    "symbol": "BTC-PERP",
    "isBuy": true,
    "orderType": "MARKET",
    "limitPrice": "30000.00",
    "size": "0.1000",
    "reduceOnly": false,
    "initMarginRatio": "0.1250",
    "status": "FILLED",
    "lastFilledSize": "0.1000",
    "lastFilledPrice": "30000.00",
    "lastFilledTime": 1683849600076,
    "avgFilledPrice": "30000.00",
    "cumFunding": "0.100000",
    "fees": "0.010000", // includes premium
    "postTime": 1683849600076,
    "nonce": 0
}

Example Payload for Order Cancellation

{
    "id": "0x0",
    "status": "CANCELLED",
    "postTime": 1683849600076,
    "nonce": 0
}

Example Payload for Order Rejection

{
    "id": "0x0",
    "status": "REJECTED",
    "code": 3010,
    "postTime": 1683849600076,
    "nonce": 0
}

LP Order

event_name: LP.

Example Payload for Order Execution

{
    "id": "0x0",
    "size": "100",
    "orderType": "DEPOSIT",
    "status": "FILLED",
    "code": null,
    "postTime": 1683849600076,
    "nonce": 0
}

Transfer

event_name: TRANSFER.

Example Payload

{
    "id": "0x0",
    "size": "100",
    "orderType": "DEPOSIT",
    "status": "FILLED",
    "code": null,
    "chainId": 1,
    "postTime": 1683849600076,
    "nonce": 0
}

Last updated