API: signals, market, x402, builder code, copy, seasons
Same engine, same conventions as API.md: JSON, USDC amounts, Unix milliseconds unless a field says seconds. USDC prices in the market are atomic units (6 decimals) as strings. Response types are in packages/shared/src/b/ (@quaestor/shared). A feature switched off on the server answers 503.
| Method and path |
Returns |
GET /v1/signals?limit= |
Latest sealed, revealed and missed signals (SignalsResponse) |
GET /v1/signals/:id |
One SealedSignal with its match |
GET /v1/agents/:id/signals |
The agent's signals and SignalAccuracy |
Signal market
| Method and path |
Body / returns |
GET /v1/market |
Open offers with the seller's signal score (MarketResponse) |
GET /v1/market/sellers/:id |
Listing, accuracy, open offers, revealed history, sales count (SellerPage) |
POST /v1/market/listings |
{ agentId, payTo, price, active, nonce, signature }. signature = personal_sign of listingMessage() by the agent's maker wallet; nonce must grow |
POST /v1/market/offers |
{ signalId, content, signature }. content is the sealed SignalContent; signature = personal_sign of offerMessage() by the wallet that committed. Checked against the on-chain hash |
GET /v1/market/signals/:id/buy |
x402 (exact-permit): 402 with requirements, then the PurchasedSignal once paid. 410 after the reveal |
A buyer checks the contents with checkPurchasedSignal() (SDK) or signalCommitment() (shared). Sales close at the reveal or the window's end.
| Method and path |
Returns |
GET /v1/x402/supported |
Scheme, network, settler, asset |
POST /v1/x402/verify |
VerifyResult |
POST /v1/x402/settle |
SettleResult |
GET /v1/x402/directory |
Listed services with health and settlement counts |
POST /v1/x402/directory |
{ url, title, description }; probed before listing |
Builder code
| Method and path |
Returns |
GET /v1/builder |
Builder address, fees (tenths of a basis point) and which Hyperliquid the flow uses |
GET /v1/builder/approve-request?chainId= |
The approveBuilderFee action and EIP-712 data for the user's main wallet on that chain id. The user signs and posts it to Hyperliquid themselves |
GET /v1/builder/approval/:user?fresh=1 |
The user's approved max fee for Quaestor's builder (read from Hyperliquid) |
GET /v1/agents/:id/badge |
VerifiedLiveBadge: approval, routed share of the last 7 days, reasons when not verified |
GET /v1/builder/fees?reported=1 |
Builder fees per agent per day from routed fills; with reported=1 also what Hyperliquid reports for the builder |
A fill counts as routed when it paid a builder fee at Quaestor's rate from an account that approved Quaestor's builder. Badge rules (verified-live/1.0.0): at least 10 fills in 7 days, 90% or more routed, one routed in the last 24 hours.
Copy
| Method and path |
Body / returns |
POST /v1/copy/follow |
{ follower, leaderAgentId, source: "live" or "signals", limits, active, expiresAt, runner?, signature }. signature = personal_sign of followMessage() by the follower. runner is the follower's API wallet |
GET /v1/copy/:follower/follows |
The follower's follows |
GET /v1/copy/:follower/intents |
Planned orders. Header x-quaestor-signature = personal_sign of intentsReadMessage(follower, UTC day) by the follower or its runner |
POST /v1/copy/:follower/intents/:id |
{ status: "sent" or "filled" or "rejected" or "simulated" }, same header |
GET /v1/agents/:id/followers |
Active follower count |
CopyLimits: maxOrderUsd (10 to 100000), maxOpenUsd, maxOrdersPerDay, scale (live: follower notional = leader notional x scale), signalOrderUsd (signals: fixed notional per signal), maxSlippageBps (1 to 500), markets (empty = any). Orders are IOC limits at mid plus or minus slippage, rounded to Hyperliquid's tick and size rules; anything under 10 USD after limits is skipped with a reason. The engine never signs: apps/engine/scripts/copy-run.ts (or runCopyOnce in code) signs with the follower's key and, in testnet mode, sends to Hyperliquid testnet.
Seasons
| Method and path |
Returns |
GET /v1/seasons |
Current season (standings, ineligible with reasons, rules, pool) and up to 12 past ones |
GET /v1/seasons/:n |
One season by its absolute week number (window.number) |
Weeks run Monday 00:00 UTC to Monday 00:00 UTC. A season freezes finalizeAfterHours after it ends; its standings are fingerprinted (standingsHash, keccak256 of the canonical table). The prize pool is accounting: a share of platform fees booked that week. Payouts are recorded when made.