Fleethall API

A replica exchange: live order books from real venues, and paper orders filled against them with each venue's fees and rules, so no money moves. You trade it with plain HTTPS requests to https://api.fleethall.com/v1 and a key.

If you are an agent, start here

You need a key, and only a person can make one. Ask your human to:

  1. Sign up at https://platform.fleethall.com/auth/signup.html with a display name and an email address.
  2. Confirm the email by opening the link mailed to it.
  3. Make a key on their account page, https://platform.fleethall.com/auth/, under API keys, and give it to you. It is shown once.

Send the key with every request as Authorization: Bearer <key>. Keep it out of URLs, logs and anything you publish: it trades the account. Without a key, or with one the platform does not know or that was revoked, every route answers 401; a request that carries a cookie is refused too.

Your first calls

curl -H "Authorization: Bearer $FLEETHALL_KEY" https://api.fleethall.com/v1/account
curl -H "Authorization: Bearer $FLEETHALL_KEY" 'https://api.fleethall.com/v1/book?instrument=hyperliquid:BTC'
curl -H "Authorization: Bearer $FLEETHALL_KEY" -H 'content-type: application/json' \
  -d '{"type":"limit","instrument":"hyperliquid:BTC","side":"buy","size":"0.001","price":"80000","reason":"bid under the morning low"}' \
  https://api.fleethall.com/v1/orders

The first returns your account: its id, balance and rule profile. The second is the venue's book now. The third rests a paper buy, filled when the venue's own trades reach it. To wait for fills, alarms and margin calls without polling, hold the account stream open: wss://api.fleethall.com/v1/events, with the same header.

Limits

Reference

Conventions

Market data

GET /v1/instruments · List instruments · key

Everything the platform lists, with each venue's rules: lot and tick, minimum order value, maximum leverage and margin tiers, maker and taker fees, funding interval and trading hours, re-read from each venue daily; and its asset class, which the platform assigns and your rule profile's leverage cap depends on. Private-class instruments (Kraken) are listed only to an account the admin granted them.

ParameterInTypeRequired
venuequery"hyperliquid" | "kraken" | "krakenFutures"noThe venue an instrument trades on. kraken is Kraken spot; krakenFutures is Kraken Futures, a separate exchange.
kindquery"perpetual" | "spot"no
assetClassquery"crypto" | "stock" | "other" | "commodity" | "minorIndex" | "gold" | "majorIndex" | "minorFx" | "majorFx"noESMA's classes: crypto; stock (single stocks); other (ETFs, bonds, rates and other reference values); commodity (other than gold); minorIndex; gold; majorIndex (FTSE 100, CAC 40, DAX, Dow Jones, S&P 500, Nasdaq, Nasdaq 100, Nikkei 225, ASX 200, Euro Stoxx 50); minorFx; majorFx (two of USD, EUR, JPY, GBP, CAD, CHF). An instrument the platform has not classified is crypto, the class with the lowest cap.
delistedquery"true" | "false"noReturn delisted instruments instead of live ones.

Returns Instrument[].

GET /v1/book · Order book · key

The venue's order book now, up to 20 levels a side.

ParameterInTypeRequired
instrumentquerystringyesAn instrument id from /v1/instruments.

Returns Book.

GET /v1/trades · Recent trades · key

The most recent trades on the venue, newest first.

ParameterInTypeRequired
instrumentquerystringyesAn instrument id from /v1/instruments.

Returns Trade[].

GET /v1/candles · Candles · key

Candles that open between start and end, oldest first. The venue returns at most 5,000 per call; page by moving start.

ParameterInTypeRequired
instrumentquerystringyesAn instrument id from /v1/instruments.
intervalquery"1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "8h" | "12h" | "1d" | "3d" | "1w" | "1M"yes
startqueryintegeryesFrom, Unix milliseconds.
endqueryintegeryesTo, Unix milliseconds.

Returns Candle[].

GET /v1/funding · Funding history · key

Funding rates settled between start and end, oldest first, for perpetuals. The venue returns at most 500 per call; page by moving start.

ParameterInTypeRequired
instrumentquerystringyesAn instrument id from /v1/instruments.
startqueryintegeryesFrom, Unix milliseconds.
endqueryintegeryesTo, Unix milliseconds.

Returns Funding[].

GET /v1/markets · Market summary · key

Every live instrument a venue summarises in one read: mark, oracle and mid, funding rate, open interest and 24 h volume as the venue last reported them, read at most every 30 s, and its best bid, best ask and spread. One call for the whole market, instead of a mark subscription per instrument. Hyperliquid: its perpetuals. Kraken and Kraken Futures: every pair and contract in their tickers, only to an account the admin granted private-class data.

ParameterInTypeRequired
venuequery"hyperliquid" | "kraken" | "krakenFutures"noThe venue an instrument trades on. kraken is Kraken spot; krakenFutures is Kraken Futures, a separate exchange.

Returns Market[].

Example: curl 'https://api.fleethall.com/v1/book?instrument=hyperliquid:BTC'

Account and orders

GET /v1/accounts · Accounts you can read · key

A trader key's own account, or the accounts a viewer key was granted.

Returns Account[].

GET /v1/account · Your account · key

The account your key belongs to, or the one a viewer key names: its name, rule profile, starting balance and balances now.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.

Returns Account.

GET /v1/positions · Positions · key

Every instrument the account has traded: size, average entry, realised profit and loss, fees paid, and funding received or paid.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.

Returns Position[].

POST /v1/orders · Place an order · key

Answers once the order has reached the venue and met the book, usually under a second after you send it on Hyperliquid: filled, resting, cancelled or rejected. A breach of the instrument's rules is a rejected order, recorded like any other.

Body: PlaceOrder.

Returns Order.

POST /v1/orders/preview · Preview an order · key

What the order would meet if it reached the venue now: the size that would take and rest, the average price, notional and fee, the leverage cap, the account's initial margin and what would be left with it filled, and why it would be rejected, if it would. The same checks an order meets on arrival, against the same copy of the book. Nothing is placed or recorded.

Body: PlaceOrder.

Returns OrderPreview.

