Quickstart
From a bot key to your first order, end to end
This is the whole flow: get a key, resolve a market, place an order, read it back. Everything here is typed parameters over HTTP — the gate builds and signs the Hyperliquid action for you.
0. Where is the gate?
The order gate is platform-hosted infrastructure — it holds the signing credentials for every vault's agent, so you don't (and can't) run your own. Point your client at your gate endpoint:
export GATE="https://<your-gate-endpoint>" # ask your onboarding contact
# local development against a self-run stack:
export GATE="http://localhost:8787"A public production gate endpoint is being finalized. Until it is published, programmatic trading runs against a development gate — confirm your endpoint during manager onboarding.
1. Get a bot key
Mint one from Manage → Trading agent → Bot API keys (see authentication). Copy the secret — it is shown once.
export KEY="hvk_…"
export VAULT="0xYourVaultAddress"2. Resolve the market
Order routing is by numeric asset id, which you resolve from Hyperliquid's
meta for the network you trade (ids differ between mainnet and testnet — see
assets & formatting and Hyperliquid's
Asset IDs):
# Native perp: the index of the coin in the perp universe.
export HL="https://api.hyperliquid.xyz/info" # testnet: api.hyperliquid-testnet.xyz
curl -s -XPOST $HL -d '{"type":"meta"}' \
| jq '.universe | to_entries[] | select(.value.name=="BTC") | .key'
# → 0 on mainnet. dex is 0 for native perps.3. Place an order
curl -s -XPOST "$GATE/vaults/$VAULT/orders" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"asset":0,"dex":0,"isBuy":true,"limitPx":"55000","sz":"0.001","reduceOnly":false,"tif":"Gtc"}'
# → {"status":"ok","response":{"type":"order","data":{"statuses":[{"resting":{"oid":...}}]}}}Check response.data.statuses[] — a rejection can arrive inside a
status:"ok" envelope ({"error":"..."}). See the
error model.
4. Read it back
The gate is write-only; read state from Hyperliquid's info API, addressing the vault address:
curl -s -XPOST $HL -d "{\"type\":\"frontendOpenOrders\",\"user\":\"$VAULT\"}"Builder-dex (HIP-3) orders need the dex param — see the
per-dex trap.
A minimal client (Node)
const GATE = process.env.GATE, KEY = process.env.KEY, VAULT = process.env.VAULT;
async function place(order) {
const res = await fetch(`${GATE}/vaults/${VAULT}/orders`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${KEY}` },
body: JSON.stringify(order),
});
const body = await res.json();
if (res.status !== 200) throw new Error(body.error ?? `gate ${res.status}`);
// HL can reject per-order inside an ok envelope.
const err = body.response?.data?.statuses?.find((s) => s.error)?.error;
if (err) throw new Error(err);
return body.response.data.statuses[0];
}
console.log(await place({
asset: 0, dex: 0, isBuy: true,
limitPx: '55000', sz: '0.001', reduceOnly: false, tif: 'Gtc',
}));That is the complete integration surface — every other action (full list) is the same POST with different typed params.