{"openapi":"3.0.3","info":{"title":"QMax API","version":"1.0.0","description":"Liquidity router for Qubic. Quotes a buy or sell across the QX order book and QSwap pool and returns the cheapest route (single venue or split). All amounts are in QU; quantities are whole asset units. Billing: everything is free for everyone, no key needed (without QMax's own key the market endpoints are limited per IP and answer 429 past the allowance). Max plans (GET /v1/max: the best position for a trade, not just the best route) cost 100 QU for agents: taken from a prepaid key's balance once the plan is made (POST /v1/keys, fill it with GET /v1/topup, then POST /v1/topup/claim, and send x-api-key), or free inside an x402 session (see GET /v1/x402): 10,000 QU for 1 hour of unlimited plans. Without either, GET /v1/max answers 402 with the price and an x402 ticket. The website's own Max is free."},"paths":{"/v1/quote":{"get":{"summary":"Get the best route for an order","parameters":[{"name":"side","in":"query","required":true,"schema":{"type":"string","enum":["buy","sell"]}},{"name":"asset","in":"query","required":true,"schema":{"type":"string"},"example":"QX"},{"name":"qty","in":"query","required":true,"schema":{"type":"integer","minimum":1},"example":5000},{"name":"slippageBps","in":"query","schema":{"type":"integer","minimum":0,"maximum":1000,"default":100},"description":"Slippage tolerance in basis points, applied to the execution limits in route[].execution"},{"name":"split","in":"query","schema":{"type":"boolean","default":true},"description":"Allow splitting across venues"}],"responses":{"200":{"description":"Route quote"},"400":{"description":"Invalid input"},"404":{"description":"Unknown asset"}}},"post":{"summary":"Same as GET, with a JSON body { side, asset, qty, split? }","responses":{"200":{"description":"Route quote"},"400":{"description":"Invalid input"}}}},"/v1/assets":{"get":{"summary":"Tradable assets, busiest first","description":"Each asset has id, symbol, issuer, category ('contract' = smart contract shares, 'token' = everything else), venues, priceQu and liquidityQu, and (when the server keeps trade history) volume24hQu, volume72hQu, volume7dQu and trades24h: what traded on QX and QSwap together, and change24hPct, change72hPct and change7dPct: how far the price moved in 24 hours, 72 hours and 7 days (null when nothing traded to measure it). The list is the busiest first (most QU traded in 24 hours, then in 7 days, then the most liquid); `sort=liquidity` gives the most liquid first. `ready` is false until the first network scan finishes.","parameters":[{"name":"category","in":"query","schema":{"type":"string","enum":["contract","token"]}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Filter by symbol"},{"name":"sort","in":"query","schema":{"type":"string","enum":["volume","liquidity"],"default":"volume"},"description":"volume: busiest first. liquidity: most liquid first."}],"responses":{"200":{"description":"Asset list"}}}},"/v1/arbitrage":{"get":{"summary":"Check an asset for arbitrage between QX and QSwap, live and at full depth","description":"Searches buy-on-one-market, sell-on-the-other loops after every venue fee and returns the most profitable size, or null. A snapshot: the two legs are separate transactions.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"}},{"name":"minProfitQu","in":"query","schema":{"type":"number","minimum":0},"description":"Only an arbitrage with at least this profit (QU)"},{"name":"minProfitPct","in":"query","schema":{"type":"number","minimum":0},"description":"Only an arbitrage with at least this profit as a percentage of the QU put in"},{"name":"maxCostQu","in":"query","schema":{"type":"number","minimum":0},"description":"Budget: the most QU to put in. The search returns the best loop that fits, not the biggest"}],"responses":{"200":{"description":"{ asset, checkedAt, bothMarkets, opportunity }"}}}},"/v1/assets/search":{"get":{"summary":"Find a token by name on the network and add it to the list","parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tradable matches (several if different issuers use the same name)"}}}},"/v1/book":{"get":{"summary":"The order book and pool of an asset, ready to show","description":"QX: resting orders grouped by price with cumulative size, the best bid and ask and the spread. QSwap: pool reserves and the price, plus how far a trade of 0.1%, 0.5%, 1%, 2% and 5% of the pool would move it. Free; limited per IP without a key.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"}},{"name":"levels","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":15},"description":"Price levels per side"}],"responses":{"200":{"description":"{ asset, checkedAt, qx, qswap }"}}}},"/v1/history":{"get":{"summary":"Prices recorded over time","description":"Prices over time, in two parts. Before QMax started recording, points are rebuilt from the trades the network logged (QX fills and QSwap swaps): one hourly volume-weighted average per hour that had trades, marked `src: \"trades\"`, going back about six months. From `recordedSince` on, QMax records the price, best bid and ask and pool price each time it reads the markets (about every 10 minutes). Free; limited per IP without a key.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"}},{"name":"range","in":"query","schema":{"type":"string","enum":["1d","7d","30d","90d","all"],"default":"7d"}},{"name":"interval","in":"query","schema":{"type":"string","enum":["1h","4h","1d"]},"description":"Also return open, high, low, close candles of this size"}],"responses":{"200":{"description":"{ asset, range, since, recordedSince, points, candles? }"}}}},"/v1/keys":{"post":{"summary":"Create an API key (free, balance starts at 0)","description":"The key is shown once. Limited to a few per hour per IP.","responses":{"201":{"description":"{ key, keyId, balanceQu, maxPriceQu, minTopupQu }"}}}},"/v1/account":{"get":{"summary":"Balance and call count for your key","security":[{"apiKey":[]}],"responses":{"200":{"description":"{ keyId, calls, balanceQu, maxPriceQu, minTopupQu }"},"401":{"description":"Unknown key"}}}},"/v1/topup":{"get":{"summary":"The transaction that tops up a key","description":"Returns a transaction to sign and broadcast from any wallet: send `amountQu` QU to QPayhub (contract index 29) with inputType 1 and the given base64 payload, which is Pay(seller, resourceId, nonce). QPayhub forwards the payment to QMax and records a receipt that names the key. Then call POST /v1/topup/claim. Amounts below minTopupQu are refused because QPayhub keeps at least 100 QU of every payment.","parameters":[{"name":"keyId","in":"query","required":true,"schema":{"type":"string"}},{"name":"amountQu","in":"query","required":true,"schema":{"type":"integer"}},{"name":"nonce","in":"query","schema":{"type":"string"},"description":"Optional; random if omitted"}],"responses":{"200":{"description":"{ contractIndex, inputType, amountQu, payload, nonce }"}}}},"/v1/topup/claim":{"post":{"summary":"Credit a confirmed top-up to a key","description":"Reads the receipt from QPayhub (payer + this key + nonce) and adds the amount paid to the balance. Each payment counts once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["keyId","payer","nonce"],"properties":{"keyId":{"type":"string"},"payer":{"type":"string","description":"Identity that signed the payment"},"nonce":{"type":"string"}}}}}},"responses":{"200":{"description":"{ ok, creditedQu, balanceQu }"},"400":{"description":"No receipt yet, or already credited"}}}},"/v1/candles":{"get":{"summary":"Candles of real trades, with volume","description":"Open, high, low, close, QU volume, units traded and trade count for each candle, built from the trades QX and QSwap logged (a minute wide at the finest, back about six months: the Qubic archive has no trade records before epoch 207, April 2026). If the server has read Quhub's older QX history (`npm run import-quhub`), candles before the archive's first day come from there, QX only, marked `src: \"quhub\"` (the chain cannot confirm them); `approx: true` says a candle is a daily summary drawn as a candle (it opens at the previous day's average price and closes at that day's), which is all there is for an asset with more than 1,000 trades before April 2026. A candle exists only where something traded, so a gap means no trades. `venue` is one market, or `all` to treat QX and QSwap as one (highs and lows span both, volumes add up); `auto` follows the market the live price comes from (QSwap if the asset has a pool, else QX). The response also gives the last 24 hours of volume across both venues. Free; limited per IP without a key.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"}},{"name":"range","in":"query","schema":{"type":"string","enum":["1d","7d","30d","90d","all"],"default":"7d"}},{"name":"interval","in":"query","schema":{"type":"string","enum":["1m","5m","15m","30m","1h","4h","1d"]},"description":"Candle width. Default: 1h for 1d and 7d, 4h for 30d, 1d for 90d and all. One answer holds at most the latest 5,000 candles (`truncated` and `available` say so)."},{"name":"venue","in":"query","schema":{"type":"string","enum":["auto","QX","QSwap","all"],"default":"auto"}}],"responses":{"200":{"description":"{ asset, range, interval, venue, candles: [{ t, o, h, l, c, volumeQu, volumeQty, trades }], truncated?, available?, volume24hQu, trades24h }"},"404":{"description":"Unknown asset, or trade history is off on this server"}}}},"/v1/x402":{"get":{"summary":"How to buy a session by x402 (Q+Pay format)","description":"Describes the x402 offer: scheme exact, network qubic:mainnet, asset QUBIC, the QPayhub address to pay, the seller id, and the session's price and length. Nothing here costs anything. Absent (404) when the operator has turned x402 off.","responses":{"200":{"description":"{ x402Version, kinds, asset, payTo, sellerId, session: { priceQu, seconds, resourceId }, how }"},"404":{"description":"x402 is not enabled on this server"}}}},"/v1/session":{"get":{"summary":"Buy (or check) an x402 session","description":"Without a session or payment this answers 402 with the x402 body: accepts[0] gives the exact amount, payTo (QPayhub) and extra.{sellerId, resourceId, settlement:'contract'}, and paymentTicket carries a nonce. Pay on-chain with QPAYHUB.Pay (inputType 1; seller = sellerId, resource = sha256(resourceId), the ticket's nonce, exactly that amount), then repeat the request with `X-PAYMENT: base64(JSON { x402Version: 2, resource, accepted, payload: { txHash, ticket }, extensions })`. The server finds the payment in QPayhub's receipt, checks payer, seller, resource, nonce and amount, counts each payment once, and returns 200 with `X-ACCESS-GRANT` and `X-ACCESS-GRANT-EXPIRES`. Send the grant as `X-ACCESS-GRANT` on later requests. If the transaction is not confirmed yet the 402 says `invalid_transaction_state`: wait a few seconds and send the same header again. Inside a session there is no per-call charge for split quotes or arbitrage results, and the rate limit is the keyed one (ten times the free limit), counted for the session rather than the IP address.","parameters":[{"name":"X-PAYMENT","in":"header","schema":{"type":"string"},"description":"base64 JSON payment payload after paying QPayhub"},{"name":"X-ACCESS-GRANT","in":"header","schema":{"type":"string"},"description":"Session token from an earlier purchase"}],"responses":{"200":{"description":"{ ok, expiresAt, seconds, note }; headers X-PAYMENT-RESPONSE, X-ACCESS-GRANT, X-ACCESS-GRANT-EXPIRES"},"402":{"description":"x402 challenge, or a rejection with a reason such as invalid_transaction_state (retry), payment_already_used, invalid_payment_nonce_mismatch or recipient_mismatch"}}}},"/health":{"get":{"summary":"Liveness check","responses":{"200":{"description":"ok"}}}},"/v1/health":{"get":{"summary":"How safe an asset is to trade (a grade from A to E)","description":"A grade, a score from 0 to 100, flags, plain-English reasons and the numbers behind them, from the QX book, the QSwap pool and the last six months of trades. Includes a check for trading that looks like one bot churning (wash trading). An automated estimate from public QX and QSwap trade data. It cannot see who is trading or anything off the network, and it is not financial advice. Computed at most once a minute.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"},"description":"An asset id from /v1/assets, for example CFB."}],"responses":{"200":{"description":"score, grade, flags, reasons, metrics, partial, computedAt"},"404":{"description":"Unknown asset"}}}},"/v1/health/all":{"get":{"summary":"The health grade of every asset in one call","description":"{ assets: { [assetId]: { grade, score, flags, reason } }, computedAt }. An automated estimate from public QX and QSwap trade data. It cannot see who is trading or anything off the network, and it is not financial advice. Computed at most once a minute.","responses":{"200":{"description":"grade, score, flags and the top reason for every asset"}}}},"/v1/premium":{"get":{"summary":"How far apart QX and QSwap priced a token, hour by hour, and how often the gap would have paid after fees","description":"Built from the trade index (what was actually paid on each venue, hourly volume-weighted prices). `premiumPct` is (QSwap - QX) / QX * 100, so positive means QSwap was dearer. `points` are averages over `barMs`; `summary` is computed from the individual hours. 'Profitable' is an indication from hourly averages, not a guarantee: it ignores the QX spread, order book depth, slippage and that the legs are separate transactions. `profitableDeepShare` is the same indication counting only hours in which at least `referenceQu` traded on each venue. For a token that trades on one venue only, `points` is empty and `bothVenues` is false.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"},"description":"Asset id from /v1/assets"},{"name":"range","in":"query","schema":{"type":"string","enum":["7d","30d","90d","all"],"default":"30d"}},{"name":"referenceQu","in":"query","schema":{"type":"number","default":10000000,"minimum":100000,"maximum":1000000000000},"description":"Trade size in QU the break-even is worked out for (the flat per-swap fee matters more for small trades)."},{"name":"carry","in":"query","schema":{"type":"integer","default":0,"minimum":0,"maximum":3},"description":"Hours a venue's last price may be carried forward to an hour the other venue traded in (0: same hour only). Carried points are marked."}],"responses":{"200":{"description":"The premium series and its summary"},"400":{"description":"A parameter is out of range"},"404":{"description":"Unknown asset"}}}},"/v1/pools":{"get":{"summary":"QSwap pools ranked by what they really earn","description":"Every asset with a QSwap pool: TVL, swap volume in the window, trailing fee APR, price change, impermanent loss and notes on how far to trust the numbers. Fees are worked out from the swaps the network logged: 0.192% of each swap's value reaches liquidity providers. Trailing estimate from swap volume. Past fees do not predict future fees; price moves cause impermanent loss. By APR, pools without quality notes are ranked first. Cached for 60 seconds.","parameters":[{"name":"window","in":"query","schema":{"type":"string","enum":["7d","30d"],"default":"7d"},"description":"The trailing window the fees and price change are measured over."},{"name":"sort","in":"query","schema":{"type":"string","enum":["apr","tvl","volume"],"default":"apr"}}],"responses":{"200":{"description":"The ranked pools."},"400":{"description":"A parameter is not one of the allowed values."}}}},"/v1/pools/detail":{"get":{"summary":"One QSwap pool's stats, and an estimate for a deposit","description":"The pool's stats for the window and, when positionQu is given, what a deposit of that many QU (both sides together) would earn per day and per 30 days at the trailing rate, and its impermanent loss for price moves of 10%, 25% and 50% either way. Trailing estimate from swap volume. Past fees do not predict future fees; price moves cause impermanent loss.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"},"description":"The asset id, as in /v1/assets."},{"name":"window","in":"query","schema":{"type":"string","enum":["7d","30d"],"default":"7d"},"description":"The trailing window the fees and price change are measured over."},{"name":"positionQu","in":"query","schema":{"type":"number","exclusiveMinimum":0},"description":"A deposit, as the QU value of both sides together."}],"responses":{"200":{"description":"The pool and, if asked, the estimate."},"400":{"description":"A parameter is missing or invalid."},"404":{"description":"That asset has no QSwap pool."}}}},"/v1/backtest":{"post":{"summary":"Test a simple strategy on the real trade history of an asset","description":"Replays a strategy over hourly candles of real QX fills and QSwap swaps. A price-based strategy decides on the close of an hour and trades at the open of the next hour with trades; hold and dca follow the calendar. Fees are the contracts' own (QX 0.3% from the seller; QSwap a flat 100,000 QU per swap, with its 0.3% pool fee already inside its prices). Order book depth was not kept, so every fill is assumed at the candle price: optimistic for large amounts and thin assets. Past results do not predict future ones.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["asset","startingQu","strategy"],"additionalProperties":false,"properties":{"asset":{"type":"string","example":"QDOGE"},"range":{"type":"string","enum":["30d","90d","all"],"default":"90d"},"venue":{"type":"string","enum":["auto","QX","QSwap","all"],"default":"auto","description":"Which market's trades make the price series. 'all' joins both and charges each trade the cheaper venue's fees."},"startingQu":{"type":"integer","minimum":1,"maximum":1000000000000,"example":10000000},"strategy":{"type":"object","required":["type"],"description":"hold: no settings. dca: amountQu (default 500000, fees included) every everyHours (default 168). bands: lookbackHours (168), bandPct (5), fractionPct (25), cooldownHours (24).","properties":{"type":{"type":"string","enum":["hold","dca","bands"]},"amountQu":{"type":"integer"},"everyHours":{"type":"integer"},"lookbackHours":{"type":"integer"},"bandPct":{"type":"number"},"fractionPct":{"type":"number"},"cooldownHours":{"type":"integer"}}}}}}}},"responses":{"200":{"description":"The summary sentence, the trades, the equity curve, metrics and warnings, with the inputs echoed back"},"400":{"description":"A setting is missing or out of range; `error` names each one"},"404":{"description":"Unknown asset"}}}},"/v1/liquidity/positions":{"get":{"summary":"A wallet's QSwap liquidity positions","description":"Every QSwap pool the wallet has liquidity in: its units, share of the pool, what removing it all would pay and its value in QU. Read live from the QSwap contract (GetLiquidityOf, GetPoolBasicState). valueQu = quOut + assetOut x (reserveQu / reserveAsset), where quOut = floor(liquidity x reserveQu / totalLiquidity) and assetOut = floor(liquidity x reserveAsset / totalLiquidity) are what removing everything pays before QSwap's flat 100,000 QU fee; the tokens are valued at the pool's own price, an estimate. earnedFees is the contract's own figure: the fees are already inside what you get when you remove, nothing pays them separately. Cached for 30 seconds per wallet.","parameters":[{"name":"identity","in":"query","required":true,"schema":{"type":"string"},"description":"The wallet's 60-letter identity."},{"name":"fresh","in":"query","required":false,"schema":{"type":"string","enum":["1"]},"description":"1 to skip a cached answer older than 5 seconds (after adding or removing liquidity)."}],"responses":{"200":{"description":"The positions (complete is false when a pool could not be read)."},"400":{"description":"Not a Qubic identity."},"503":{"description":"Too many wallets being read at once; retry shortly."}}}},"/v1/liquidity/pool":{"get":{"summary":"One QSwap pool's live state, for adding or removing liquidity","description":"Read live from QSwap's GetPoolBasicState. totalLiquidity includes the 1,000 units locked to the contract when the pool was created. Adding and removing liquidity each cost a flat 100,000 QU. Cached for 10 seconds; read the contract directly right before signing.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"},"description":"The asset id, as in /v1/assets."}],"responses":{"200":{"description":"The pool's reserves and total liquidity."},"400":{"description":"asset is missing."},"404":{"description":"That asset has no QSwap pool."}}}},"/v1/ledger":{"get":{"summary":"Trade ledger, profit and CSV export for a wallet","description":"Every QX and QSwap trade of a wallet over the last `days`, read from the public archive's event log, with average-cost positions, realized and unrealized profit (at QMax's current mid or pool price) and estimated fees. Estimated from on-chain transfers; fees include the venues' fees. Not tax advice. Expensive: results are cached for 60 seconds.","parameters":[{"name":"identity","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Z]{60}$"},"description":"The wallet's 60-letter Qubic identity."},{"name":"format","in":"query","schema":{"type":"string","enum":["json","csv"],"default":"json"}},{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":365,"default":180},"description":"How far back to look."}],"responses":{"200":{"description":"The ledger (JSON), or a CSV download"},"400":{"description":"Bad identity or days"},"503":{"description":"Busy; retry shortly"}}}},"/v1/tape":{"get":{"summary":"Live trade tape: the newest QX fills and QSwap swaps, newest first, with the direction of each","description":"Every fill on the QX order book and every swap on QSwap, from one feed. `side` is the direction of the party that started the trade (buy: they bought the asset); it is read from the swap's own log or, for a QX fill, from the transaction that caused it, and is missing for a QX fill whose transaction could not be read (yet). Pass `since=<latestId>` from the last response to get only what came after: ids only grow, and a row keeps its id when its side arrives later, so a row whose side is missing can be fetched again by asking from before it. If more than `limit` rows are newer than `since`, the newest `limit` are returned. Ids start over when the server restarts: `instance` changes then, and a client holding an old cursor should drop it. `flow24h` sums the last 24 hours for the same asset and venue (see /v1/flow).","parameters":[{"name":"asset","in":"query","required":false,"schema":{"type":"string"},"description":"An asset id as listed by /v1/assets. Leave out for all assets."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"since","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Only rows with an id greater than this."},{"name":"venue","in":"query","required":false,"schema":{"type":"string","enum":["QX","QSwap"]},"description":"Only trades on this venue."}],"responses":{"200":{"description":"{ trades: [{ id, t, venue, asset, assetKey, qty, qu, price, side?, txHash? }], latestId, instance, flow24h }"},"400":{"description":"Invalid input"},"404":{"description":"Unknown asset"}}}},"/v1/flow":{"get":{"summary":"Buy versus sell pressure over the last hour or 24 hours, from the live trade tape","description":"Volume in QU and units, and number of trades, that were buys and sells, and the net pressure (buy QU minus sell QU, over their sum: +1 all buying, -1 all selling, null if nothing with a known direction traded). Direction is that of the party who started the trade. Trades whose direction is not known are counted in `unknown` and left out of the pressure. `partial` is true when the tape has not covered the whole window yet (it was started or refilled later).","parameters":[{"name":"asset","in":"query","required":false,"schema":{"type":"string"},"description":"An asset id as listed by /v1/assets. Leave out for all assets."},{"name":"window","in":"query","required":false,"schema":{"type":"string","enum":["1h","24h"],"default":"24h"}},{"name":"venue","in":"query","required":false,"schema":{"type":"string","enum":["QX","QSwap"]},"description":"Only trades on this venue."}],"responses":{"200":{"description":"{ asset, venue, window, sinceMs, buy, sell, unknown, trades, netQu, pressure, partial, coveredFromMs }"},"400":{"description":"Invalid input"},"404":{"description":"Unknown asset"}}}},"/v1/liquidation":{"post":{"summary":"What a set of holdings would fetch if sold now","description":"Each holding is run through the router as a sale of the whole amount, so the answer counts the depth of the QX order book and the QSwap pool and every fee, not just units times the last price. Where the market cannot take the whole holding, only the part it can take is counted and the holding is marked incomplete. Each asset is priced on its own. Nothing is sent or reserved.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["holdings"],"properties":{"holdings":{"type":"array","maxItems":100,"items":{"type":"object","required":["asset","qty"],"properties":{"asset":{"type":"string"},"qty":{"type":"integer","minimum":1}}}}}}}}},"responses":{"200":{"description":"{ items: [{ asset, qty, fillableQty, proceedsQu, avgPriceQu, midValueQu, haircutPct, venues, complete, error? }], totalProceedsQu, totalMidQu, incomplete, at }"},"400":{"description":"Invalid holdings"}}}},"/v1/qu":{"get":{"summary":"The price of QU in dollars","description":"One QU in US dollars: the median of five independent sources (CoinGecko, CoinPaprika, Gate.io, MEXC and Bitget; readings that disagree are dropped), with the change over 24 hours, the market value and the 24 hour volume where CoinGecko has them (null when it cannot be reached). Answers are kept for a few minutes.","responses":{"200":{"description":"{ usdPerQu, at, note, change24hPct, marketCapUsd, volume24hUsd }"},"503":{"description":"No price that can be trusted right now"}}}},"/v1/qu/candles":{"get":{"summary":"Candles of the QU price in dollars","description":"Open, high, low and close of one QU in US dollars. Without `interval` they are CoinGecko's, 30 minutes wide for `1d`, 4 hours for `7d` and `30d`, and 4 days beyond (`intervalMs` says which). With `interval` they are exchange candles from the QUBIC/USDT market on MEXC (Gate.io if MEXC cannot be reached) at that width, each with `v`, the QU traded in it; an answer holds at most the latest 5,000 (`truncated` says when there were more).","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","enum":["1d","7d","30d","90d","1y","all"],"default":"7d"}},{"name":"interval","in":"query","required":false,"schema":{"type":"string","enum":["1m","5m","15m","30m","1h","4h","1d"]}}],"responses":{"200":{"description":"{ range, intervalMs, candles: [{ t, o, h, l, c }] }, or with `interval`: { range, interval, intervalMs, truncated, candles: [{ t, o, h, l, c, v }] }"},"400":{"description":"Invalid range or interval"},"503":{"description":"No candles right now"}}}},"/v1/plans":{"get":{"summary":"Is QMax free, and where to send support","description":"QMax is free for everyone: the website, the API and the Discord bot (`discord.subscriptions` is false). Support is welcome: `support.url` is the Q+Pay tip jar it goes through (null until one is set) and `support.address` is the address QU can also be sent to. If the bot's subscription is ever switched on, `discord` also gives its length in days, its price (a fixed amount of QU, or dollars at the live QU price), when a free period ends (null if there is none) and the share of fees that goes to subscribers.","responses":{"200":{"description":"{ discord: { subscriptions, days, priceQu, priceUsd, freeUntil, freeNow, profitSharePct }, support: { url, address }, agents: { maxPriceQu, sessionPriceQu, sessionSeconds } }"}}}},"/llms.txt":{"get":{"summary":"What QMax is and how an agent uses it (llms.txt)","description":"A Markdown page for AI agents: what QMax is, the API's address and main endpoints, what is free, what Max plans cost and how to pay with x402. Made from the server's own settings, so it states the real prices. Served at the site's root as /llms.txt.","responses":{"200":{"description":"Markdown text"}}}},"/v1/swap-quote":{"post":{"summary":"Plan a token-to-token swap: sell one token for QU, then buy another with that QU","description":"Two trades run one after the other, each on QMax's best route (QX, QSwap or a split). Leg 2 is sized so that its worst case (the QU its signed limits can take) fits the LEAST leg 1 can pay within its limits, less a small safety margin: that size is `minOutQty`. `expectedOutQty` is the size if leg 1 pays what it is expected to. `upfrontQu` must be in the wallet before leg 1: QSwap's flat 100,000 QU per sell call leaves before the proceeds arrive (share-management moves are not included, as the wallet is not known here). Nothing is signed or sent; leg 2 must be sized again from the wallet's real balance after leg 1 settles. Both legs are limit orders: a QX order that does not fill stays on the book.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to","qty"],"properties":{"from":{"type":"string","description":"Asset id to sell, from /v1/assets"},"to":{"type":"string","description":"Asset id to buy, from /v1/assets"},"qty":{"type":"integer","minimum":1,"maximum":1000000000000,"description":"Units of `from` to sell"},"slippageBps":{"type":"integer","minimum":0,"maximum":1000,"default":100,"description":"Price movement allowed on each leg"},"compare":{"type":"boolean","default":false,"description":"Also plan the same swap forced onto each single market (QX or QSwap for each leg) and say what QMax's route gains over the best of them (`bestDeal`). Slower: several plans."}}}}}},"responses":{"200":{"description":"The plan: both quotes, expected and guaranteed amounts, QU needed up front, warnings and whether it can be signed"},"400":{"description":"Bad body, the same token twice, or QU as either side"},"404":{"description":"Unknown asset"}}}},"/v1/max":{"get":{"summary":"Max: the best position for a trade","description":"Searches for the best position, not just the best route. A normal order already goes at the best route (each market priced alone and a split, the cheapest taken); Max also looks at what to do with the order: take the best route now, or rest on the QX book at the touch for a better price (with how often QX traded there in the last day), or both; the size where one more unit starts to cost more; an arbitrage between QX and QSwap sized to the wallet; and, for a sale, what it returns against what the units cost. Each pick is a short list of ordinary actions (a market order at the best route or on one market, a limit order) with what it should give. Prices move: quote and check every action again before signing. Nothing is signed or sent. The wallet's numbers (`balanceQu`, `heldQty`, `avgCostQu`) come from the caller and are used only to size the plan. For agents a plan costs 100 QU: from a prepaid key's balance (POST /v1/keys, GET /v1/topup, POST /v1/topup/claim, then send x-api-key) or free inside an x402 session (GET /v1/x402). Without either the answer is a 402 with the price. The website's own Max is free.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"},"description":"Asset id from /v1/assets"},{"name":"side","in":"query","required":true,"schema":{"type":"string","enum":["buy","sell"]}},{"name":"qty","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"The amount asked for. Without it Max plans for all the wallet can buy (needs balanceQu) or all it holds (needs heldQty)."},{"name":"balanceQu","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"QU in the wallet: what a buy or an arbitrage may spend."},{"name":"heldQty","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Units the wallet can sell here."},{"name":"avgCostQu","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"What each held unit cost, for the profit of an exit."},{"name":"slippageBps","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":1000,"default":100}}],"responses":{"200":{"description":"{ asset, side, qty, baseline, picks, recommendedId, arbitrage, searched, cut, quotesUsed }"},"400":{"description":"Bad parameters, or nothing to plan from"},"402":{"description":"A Max plan costs 100 QU for agents: send a prepaid key (x-api-key) with at least that balance, or an x402 session"},"404":{"description":"Unknown asset"},"422":{"description":"The market cannot fill it, or the asset cannot be signed from here"}}}},"/v1/venue-quote":{"get":{"summary":"A quote on one market only (a plain QSwap swap, or a leg of a Max arbitrage)","description":"The same quote as /v1/quote, but routed on the one market asked for (QX or QSwap) instead of the cheapest route: what a plain QSwap swap is, and how the two legs of an arbitrage land on the markets the plan chose. A QSwap buy too small to be safe (the pool would keep the whole payment) is not quoted.","parameters":[{"name":"asset","in":"query","required":true,"schema":{"type":"string"}},{"name":"side","in":"query","required":true,"schema":{"type":"string","enum":["buy","sell"]}},{"name":"qty","in":"query","required":true,"schema":{"type":"integer","minimum":1}},{"name":"venue","in":"query","required":true,"schema":{"type":"string","enum":["QX","QSwap"]}},{"name":"slippageBps","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":1000,"default":100}}],"responses":{"200":{"description":"A quote, as /v1/quote answers"},"400":{"description":"Bad parameters"},"404":{"description":"Unknown asset"}}}},"/v1/membership":{"get":{"summary":"Is this wallet a QMax member, and what has it earned in profit share","description":"Read from the chain: a subscription paid through the Discord bot (or a pass) is a QPayhub payment signed by the wallet, so the same wallet is recognised here with nothing to link. Returns whether the membership is active and until when, and the wallet's profit share: earned over complete months, paid out, owed, and an estimate for the month so far. Any wallet may be asked about: it is all public chain data.","parameters":[{"name":"wallet","in":"query","required":true,"schema":{"type":"string"},"description":"The wallet's 60-letter identity."}],"responses":{"200":{"description":"The membership."},"400":{"description":"Not a Qubic identity."}}}},"/v1/pro":{"get":{"summary":"Is this address covered by a Max pass","description":"Whether a Max pass covers the address now, and whose it is. A pass is one QPayhub payment that covers the paying address and up to 14 others it listed (15 in all), found from the chain like Discord subscriptions. `via` is `own` when the address paid, `cover` when it is on another address's list. For the paying address `covered` is the list.","parameters":[{"name":"wallet","in":"query","required":true,"schema":{"type":"string"},"description":"A Qubic address (60 capital letters)"}],"responses":{"200":{"description":"{ wallet, active, until, via, payer, covered, listPending, coverMax, rules }"},"400":{"description":"Not an address"}}}},"/v1/pro/cover":{"post":{"summary":"Give QMax the list of addresses a Max pass covers","description":"After paying for a pass (or changing its list) the payer sends the list its payment committed to: { payer, addresses }, public addresses only, up to 15 counting the payer. QMax keeps it only if its fingerprint matches a payment that address made.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["payer","addresses"],"properties":{"payer":{"type":"string"},"addresses":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"The payer's status, with the list now in force"},"400":{"description":"Not a valid list"},"404":{"description":"No payment from that address carries this list (yet)"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"servers":[{"url":"https://qmax.exchange/api"}]}