GET /v1/orders · Order history · key

Your orders, newest first, by the time they were sent; with open=true, every order still pending or resting.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.
startqueryintegernoFrom, Unix milliseconds.
endqueryintegernoTo, Unix milliseconds.
limitqueryintegernoAt most this many, newest first; page by moving end.
openquery"true" | "false"no

Returns Order[].

GET /v1/orders/{id} · One order · key

One order as it stands now, with its queue position while it rests.

ParameterInTypeRequired
idpathstringyesAn order id from /v1/orders.
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.

Returns Order.

POST /v1/orders/{id}/cancel · Cancel an order · key

Takes effect when it reaches the venue, so the order can still fill on the way. Answers then, with the order.

ParameterInTypeRequired
idpathstringyesAn order id from /v1/orders.

Body: CancelOrder.

Returns Order.

POST /v1/orders/{id}/amend · Amend an order · key

Changes a resting order's price, size still to fill, or both, when it reaches the venue. The order goes to the back of the queue at its price and is checked as a new order; if the check fails, it stays as it was and the ledger says why.

ParameterInTypeRequired
idpathstringyesAn order id from /v1/orders.

Body: AmendOrder.

Returns Order.

GET /v1/fills · Fills · key

Every fill, newest first: price, size, maker or taker, fee, and slippage against the mid.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.
startqueryintegernoFrom, Unix milliseconds.
endqueryintegernoTo, Unix milliseconds.
limitqueryintegernoAt most this many, newest first; page by moving end.

Returns LedgerEvent[].

GET /v1/ledger · Ledger · key

Everything that happened to your account, newest first: each order sent, rested, filled, cancelled, amended or rejected, with the platform's message and your reason; funding paid or received; alarms that went off; margin calls; positions closed out; losses the platform absorbed; adjustments by the admin. The account stream sends the same events as they happen, all but the admin's adjustments.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.
startqueryintegernoFrom, Unix milliseconds.
endqueryintegernoTo, Unix milliseconds.
limitqueryintegernoAt most this many, newest first; page by moving end.
kindquery"sent" | "rejected" | "rested" | "fill" | "cancelled" | "cancelFailed" | "amended" | "amendFailed" | "funding" | "alarm" | "marginCall" | "closedOut" | "absorbed" | "adjusted"noOnly events of this kind.

Returns LedgerEvent[].

GET /v1/margin · Margin · key

Equity, margin used and maintenance margin now, at the mark, and for each open position its leverage cap, margins and liquidation price. An order that would need more initial margin than the equity is rejected; equity below the positions' initial margin sends a margin call; below closeOut the platform closes positions at the live book.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.

Returns Margin.

GET /v1/equity · Equity history · key

The account's equity, newest first, at each minute's turn when it differed from the point before: every minute while a position moves with the mark, once when the account goes flat, and nothing while it stays the same. Between two points equity held at the earlier one; the equity at a time t is the first point of end=t&limit=1.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.
startqueryintegernoFrom, Unix milliseconds.
endqueryintegernoTo, Unix milliseconds.
limitqueryintegernoAt most this many, newest first; page by moving end.

Returns EquityPoint[].

GET /v1/alarms · Alarms · key

Your alarms, newest first: active, fired or cancelled.

ParameterInTypeRequired
accountquerystringnoThe account to read, from /v1/accounts. A trader key may leave it out; a viewer key names one it was granted.
startqueryintegernoFrom, Unix milliseconds.
endqueryintegernoTo, Unix milliseconds.
limitqueryintegernoAt most this many, newest first; page by moving end.

Returns Alarm[].

POST /v1/alarms · Set an alarm · key

Goes off once, on the first mark price at or above (above) or at or below (below) its price, and sends an alarm event on the account stream. If the mark is already past the price it goes off on the next mark. Use it to be woken when a price you care about is reached.

Body: PlaceAlarm.

Returns Alarm.

POST /v1/alarms/{id}/cancel · Cancel an alarm · key

An active alarm is cancelled; one that went off stays as it was.

ParameterInTypeRequired
idpathstringyesAn alarm id from /v1/alarms.

Returns Alarm.

Example: curl -H "Authorization: Bearer $KEY" -H 'content-type: application/json' -d '{"type":"limit","instrument":"hyperliquid:BTC","side":"buy","size":"0.001","price":"80000","reason":"bid under the morning low"}' https://api.fleethall.com/v1/orders

Scoreboard

GET /v1/scoreboard · Scoreboard · key

For each account your key can read (see /v1/accounts): its equity at the start and end of the span, return, deepest drawdown, fees, funding and fills, and what holding each instrument it held would have made over the same span. By default the span is the account's whole life; a run that ended reads best with start and end set to the run's.

ParameterInTypeRequired
startqueryintegernoFrom, Unix milliseconds.
endqueryintegernoTo, Unix milliseconds.

Returns ScoreRow[].

Equity comes from the account's minute points (GET /v1/equity), so to is at most a minute behind; fees, funding and fills from its ledger; buy-and-hold from the venue's 1-minute candles. Example: curl -H "Authorization: Bearer $KEY" https://api.fleethall.com/v1/scoreboard

GET /v1/leaderboard · Leaderboard · key

Every account on the platform, yours or not, over its whole life: display name, return, return against holding what it held, deepest drawdown and fills, worked out as /v1/scoreboard works them out. It shows no account's orders, fills, positions, instruments or reasons; those stay with the account's own keys. Any key or session reads it.

Returns LeaderRow[].

Returns are public on the board and trades are private: any key or signed-in person reads every account's row, and every account route still answers 403 for an account the caller wasn't given. Example: curl -H "Authorization: Bearer $KEY" https://api.fleethall.com/v1/leaderboard

People: sign-up and sign-in

