API
Everything the app shows comes from Anyperp's public HTTP API: markets, rounds and their signatures, the index series, the vault, protocol parameters and the attested listener data. Public reads need no sign-in and no key. Wallet reads and signed actions use the same session the app uses.
Base URL and versions
The API's base URL is <API base URL>, and every path starts with /v1. The full description of every operation is served as OpenAPI at <API base URL>/v1/openapi.json and as JSON Schema at <API base URL>/v1/schema.json.
curl "<API base URL>/v1/markets?state=live&limit=20"
Responses and errors
A successful response wraps its data with metadata:
{
"data": { "items": [], "nextCursor": null, "total": 0 },
"meta": {
"requestId": "8f1c2b9e-0d4a-4e57-9a51-3f6d2f1e7c10",
"asOf": "2026-10-16T12:00:00.000Z",
"mode": "indexed",
"contractVersion": "1.0.0"
}
}
Collections are pages of { items, nextCursor, total }. Pass nextCursor back as cursor for the next page; limit takes 1 to 100. Data read from the chain is served from finalized state.
An error has one shape everywhere:
{
"error": {
"code": "NOT_FOUND",
"message": "The requested resource was not found.",
"retryable": false,
"details": { "resourceId": "..." }
},
"meta": { "requestId": "...", "asOf": "...", "mode": "indexed", "contractVersion": "1.0.0" }
}
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | the input is malformed; details.issues names each field |
UNAUTHORIZED | 401 | the request needs a signed-in wallet, or the sign-in failed |
SESSION_EXPIRED | 401 | the session has ended; sign in again |
GEO_BLOCKED | 403 | trading isn't available where the request comes from |
FORBIDDEN | 403 | the request isn't allowed for this caller |
NOT_FOUND | 404 | no such resource |
CONFLICT | 409 | the request conflicts with current state, such as a second pending order on one position |
MARKET_NOT_TRADABLE | 409 | the market or side isn't taking new orders |
ORDER_NOT_CANCELLABLE | 409 | the order can still fill |
WITHDRAWAL_LOCKED | 409 | the withdrawal hasn't unlocked or been priced |
QUOTE_EXPIRED | 409 | the quote is older than its expiry |
RATE_LIMITED | 429 | too many requests; retryAfterSeconds says when to retry |
CAP_EXCEEDED | in a quote | the size doesn't fit under the side's cap or the vault's reserve |
TRANSACTION_FAILED | in a transaction | the transaction failed on chain |
retryable says whether the same request can succeed later.
Units
| Value | Encoding |
|---|---|
| USDC amounts | integer strings in base units, 6 decimals: "1000000" is 1 USDC |
| Index, contract size, shares, rates | integer strings in fixed-point with 9 decimals ("fixed9"): "600000000000" is 600.00 |
| Notional | size × index ÷ 10¹², in USDC base units; one contract is worth the index in USDC |
| Funding and borrow rates | per hour, fixed9; funding velocity per hour², fixed9 |
| Basis points, counts, leverage | JSON numbers |
| Times | ISO 8601 strings in UTC |
Amounts can exceed the range of a JavaScript number. Read them with BigInt or an exact decimal type. Signed values (PnL, skew, net exposure, accruals) carry a leading minus sign.
Public endpoints
| Method | Path | Returns |
|---|---|---|
| GET | /v1/protocol | protocol parameters with their units and provenance, the fee split, units, attestation roots and chain addresses |
| GET | /v1/markets | market summaries with state, index, 24 h change, open interest and caps, funding, borrow, next round and pauses; filter with state |
| GET | /v1/markets/:marketId | one market with its metric spec, cost dossier, Sentinel flags, other venues, recent rounds, market parameters and settlement |
| GET | /v1/markets/:marketId/series | the index history; interval is hour or day; by default the latest 168 hourly points in ascending time, limit 1 to 168, cursor pages back |
| GET | /v1/markets/:marketId/rounds | the market's rounds with both observations; filter with state |
| GET | /v1/rounds/:roundId | one round with fetch and source times, signers and signatures |
| GET | /v1/vault | NAV, share price, shares, liquidity, net exposure, fees, insurance fund and the withdrawal queue |
| GET | /v1/attested/subjects | every artist with attested data, its latest value and its root agreement rate |
| GET | /v1/attested/subjects/:subjectId/series | an artist's daily attested series; from, to, cursor, limit |
| GET | /v1/attested/compare | two artists' series aligned; a, b, normalization (raw or rebased_100), from, to |
| GET | /v1/proposals | proposals with bonds, shadow agreement, curator votes and listing times; filter with state |
| GET | /v1/proposals/:proposalId | one proposal with its spec, cost dossier and Sentinel flags |
| GET | /v1/status | service liveness, each market's latest print and consecutive failed rounds, and halts |
| GET | /v1/eligibility | whether the visitor can trade, provide liquidity and propose, and the current terms version |
| GET | /v1/session | the current wallet session, or anonymous |
| POST | /v1/quotes/orders | a quote for an order: fill price, skew impact, fees, margin, liquidation price and room under the caps |
A quote takes marketId, side, sizeFixed9 and leverage, and for a close intent: "close" with the positionId. It reports canSubmit and, when the order can't go through, a rejection in the error shape. A quote expires 60 seconds after it is made.
Wallet endpoints
These need the session cookie set at sign-in:
| Method | Path | Returns |
|---|---|---|
| GET | /v1/me/orders | your orders and whether each can be cancelled |
| GET | /v1/me/positions | your positions with accruals, margin, equity and liquidation price |
| GET | /v1/me/lp | your shares, pending deposit, withdrawals and claim balances |
| GET | /v1/me/proposer | your proposals, bonds and claimable proposer fees |
| GET | /v1/me/history | fills, funding, borrow, liquidations, deposits, withdrawals, margin, bonds, fee claims and settlements |
| POST | /v1/proposals/draft | a Sentinel draft of a market spec and cost dossier |
| POST | /v1/transactions/prepare | an unsigned transaction for an action |
| POST | /v1/transactions/submit | records the signature of a sent transaction |
| GET | /v1/transactions/:transactionId | the transaction's state |
The live event stream
GET /v1/events is a server-sent event stream. Each message's event is the event type, its data the JSON event, and its id the event id. Filter market events with ?marketId=.
| Event | Sent when |
|---|---|
round.finalized | a round prints; carries the market and the round |
order.filled | an order fills; carries the market, order, position, round, side, size, fill price and fee, without the wallet address |
market.state_changed | a market changes state, such as probation to live |
vault.updated | the vault's NAV or queue changes |
transaction.updated | one of your transactions changes state; needs your session |
stream.reset | the stream can't resume from your position; refetch your reads |
A heartbeat comment arrives every 15 seconds. A reconnecting EventSource sends Last-Event-ID and resumes where it left off; a new client can pass cursor. Events tell you what changed; read the endpoints again for the new state.
curl -N "<API base URL>/v1/events?marketId=<marketId>"
The contract package
@anyperp/contract is the API's TypeScript contract: every request and response type, the event union, the operation registry and a small client for browsers and Node 22.
import { createAnyperpClient, ApiClientError } from "@anyperp/contract";
const api = createAnyperpClient({ baseUrl: "<API base URL>" });
const markets = await api.request("markets", { query: { state: "live", limit: 20 } });
const first = markets.data.items[0];
const series = await api.request("marketSeries", {
params: { marketId: first.id },
query: { interval: "hour", limit: 168 },
});
try {
await api.request("round", { params: { roundId: "unknown" } });
} catch (error) {
if (error instanceof ApiClientError) console.log(error.status, error.response.error.code);
}
// Browser: live events through EventSource
const stop = api.subscribe(
(event) => {
if (event.type === "round.finalized") console.log(event.data.round.finalIndexFixed9);
},
{ marketId: first.id },
);
request takes an operation name from the registry with its params, query and body, and throws ApiClientError with the status and the error envelope. eventsUrl gives the stream's URL for server-side consumers.
Signed actions
Every action that changes state is a Solana transaction signed in the user's wallet. The API prepares it and records it; it never holds a user's key.
Signing in
GET /v1/eligibilityreturnstermsVersion.POST /v1/auth/challengewith{ wallet, origin }, the wallet's address and the app's origin, returns a challenge with the exactmessageto sign. It expires after 5 minutes.- The wallet signs the message bytes with its message-signing feature.
POST /v1/auth/verifywith{ challengeId, wallet, signatureBase64, nonUsPersonAttested, termsVersion }checks the Ed25519 signature and sets the HttpOnly session cookie.
POST /v1/auth/logout ends the session.
Prepare, sign and send, submit
- Prepare.
POST /v1/transactions/preparewith{ action, idempotencyKey }. The response carries the unsigned transaction (serializedTransactionBase64), itsrecentBlockhashandlastValidBlockHeight, thecluster, a plain description (unsignedMessageDescription), the required signers and the network fee in lamports. - Sign and send. The wallet signs the transaction and sends it to Solana.
- Submit.
POST /v1/transactions/submitwith{ transactionId, signature, idempotencyKey }. The API checks the transaction against the prepared message and returns its state:submitted,confirmed,failedorexpired.
GET /v1/transactions/:transactionId and the transaction.updated event follow it to confirmation. For an order, orderId in the transaction matches the orderId of its order.filled event. A repeated request with the same idempotency key returns the same intent; the same key with different input is a CONFLICT.
Actions
kind | Fields | What it does |
|---|---|---|
open_order | quoteId, clientOrderId, marketId, side, sizeFixed9, marginUsdc, acceptablePriceFixed9 | escrows margin and queues an order for the next print; on the side you hold, it adds to the position |
close_position | positionId, sizeFixed9, acceptablePriceFixed9 | queues a close for the next print |
cancel_order | orderId | cancels an order that can no longer fill and credits its margin to the claim balance |
modify_margin | positionId, direction (add or remove), amountUsdc | adds or removes margin at once |
deposit | amountUsdc | deposits USDC into the vault at the next NAV |
request_withdraw | sharesFixed9 | queues a withdrawal for 72 h |
execute_withdraw | withdrawalId | credits a priced withdrawal's USDC to the claim balance |
claim_balances | amountUsdc, sharesFixed9 | moves claim balances into the wallet; either may be "0" |
propose_market | draftId, bondUsdc | posts the bond and proposes a drafted market |
claim_market_fees | marketId, amountUsdc (optional) | claims the proposer's fee share of a market; without an amount, all of it |