API reference · testnet
Rules and limits
The limits every client must stay within, what happens when it doesn't, and what is not allowed. The numbers on this page are read from the code that enforces them.
Terms of use
- The API is open: no key or account is needed. Reads need nothing; actions need a valid signature from the account or its approved session key.
- The testnet has no value and no service-level agreement. It can pause, change or reset at any time.
- Do not evade a limit by spreading requests over several IP addresses or accounts.
- Do not load-test the public node. Ask on Telegram first.
- Do not script the faucet across many addresses.
- Never send a private key to the API. Requests carry signatures, never keys.
Breaking these rules gets the IP addresses involved blocked at Cloudflare.
IP limits (HTTP)
Every IP address has a budget of 1,200 weight per minute, refilled continuously (20 per second). Each request costs its weight:
| Request | Weight |
|---|---|
POST /exchange | 1 + floor(batch length ÷ 40). Single actions: 1 |
POST /info: candles, userFills, recentTrades, tokenDetails, vaultDetails, vaults | 2 |
POST /info: every other type | 1 |
GET /status, POST /faucet, anything else | 1 |
Hyperliquid charges 2 and 20 for info requests. Functor charges 1 and 2 because its web app polls over HTTP; one browser tab uses about 150 a minute at most.
Address limits (actions)
Each account has a request budget for /exchange actions, whichever key signs them (a session key spends its owner's budget):
- 10,000 requests to start, plus 1 per USDC the account has traded (maker and taker, perpetuals and spot, since the account first traded).
- Over the budget, the account gets 1 request every 10 seconds. The refusal says how long to wait.
- Cancels have more room: min(budget + 100,000, 2 × budget). An account over its budget can still cancel its orders.
- A refused request costs nothing and doesn't use its nonce, so it can be resent unchanged.
Check the numbers with userRateLimit.
Open orders
Enforced by the chain itself, so every validator applies it:
- At most 1,000 resting orders per account, plus 1 per $5,000,000 traded, up to 5,000. Perpetuals and FCTR/USDC orders count together.
- Reduce-only orders that would rest are refused once the account has 1,000 or more open, whatever its volume.
- IOC orders never rest and are never limited, so a position can always be closed.
- Error:
TooManyOpenOrders { open, limit }.
WebSocket limits
| Limit | Per IP |
|---|---|
| Open connections | 10 |
| New connections | 30 per minute |
| Subscriptions, all connections | 1,000 |
| Subscriptions on one connection | 20 |
Distinct users in user subscriptions | 10 |
| Messages sent to the node | 2,000 per minute |
A refused subscription gets an error message; the connection stays open.
Faucet
10,000 mock USDC and 1,000 test FCTR per address, once every 24 hours, and at most 5 different addresses per IP per 24 hours.
Nonces
Every action carries a nonce. Use the current time in milliseconds. The node keeps the 100 highest nonces per signer (Hyperliquid's rule). A nonce must:
- not have been used by the same signer;
- be larger than the smallest of those 100, once 100 are kept;
- be within (now − 2 days, now + 1 day).
Because a nonce works only once, resending a request after a timeout is safe: a duplicate is refused with nonce already used.
When you are limited
HTTP limits answer 429 with a Retry-After header (seconds) and a JSON body:
{
"status": "err",
"response": "rate limited: 1200 request weight per minute per IP; retry in 1 s"
}
- Wait at least
Retry-After. Don't retry immediately. - For live data use the WebSocket instead of polling.
- Cache
metaandspotMeta; they rarely change.
Requests that set their own CF-Connecting-IP header are refused by Cloudflare with 403.
Generated from the node's source code by tools/api-docs.mjs; examples are real responses from https://api.functorfund.com. Questions: Telegram. Changes: News.