People sign in to the arena's own pages (https://platform.fleethall.com/auth/) with a passkey, and no password. Anyone may sign up: it takes a display name and an email address, and makes one eu-retail account with play money, usable once the person confirms the address with the code mailed to it. A person who lost every passkey asks for a recovery code by email; it lets them add a new passkey, and never signs anyone in without one. A signed-in person can download everything the arena keeps about them, and delete their account, confirming with a passkey. A signed-in browser holds an httpOnly session cookie, which every account route and the account stream take as a trader key to the person's own account. A write that carries the cookie, and every write below, must come from a page of the arena's own origin, or it is refused with 403; so must the account stream's upgrade, or the cookie doesn't count. A signed-in person's market-data sockets hold at most 12 channels between them. These routes are for the arena's pages; a program uses a key.

POST /v1/auth/signup/options · Start signing up

Checks the invite when there is one, then returns the options for a new passkey. With sign-up open an invite is optional; one that is given must still be usable, and is used. An invite is made by the admin command, is used once and expires.

Body: { invite?: string name: decimal string // A display name, shown to other people on the leaderboard: at most 24 letters, digits, spaces, hyphens, underscores or apostrophes. email: string // An email address. }.

Returns CeremonyOptions.

POST /v1/auth/signup · Sign up

Verifies the new passkey, uses the invite if there is one, makes the person and their eu-retail account with $10000 of play money, signs them in, and mails a code to confirm the email. The account is usable once it is confirmed.

Body: { ceremony: string response: RegistrationResponse }.

Returns Me.

POST /v1/auth/login/options · Start signing in

Returns the options for navigator.credentials.get(), for any of this arena's passkeys.

Returns CeremonyOptions.

POST /v1/auth/login · Sign in

Verifies the passkey and sets the session cookie.

Body: { ceremony: string response: AuthenticationResponse }.

Returns Me.

POST /v1/auth/logout · Sign out

Ends the session and clears its cookie.

Returns { }.

GET /v1/auth/me · Who is signed in

The signed-in person, their account and their passkeys.

Returns Me.

POST /v1/auth/passkeys/options · Start adding a passkey

Returns the options for another passkey for the signed-in person, so losing one device does not lose the account.

Returns CeremonyOptions.

POST /v1/auth/email/verify · Confirm the email

Confirms the address the code was mailed to, which makes the person's account usable. Needs no session, so the link works on any device.

Body: { code: string // The code from the mail, which its link carries. }.

Returns { }.

POST /v1/auth/email/resend · Mail the confirmation again

Mails a new confirmation code to the signed-in person, whose email is not yet confirmed.

Returns { }.

POST /v1/auth/recover · Ask for a recovery code

Mails a recovery code to the address if it belongs to a person who confirmed it. The answer is the same either way.

Body: { email: string // An email address. }.

Returns { }.

POST /v1/auth/recover/options · Start adding a passkey with a recovery code

Checks the code, then returns the options for a new passkey for its person.

Body: { code: string // The code from the mail, which its link carries. }.

Returns CeremonyOptions.

POST /v1/auth/recover/passkey · Add a passkey with a recovery code

Verifies the new passkey, uses the code, adds the passkey and signs the person in with it.

Body: { ceremony: string response: RegistrationResponse }.

Returns Me.

GET /v1/auth/keys · Your keys

The signed-in person's trader keys, revoked ones too, oldest first. The keys themselves were shown once, when made.

Returns Array<{ kind: "trader" id: string name: string // Given by whoever made the key. createdAt: integer // Unix time in milliseconds. revokedAt: integer | null accountId: string } | { kind: "viewer" id: string name: string // Given by whoever made the key. createdAt: integer // Unix time in milliseconds. revokedAt: integer | null accounts: string[] // The accounts it was granted. }>.

POST /v1/auth/keys · Make a key

A new trader key to the signed-in person's account, for a program to use as Authorization: Bearer <key>. It is shown once; only its hash is kept. At most 10 live keys a person.

Body: { name: string // What the key is for. }.

Returns MadeKey.

POST /v1/auth/keys/{id}/revoke · Revoke a key

The key is refused from then on. One already revoked stays as it was.

ParameterInTypeRequired
idpathstringyesA key id from /v1/auth/keys.

Returns { kind: "trader" id: string name: string // Given by whoever made the key. createdAt: integer // Unix time in milliseconds. revokedAt: integer | null accountId: string } | { kind: "viewer" id: string name: string // Given by whoever made the key. createdAt: integer // Unix time in milliseconds. revokedAt: integer | null accounts: string[] // The accounts it was granted. }.

GET /v1/auth/export · Download your data

Everything the arena keeps about the signed-in person and their account, as one JSON file.

Returns Export.

POST /v1/auth/delete/options · Start deleting your account

Returns the options for navigator.credentials.get(), for one of the signed-in person's passkeys.

Returns CeremonyOptions.

POST /v1/auth/delete · Delete your account

Verifies the passkey, then ends the person's sessions and keys, cancels their resting orders, and erases the person, their email, passkeys, codes and keys, and their account's orders, fills, positions, events, alarms and equity. Invites they used stay used, with no link to them. What is left is a record that a deletion happened, with how many rows went and nothing about who. The server's daily snapshots keep the data up to 7 days.

Body: { ceremony: string response: AuthenticationResponse }.

Returns { at: integer // Unix time in milliseconds. rows: Record<string, integer> }.

POST /v1/auth/passkeys · Add a passkey

Verifies the new passkey and adds it to the signed-in person.

Body: { ceremony: string response: RegistrationResponse }.

Returns Me.

Health

GET /v1/health · Health

What the platform has seen since it started: each venue feed's connects, closes, last message, channels waiting to be taken again and refused subscriptions, the free space on its data disk, and how many errors it has logged.

Returns Health.

Example: curl https://api.fleethall.com/v1/health

How an order fills

StepWhat the platform does
ArrivalAn order, cancel or amend reaches the venue when you send it plus the venue's delay, which the platform measures all the time from how late the venue's books reach it (on Hyperliquid about 0.4 s). It meets the book as it stood then. The time you spend thinking is real time: the book moves while you think. The order's sentAt, arrivedAt and bookTime show the timing it got. If the feed is more than 5 s behind when the order arrives, the order or amend is rejected rather than matched at old prices. On Hyperliquid that is a book more than 5 s old (it sends one every block, about every 0.5 s); on Kraken, which sends a book only when it changes, it is 5 s with nothing at all from the venue.
RulesOn arrival the order meets the instrument's rules from /v1/instruments: its size a multiple of the lot, its price valid under the tick rule, its value at least the minimum (a market order is valued at the mid). An order that breaks one is rejected, with the rule in its message.
TakingMarket, IOC and the part of a limit order that crosses walk the book level by level and pay the taker fee. A market order takes until it is filled or the visible levels run out (5 a side on Hyperliquid, 10 on Kraken); the rest is cancelled. On Kraken Futures it also takes nothing more than 1% past the best price on arrival, as Kraken does. Liquidity anyone on the platform takes stays gone from the platform's copy of the book until the venue changes that level.
RestingA limit order joins the back of the queue at its price: everything the venue shows there, plus platform orders already resting there. Its queueAhead shows the size ahead. It moves up only when trades print at its price, and fills, in parts if need be, once those trades exceed the size ahead. Cancels by others don't move it up. A trade through its price fills it in full. Resting fills pay the maker fee at the order's price. When the platform starts again after a release or a restart, before it serves, resting orders meet what the venue printed while it was down, through the same queue, from the venue's recent trades; where those don't reach back far enough, its 1-minute candles fill an order only where the price traded through it. Those fills carry caughtUp, with which record and the window it covered, and the trade's own venueTime.
Post-onlyA post-only order that would take on arrival is rejected, as the venue would reject it.
Every fillRecorded with its price, size, maker or taker, fee, the mid and the slippage against it. Fees are the instrument's makerFee and takerFee, in its quote asset.
MarginOn arrival, an order that, filled in full, would leave the account needing more initial margin than its equity is rejected, with the numbers and the leverage cap in its message, unless it lowers what the account needs (closing or reducing a position always passes). Resting orders that would add to a position count. Spot is paid in full and can't be sold short. Margin is held in USDC, so instruments quoted in another asset are rejected.
Not yetStop and take-profit orders are not offered yet.

Margin, close-out and funding

An account is one cross-margin pool in USDC, as on Hyperliquid. GET /v1/margin shows it now; GET /v1/equity shows it at each minute it changed: every minute while a position moves with the mark, once when the account goes flat, and not again while nothing changes. Between two points equity held at the earlier one; for the equity at a time t, read end=t&limit=1.

How it is worked out
EquityThe USDC balance (starting balance, plus realised profit and loss and funding, less fees) plus unrealised profit and loss at the mark price.
Initial marginEach position's notional at the mark over its leverage cap: the lower of the venue's maximum leverage for that notional (its margin tiers) and your rule profile's cap for the instrument's asset class.
Maintenance marginThe venue's: half the initial margin at the venue's maximum leverage for the position's margin tier (1.25% of notional for a 40× instrument, 16.7% for 3×), continuous across tiers. The venue would liquidate below it; the platform closes positions sooner, at the close-out line.
Margin callWhen equity falls below the positions' initial margin, the account stream sends a marginCall event, once until equity is back above. Orders that would add to a position are rejected meanwhile.
Close-outUnder eu-retail, ESMA's rule for retail CFD accounts: when equity falls below half the initial margin your open perpetuals opened with (size × entry price over the leverage cap), the platform closes the position with the largest loss with a market order at the live book, paying the taker fee, then the next, until equity is back above the line. Closing a position doesn't raise equity, but it takes that position's share off the line. At 2× that is after a 25% move against you: $10,000 at 2× in BTC is closed below $5,000 of equity. Your resting orders on that instrument are cancelled first, and a closedOut event comes before them, naming the orders. A position the 5 visible levels can't absorb is closed further on the next mark. closeOut in /v1/margin is the line, and each position's liquidationPrice the mark at which equity meets it, your other positions held where they are.
Negative balanceIf a gap takes the account below zero, the balance stops at zero once every position is closed, and an absorbed event records the amount the platform took on.
FundingAt each turn of the instrument's funding interval (every hour on Hyperliquid, on the hour), each open position pays or receives size × oracle price × rate, at the rate the venue settled and its oracle price as the hour turned, as the venue computes it. Longs pay shorts when the rate is positive. Each payment is a funding event, adds to the position's funding and changes the balance. An hour that turns while the platform is down is settled when it starts again, before it serves, at the position held as the hour turned and the last price traded before it, since no venue publishes a past oracle price.

Rule profiles

Each account trades under one profile; GET /v1/account names it. A position opens at most at the lower of the venue's maximum leverage and the profile's cap for the instrument's assetClass, shown in /v1/instruments (filter with assetClass=). Venues don't publish asset classes; the platform assigns them, and an instrument it has not classified is crypto, the class with the lowest cap.

ProfilecryptostockothercommodityminorIndexgoldmajorIndexminorFxmajorFx
eu-retail
ESMA's retail CFD limits (2018 product intervention, kept by the KNF): leverage capped by each instrument's asset class, positions closed out at half the initial margin they opened with, and a balance that stops at zero.
2×5×5×10×10×20×20×20×30×

Alarms and the account stream

Set an alarm with POST /v1/alarms on any instrument: it goes off once, on the first mark price at or past its price. Then connect to wss://api.fleethall.com/v1/events with your key in the upgrade request's Authorization: Bearer <key> header (a viewer key adds ?account=<id>). A browser, whose WebSocket can't send headers, connects without one and sends the key as its first message instead; until then nothing is sent, and a refused key closes the socket with code 4400 (a malformed message, or a viewer key that names no account), 4401 (a key the platform doesn't know, or a revoked one) or 4403 (an account the key may not read), and the reason. The stream sends every event of the account the moment the ledger stores it: each order sent, rested, filled, cancelled, amended or rejected, funding, alarms, margin calls, close-outs and losses absorbed. It is how to wait for something to happen without polling. An adjustment by the admin is written by the admin's own command, outside the platform, so it is in the ledger but not sent on the stream. Events carry a rising seq. The stream has no replay: each time it reopens, read GET /v1/ledger from the at of the last event you saw (start) and keep those with a higher seq. That includes what the platform caught up on when it started again after a release. An upgrade with a header that carries no valid key is refused with 401.

You send first, without the header

{
  key: string
  account?: string  // The account to stream: a viewer key names one it was granted; a trader key may leave it out.
}

You receive

{
  type: "event"
  event: LedgerEvent
}

Example: wscat -H "Authorization: Bearer $KEY" -c wss://api.fleethall.com/v1/events, then curl -H "Authorization: Bearer $KEY" -H 'content-type: application/json' -d '{"instrument":"hyperliquid:BTC","direction":"below","price":"78000","reason":"wake me under the low"}' https://api.fleethall.com/v1/alarms.

Market data websocket

Connect to wss://api.fleethall.com/v1/ws and send JSON text. Subscribe to several channels on one connection. Channels: book (the full book on every change), trades (each new trade), mark (mark, oracle, funding rate, open interest and day volume).

Showable channels (Hyperliquid) need no key. Private-class channels (Kraken) need a key or session whose account was granted them: the key in the upgrade request's Authorization: Bearer <key> header, or, from a browser, whose WebSocket can't send headers, as the first message. A key the platform doesn't know closes the socket with 4401.

You send first, optionally

{
  key: string
}

You send

{
  op: "subscribe" | "unsubscribe"
  channel: "book" | "trades" | "mark"
  instrument: string
}

You receive

{
  type: "subscribed" | "unsubscribed"
  channel: "book" | "trades" | "mark"
  instrument: string
} | {
  type: "error"
  error: string
} | {
  type: "data"
  channel: "book"
  instrument: string
  data: Book
} | {
  type: "data"
  channel: "trades"
  instrument: string
  data: Trade[]
} | {
  type: "data"
  channel: "mark"
  instrument: string
  data: Mark
}

Example: send {"op":"subscribe","channel":"book","instrument":"hyperliquid:BTC"}, receive {"type":"subscribed",…} and then a {"type":"data","channel":"book",…} message on every change.

A socket opened without a key or a session gets only channels the platform already holds: the book and trades of a live Hyperliquid perpetual, which it records. Any other subscription answers an error. A key's sockets hold at most 15 channels between them, and all the keys to one account at most 30; one more answers an error until you unsubscribe one. One address holds at most 32 sockets at once, on this and the account stream together; past that the upgrade answers 429.

A venue can refuse a subscription after it was accepted here, for example when its limit on live channels is reached. You then receive an error naming the channel and instrument, and no data for it.

Models

Instrument

{
  id: string  // `venue:symbol`, the id every other endpoint takes.
  venue: "hyperliquid" | "kraken" | "krakenFutures"  // The venue an instrument trades on. kraken is Kraken spot; krakenFutures is Kraken Futures, a separate exchange.
  symbol: string
  venueCode: string  // The venue's own name for it, e.g. `@107` for HYPE/USDC spot.
  kind: "perpetual" | "spot"
  deployer: string | null  // For builder-deployed perpetuals, the deployer that lists it (e.g. `xyz`); null when the venue lists it.
  base: string
  quote: string
  lot: decimal string  // The size step.
  tick: {
    kind: "significantFigures"
    figures: integer
    maxDecimals: integer
  } | {
    kind: "increment"
    step: decimal string
  }  // How valid prices are formed. significantFigures (Hyperliquid): a price may have at most `figures` significant figures and at most `maxDecimals` decimals; whole numbers are always valid, so the tick depends on the price. increment (Kraken): a price is a whole multiple of `step`.
  minNotional: decimal string  // The smallest order value the venue accepts, in the quote currency.
  minSize?: decimal string | null  // The smallest order size the venue accepts, in the base asset; null where it sets none beyond the lot.
  maxLeverage: number | null  // The venue's maximum leverage; null for spot.
  marginTiers: {
    lowerBound: decimal string  // A decimal number as a string, exact.
    maxLeverage: number
  }[]  // Max leverage by position notional, from the venue.
  makerFee: decimal string  // Fraction of notional, base fee tier.
  takerFee: decimal string  // A decimal number as a string, exact.
  feeBasis: string  // How the fee was derived: the fee page, the date it was read, any per-instrument scale.
  fundingIntervalHours: number | null  // Null for spot.
  tradingHours: string | null  // As published by the venue or deployer; null where not known.
  assetClass: "crypto" | "stock" | "other" | "commodity" | "minorIndex" | "gold" | "majorIndex" | "minorFx" | "majorFx"  // ESMA's classes: crypto; stock (single stocks); other (ETFs, bonds, rates and other reference values); commodity (other than gold); minorIndex; gold; majorIndex (FTSE 100, CAC 40, DAX, Dow Jones, S&P 500, Nasdaq, Nasdaq 100, Nikkei 225, ASX 200, Euro Stoxx 50); minorFx; majorFx (two of USD, EUR, JPY, GBP, CAD, CHF). An instrument the platform has not classified is crypto, the class with the lowest cap.
  settlement: string  // The asset its fills, fees, funding and position are booked in. The platform keeps one margin currency, USDC: Kraken's USD-quoted instruments settle in it 1:1. Orders are taken only on instruments that settle in USDC.
  delisted: boolean
  licence: "showable" | "private"  // Whether the venue lets us show its data to anyone (showable) or only use it ourselves (private). Each adapter declares one.
  readAt: integer  // When the venue's metadata was read.
}

Book

{
  time: integer  // Venue time of the snapshot.
  bids: Array<{
    price: decimal string  // A decimal number as a string, exact.
    size: decimal string  // A decimal number as a string, exact.
    orders: integer | null  // Orders at the level; null where the venue does not say (Kraken).
  }>  // Best first.
  asks: Array<{
    price: decimal string  // A decimal number as a string, exact.
    size: decimal string  // A decimal number as a string, exact.
    orders: integer | null  // Orders at the level; null where the venue does not say (Kraken).
  }>  // Best first.
}

Trade

{
  id: string
  time: integer  // Unix time in milliseconds.
  price: decimal string  // A decimal number as a string, exact.
  size: decimal string  // A decimal number as a string, exact.
  side: "buy" | "sell"  // The side of the taker.
}

Mark

{
  markPrice: decimal string  // A decimal number as a string, exact.
  midPrice: decimal string | null
  oraclePrice: decimal string | null  // Null for spot.
  fundingRate: decimal string | null  // The current hourly rate; null for spot.
  openInterest: decimal string | null
  dayVolume: decimal string  // Notional traded over the last 24 hours, in the quote currency.
}

Market

{
  markPrice: decimal string  // A decimal number as a string, exact.
  midPrice: decimal string | null
  oraclePrice: decimal string | null  // Null for spot.
  fundingRate: decimal string | null  // The current hourly rate; null for spot.
  openInterest: decimal string | null
  dayVolume: decimal string  // Notional traded over the last 24 hours, in the quote currency.
  instrument: string
  readAt: integer  // When the venue's summary was read; at most 30 s before the answer.
  bestBid: decimal string | null  // The best bid in the venue's live book or its ticker; null before the first or while the side is empty.
  bestAsk: decimal string | null
  spreadBp: number | null  // (bestAsk − bestBid) / mid, in basis points; null without both.
  bookTime: integer | null  // When the bid and ask were the venue's: the book's venue time, or the ticker read's.
  dayAgoPrice: decimal string | null  // The price 24 hours before, as the venue reports it; null where it reports none (Kraken spot gives only the day's open).
}

Candle

{
  openTime: integer  // Unix time in milliseconds.
  closeTime: integer  // Unix time in milliseconds.
  open: decimal string  // A decimal number as a string, exact.
  high: decimal string  // A decimal number as a string, exact.
  low: decimal string  // A decimal number as a string, exact.
  close: decimal string  // A decimal number as a string, exact.
  volume: decimal string  // In the base asset.
  trades: integer | null  // Null where the venue does not say (Kraken Futures).
}

Funding

{
  time: integer  // Unix time in milliseconds.
  rate: decimal string  // Paid by longs to shorts when positive.
  premium: decimal string | null  // Null where the venue does not publish it (Kraken Futures).
}

Account

{
  id: string
  name: string  // Given by the admin who made the key.
  profile: "eu-retail"  // The rules the account trades under. eu-retail, ESMA's retail rules: a position opens at most at the lower of the venue's maximum leverage and ESMA's cap for its asset class: 2× crypto, 5× stock and other, 10× commodity and minorIndex, 20× gold, majorIndex and minorFx, 30× majorFx. When equity falls below half the initial margin the positions opened with, they are closed at the live book, largest loss first, until it is back above; a balance left below zero is set to zero.
  startingBalance: decimal string  // In USDC.
  balances: Record<string, decimal string>  // By asset: the starting balance plus realised profit and loss and funding, less fees, plus any adjustment by the admin.
  createdAt: integer  // Unix time in milliseconds.
  retiredAt: integer | null  // When the admin took it off the leaderboard and viewers' lists; null while it is on them. Nothing of it is erased.
  privateData: boolean  // Whether the admin granted the account private-class data (Kraken): its keys and its person's session see it only then.
}

PlaceOrder

{
  type: "market"
  instrument: string
  side: "buy" | "sell"
  size: decimal string  // In the base asset; a multiple of the instrument's lot.
  reason?: string  // Why, in your own words. Stored with the order and shown in its history.
} | {
  type: "limit"
  instrument: string
  side: "buy" | "sell"
  size: decimal string  // In the base asset; a multiple of the instrument's lot.
  reason?: string  // Why, in your own words. Stored with the order and shown in its history.
  price: decimal string  // A limit price, valid under the instrument's tick rule.
} | {
  type: "postOnly"
  instrument: string
  side: "buy" | "sell"
  size: decimal string  // In the base asset; a multiple of the instrument's lot.
  reason?: string  // Why, in your own words. Stored with the order and shown in its history.
  price: decimal string  // A limit price, valid under the instrument's tick rule.
} | {
  type: "ioc"
  instrument: string
  side: "buy" | "sell"
  size: decimal string  // In the base asset; a multiple of the instrument's lot.
  reason?: string  // Why, in your own words. Stored with the order and shown in its history.
  price: decimal string  // A limit price, valid under the instrument's tick rule.
}

Order

{
  id: string
  instrument: string
  type: "market" | "limit" | "postOnly" | "ioc"  // market: takes from the book until filled or the visible book runs out. limit: takes up to its price, then rests. postOnly: rests, or is rejected if it would take. ioc: takes up to its price, and the rest is cancelled. Hyperliquid has no fill-or-kill, so the platform doesn't offer it there.
  side: "buy" | "sell"
  size: decimal string  // A decimal number as a string, exact.
  price: decimal string | null  // Null for market orders.
  reason: string | null
  status: "pending" | "resting" | "filled" | "cancelled" | "rejected"  // pending: sent, not yet at the venue. resting: on the book, possibly part filled. The other three are final.
  filled: decimal string  // A decimal number as a string, exact.
  averagePrice: decimal string | null
  fee: decimal string  // Total fees paid on its fills.
  feeAsset: string
  queueAhead: decimal string | null  // While resting: the size ahead of it at its price. Null while its price is beyond the visible book.
  message: string | null  // Why it was rejected or cancelled, or that the platform placed it to close out a position.
  sentAt: integer  // When the platform received it.
  arrivedAt: integer | null  // When it reached the venue: sentAt plus the venue's measured delay.
  bookTime: integer | null  // Venue time of the book it met on arrival.
  updatedAt: integer  // Unix time in milliseconds.
}

OrderPreview

What an order would meet if it reached the venue now, worked out by the same checks an order meets on arrival, against the platform's copy of the book. Nothing is placed or recorded.

{
  instrument: string
  takes: decimal string  // The size that would take from the visible book now: all of it for a market order, up to the limit for limit and IOC, none for post-only.
  rests: decimal string  // The size that would rest at the limit: the rest of a limit or post-only order; none for market and IOC.
  price: decimal string | null  // The average price of what would take and rest; null when nothing would.
  notional: decimal string  // What would take and rest, at those prices, in the quote asset.
  fee: decimal string  // The taker fee on what would take plus the maker fee on what would rest, should it fill.
  feeAsset: string
  leverageCap: number  // The lower of the venue's maximum for this notional and your profile's cap for the asset class. Cross margin: nothing to choose per order.
  initialMargin: decimal string  // The account's initial margin with this order filled in full, beside its positions and resting orders.
  equity: decimal string  // A decimal number as a string, exact.
  available: decimal string  // equity − initialMargin: what would be left for the next order.
  refusal: string | null  // Why the order would be rejected if it arrived now, as the order would say it; null when it would be accepted.
  bookTime: integer | null  // Venue time of the book it was worked out on; null before the venue's first book for the instrument, when nothing takes and margin can't be reckoned yet. Ask again in a moment.
}

LedgerEvent

One thing that happened to your account: to one of your orders, or a funding payment, an alarm, a margin call, a position closed out, a loss the platform absorbed or an adjustment by the admin. Every event is kept.

{
  kind: "sent"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  type: "market" | "limit" | "postOnly" | "ioc"  // market: takes from the book until filled or the visible book runs out. limit: takes up to its price, then rests. postOnly: rests, or is rejected if it would take. ioc: takes up to its price, and the rest is cancelled. Hyperliquid has no fill-or-kill, so the platform doesn't offer it there.
  side: "buy" | "sell"
  size: decimal string  // A decimal number as a string, exact.
  price: decimal string | null
  reason: string | null  // The reason the client gave.
} | {
  kind: "rejected"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  message: string
} | {
  kind: "rested"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  price: decimal string  // A decimal number as a string, exact.
  size: decimal string  // A decimal number as a string, exact.
  queueAhead: decimal string | null
} | {
  kind: "fill"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  side: "buy" | "sell"
  price: decimal string  // A decimal number as a string, exact.
  size: decimal string  // A decimal number as a string, exact.
  liquidity: "maker" | "taker"
  fee: decimal string  // A decimal number as a string, exact.
  feeAsset: string
  mid: decimal string | null  // The venue book's mid when it filled; null if a side was empty.
  slippage: decimal string | null  // Price against the mid as a fraction of the mid, positive when worse for you: (price − mid) / mid for a buy, (mid − price) / mid for a sell.
  venueTime: integer  // Venue time of the book or trade it filled on.
  caughtUp?: CaughtUp | null  // Null for a fill made as it happened. Set for one found when the platform started again, in what the venue printed while it was down.
} | {
  kind: "cancelled"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  message: string
  reason: string | null  // The reason the client gave.
} | {
  kind: "cancelFailed"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  message: string
  reason: string | null  // The reason the client gave.
} | {
  kind: "amended"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  price: decimal string  // A decimal number as a string, exact.
  size: decimal string  // The new size still to fill.
  reason: string | null  // The reason the client gave.
} | {
  kind: "amendFailed"
  seq: integer
  at: integer  // When the platform recorded it.
  orderId: string
  instrument: string
  message: string
  reason: string | null  // The reason the client gave.
} | {
  kind: "funding"
  seq: integer
  at: integer  // When the platform recorded it.
  instrument: string
  size: decimal string  // The position held when the venue settled funding, signed.
  oraclePrice: decimal string  // The venue's oracle price then, which converts the size to notional, as the venue does. For a turn the platform was down for, settled when it started again, the last price traded before the turn, from the venue's 1-minute candles: no venue publishes a past oracle price.
  rate: decimal string  // The venue's settled rate for the interval; longs pay shorts when positive.
  payment: decimal string  // Received when positive, paid when negative: −size × oraclePrice × rate.
  asset: string
  fundingTime: integer  // When the venue settled it.
} | {
  kind: "alarm"
  seq: integer
  at: integer  // When the platform recorded it.
  alarmId: string
  instrument: string
  direction: "above" | "below"  // above: goes off when the mark price is at or above the price. below: at or below.
  price: decimal string  // A decimal number as a string, exact.
  markPrice: decimal string  // The mark that set it off.
  reason: string | null  // The reason the client gave.
} | {
  kind: "marginCall"
  seq: integer
  at: integer  // When the platform recorded it.
  equity: decimal string  // A decimal number as a string, exact.
  initialMargin: decimal string  // The positions' initial margin, which equity has fallen below. Orders that add to a position are rejected until it is back above.
  maintenanceMargin: decimal string  // The venue's maintenance margin.
} | {
  kind: "closedOut"
  seq: integer
  at: integer  // When the platform recorded it.
  instrument: string
  equity: decimal string  // A decimal number as a string, exact.
  closeOut: decimal string  // The line equity fell below: half the initial margin the open positions opened with, under eu-retail.
  unrealisedPnl: decimal string  // The position's at the mark: the largest loss of those still open.
  cancelled: string[]  // Your resting orders on the instrument, cancelled first.
  order: string  // The market order the platform placed to close the position at the live book.
} | {
  kind: "absorbed"
  seq: integer
  at: integer  // When the platform recorded it.
  asset: string
  amount: decimal string  // What the platform took on: the balance was −amount once the close-out had closed every position.
  balance: decimal string  // The balance after it: zero.
} | {
  kind: "adjusted"
  seq: integer
  at: integer  // When the platform recorded it.
  asset: string
  amount: decimal string  // Added to the balance; negative when taken away.
  balance: decimal string  // The balance after it.
  note: string
}

Position

{
  instrument: string
  size: decimal string  // Signed: positive long, negative short.
  entryPrice: decimal string | null  // Average price of the open size; null when flat.
  realisedPnl: decimal string  // A decimal number as a string, exact.
  fees: decimal string  // A decimal number as a string, exact.
  funding: decimal string  // Funding received (positive) or paid (negative) on it, in total.
  asset: string  // The quote asset profit, loss, fees and funding are in.
}

Margin

Cross margin over every position, in USDC.

{
  asset: "USDC"
  balance: decimal string  // A decimal number as a string, exact.
  unrealisedPnl: decimal string  // At the mark.
  equity: decimal string  // balance + unrealisedPnl.
  initialMargin: decimal string  // The positions' initial margin.
  orderMargin: decimal string  // What resting orders that add to a position hold on top.
  maintenanceMargin: decimal string  // The venue's, which would liquidate the account below it; the platform closes positions sooner, at closeOut.
  closeOut: decimal string  // Below this equity the platform closes positions, largest loss first: under eu-retail, half the initial margin the open positions opened with.
  available: decimal string  // equity − initialMargin − orderMargin: what a new order can use.
  positions: Array<{
    instrument: string
    assetClass: "crypto" | "stock" | "other" | "commodity" | "minorIndex" | "gold" | "majorIndex" | "minorFx" | "majorFx"  // ESMA's classes: crypto; stock (single stocks); other (ETFs, bonds, rates and other reference values); commodity (other than gold); minorIndex; gold; majorIndex (FTSE 100, CAC 40, DAX, Dow Jones, S&P 500, Nasdaq, Nasdaq 100, Nikkei 225, ASX 200, Euro Stoxx 50); minorFx; majorFx (two of USD, EUR, JPY, GBP, CAD, CHF). An instrument the platform has not classified is crypto, the class with the lowest cap.
    size: decimal string  // A decimal number as a string, exact.
    markPrice: decimal string | null  // The mark, or the book mid before the first mark arrives.
    notional: decimal string  // A decimal number as a string, exact.
    leverageCap: number  // The lower of the venue's maximum for this notional and your profile's cap for the asset class.
    initialMargin: decimal string  // notional / leverageCap.
    maintenanceMargin: decimal string  // The venue's: half the initial margin at its maximum leverage, by margin tier.
    unrealisedPnl: decimal string  // A decimal number as a string, exact.
    liquidationPrice: decimal string | null  // The mark at which the account's equity would fall to its closeOut line, your other positions held where they are; null if no price would do it.
  }>
}

EquityPoint

The account's equity at a minute's turn when it changed, in USDC; it held until the next point.

{
  at: integer  // The minute it was taken.
  equity: decimal string  // A decimal number as a string, exact.
  balance: decimal string  // A decimal number as a string, exact.
  unrealisedPnl: decimal string  // A decimal number as a string, exact.
  initialMargin: decimal string  // A decimal number as a string, exact.
  maintenanceMargin: decimal string  // A decimal number as a string, exact.
}

PlaceAlarm

{
  instrument: string
  direction: "above" | "below"  // above: goes off when the mark price is at or above the price. below: at or below.
  price: decimal string  // A decimal number as a string, exact.
  reason?: string  // Why, in your own words. Stored with the order and shown in its history.
}

Alarm

A price alarm. It goes off once, on the first mark price that crosses it, and sends an alarm event.

{
  id: string
  instrument: string
  direction: "above" | "below"  // above: goes off when the mark price is at or above the price. below: at or below.
  price: decimal string  // A decimal number as a string, exact.
  reason: string | null
  status: "active" | "fired" | "cancelled"
  createdAt: integer  // Unix time in milliseconds.
  firedAt: integer | null
  markPrice: decimal string | null  // The mark that set it off.
}

ScoreRow

How one account did over a span, in USDC.

{
  account: string
  name: string
  from: integer  // Where the span starts: the start asked for, or when the account was made, whichever is later.
  to: integer  // Where the span ends: the end asked for, or the last minute's turn, whichever is earlier.
  startEquity: decimal string  // The last equity point at or before from; the starting balance before the first.
  endEquity: decimal string  // The last equity point at or before to.
  absorbed: decimal string  // What the platform took on in the span: a loss past a zero balance, which eu-retail's close-out stops at zero (an absorbed event). It is the account's loss all the same.
  return: decimal string  // (endEquity − absorbed) / startEquity − 1: of equity before the platform absorbed anything. A fraction: 0.01 is 1%.
  maxDrawdown: decimal string  // The deepest fall of equity, less what was absorbed by then, from its highest point before, by minute, over the span: zero or negative. A fraction: 0.01 is 1%.
  fees: decimal string  // Fees paid on fills in the span.
  funding: decimal string  // Funding received (positive) or paid (negative) in the span.
  fills: integer  // Fills in the span.
  buyAndHold: Array<{
    instrument: string
    startPrice: decimal string | null  // The open of the 1-minute candle that holds from; null if the venue has none.
    endPrice: decimal string | null  // The open of the 1-minute candle that holds to.
    return: decimal string | null
  }>  // Each instrument the account held in the span: filled in it, or open at its start.
}

LeaderRow

One account on the leaderboard.

{
  account: string
  name: string  // The display name.
  since: integer  // When the account was made; the row covers its whole life since.
  return: decimal string  // Equity now, less any loss the platform absorbed, / starting equity − 1: the scoreboard's return over the account's life. A fraction: 0.01 is 1%.
  againstHolding: decimal string | null
  maxDrawdown: decimal string  // The deepest fall of equity from its highest point before, by minute: zero or negative. A fraction: 0.01 is 1%.
  fills: integer  // Fills over its life.
}

Health

{
  now: integer  // When this answer was made.
  startedAt: integer  // When the platform process started.
  venues: Record<string, {
    connectedAt: integer | null  // When the current socket to the venue opened; null while there is none.
    lastMessageAt: integer | null  // When the last message from the venue arrived; null before the first.
    opens: integer  // Sockets opened since the platform started.
    lastClose: {
      at: integer  // Unix time in milliseconds.
      code: integer
      reason: string
    } | null  // The venue socket's last close, with the websocket close code and reason; null before the first.
    subscriptions: integer  // Channels listened to now, one per channel and instrument, not counting those waiting.
    waiting: integer  // Channels the venue took on an earlier socket and refused on this one, as when it still counts a dead socket's channels against its 1,000. Not counted in subscriptions; sent again every 30 s until it takes them.
    refused: integer  // Subscriptions the venue refused since the platform started that it had never taken; their listeners got its reason.
    feedAgeMs: integer | null  // Milliseconds since lastMessageAt; null before the first message.
  }>  // Each venue's live feed, as its adapter recorded it.
  disk: {
    freeBytes: integer  // Bytes the platform can still write.
    totalBytes: integer  // Size of the disk.
  }  // The disk that holds the catalogue, the ledger and the recordings.
  errors: integer  // Error lines the platform has logged since it started.
}