# SaaSDaq > The Nasdaq for Side Projects. A fantasy stock market where every listed product is > a ticker: you get free play money, buy the products you believe in, and your ROI is > ranked in public. No real money changes hands. This file is written for agents. Everything below is stable and machine-callable. Reads need no credentials. Writes need one header. Base URL: https://saasdaq.net ## Getting a key A human mints the key in a browser at `/me` → API Keys, then pastes it into you. There is no endpoint that issues keys — that is deliberate. A key represents the person who minted it, so a key that could mint another key would make revocation meaningless. Authorization: Bearer sdq_<40 hex chars> Two scopes exist, chosen at mint time: - `trade` — place orders, claim the daily faucet - `listing` — list new products (each listing burns a 50 HYPE bond, permanently) A key acts as the person who owns it: same balance, same rank, same daily limits. Trades you place are marked `via: "api"` and shown as such in the public tape. Losing HYPE hurts only that account, so nothing here is sandboxed — the money is fake, but the leaderboard is real. ## MCP server If your client speaks MCP, you do not have to write any of the HTTP below: { "mcpServers": { "saasdaq": { "command": "npx", "args": ["-y", "@saasdaq/mcp"], "env": { "SAASDAQ_API_KEY": "sdq_..." } } } } Ten tools. Six read and need no key at all — `get_board`, `get_market`, `get_candles`, `get_trader`, `search_products`, `get_totals`. Four send the header: `get_me`, `place_trade`, `claim_faucet`, `list_product`. `SAASDAQ_BASE_URL` overrides the base URL and defaults to this site. Amounts are the same base-unit strings documented below — the server deliberately does not accept "5 HYPE", because a tool that took decimals while this file says `"5000000"` is a 10^6 mistake waiting to happen. Package: https://www.npmjs.com/package/@saasdaq/mcp Setup page for humans: /mcp ## 🔴 Units: read this before your first order All money is an **integer string** in base units. Never a float, never a decimal point, never a number literal (JSON numbers lose precision past 2^53). 1 HYPE = 1000000 µH (microHYPE) 1 share = 1000000 µS (microShares) Every amount, balance, price and delta in every response is a µH or µS string. `roiBps` and `pnlBps` are the exception: they are basis points as JSON integers, where 10000 = break even. **The `amount` field on an order changes unit with `side`:** side = "buy" → amount is µH — how much cash to spend side = "sell" → amount is µS — how many shares to sell So `{"side":"buy","amount":"5000000"}` spends 5 HYPE, and `{"side":"sell","amount":"5000000"}` sells 5 shares. Getting this backwards is the single most common way to place an order you did not mean to place. ## 🔴 Idempotency: reuse the key on retry, rotate it on intent Every order carries `idempotencyKey`, a UUID you generate. Replaying the same key returns the **original receipt** and writes nothing — `"replayed": true` says so. network timeout, unknown outcome → retry with the SAME key (safe) buy the same amount again, on purpose → generate a NEW UUID (required) Reusing a key after a success looks like it worked and moves no money. ## Authenticated endpoints (`/api/v1`) All four are JSON in, JSON out, `cache-control: private, no-store`. ### GET /api/v1/me — account, holdings, and what this key may do curl -H "Authorization: Bearer $SDQ_KEY" https://saasdaq.net/api/v1/me { "user": { "id": "...", "handle": "alice", "name": "Alice", "image": "...", "isAdmin": false, "shadowBanned": false }, "account": { "hype": "1000000000", "totalFaucetReceived": "1000000000", "pendingRoyalty": "0", "streakDays": 1, "lastFaucetAt": 1756684800000, "nextClaimAt": 1756771200000 }, "portfolio": { "cash": "1000000000", "pendingRoyalty": "0", "faucetTotal": "1000000000", "netWorth": "1000000000", "roiBps": 10000, "queriedAt": 1756700000000, "positions": [ { "ticker": "LNR", "shares": "5000000", "sellable": "5000000", "locked": "0", "costBasis": "5000000", "value": "5100000", "price": "1020000", "pnlBps": 200, "status": "active" } ] }, "scopes": ["trade"] } Both payloads come back in one call on purpose: an agent needs cash *and* positions before it can decide anything, and requests carrying `Authorization` always bypass the edge cache. Check `scopes` before trying to list — otherwise you learn your limits from a 403. `nextClaimAt` is `null` when the faucet has never been claimed. ### POST /api/v1/trade — buy or sell Scope: `trade`. Body: { "ticker": "LNR", "side": "buy", "amount": "5000000", "idempotencyKey": "3f0c9f7e-8d1a-4f9b-9c2e-2b7a5d6e1f04" } - `ticker` — 3–6 uppercase letters or digits, no `$` - `side` — `"buy"` or `"sell"` - `amount` — 1–18 digits, µH when buying and µS when selling (see Units) - `idempotencyKey` — a UUID (see Idempotency) { "ticker": "LNR", "side": "buy", "hypeDelta": "-5000000", // signed from your side: buy negative "sharesDelta": "4901960", // µS you received "fee": "50000", // 1%: half burned, half to the founder "priceAfter": "1020000", // µH per share, marginal price after the fill "balance": "994950000", "positionShares": "4901960", "createdAt": 1756700000000, "replayed": false, "marketAfter": { ... } // null on a replay } Prices come off a bonding curve, so a large order moves the price against you — `priceAfter` is where the curve ended up, not what you paid on average. Divide `hypeDelta` by `sharesDelta` for your effective fill price. ### POST /api/v1/faucet — claim the daily allowance Scope: `trade`. No body. Once per UTC day; a second call the same day returns `FAUCET_TOO_SOON` and writes nothing. { "amount": "200000000", "balance": "1194950000", "streakDays": 2, "nextClaimAt": 1756857600000, "totalFaucetReceived": "1200000000" } Worth knowing before you farm it: rank is `netWorth / totalFaucetReceived`, so every claim raises the denominator too. For an account that is already up, claiming pulls your own ROI back toward break even. `nextClaimAt` is the next UTC midnight — sleep until then rather than polling. ### POST /api/v1/listing — list a product Scope: `listing`. Costs **50 HYPE**, non-refundable, and the 100 founder shares you receive are locked forever. Body: { "name": "Linear", "url": "linear.app", "tagline": "Issue tracking built for speed", "category": "PROD" } - `name` — 1–80 characters - `url` — 3–2048 characters; a missing scheme is fine, `https://` gets added, tracking parameters are stripped - `tagline` — 1–140 characters - `category` — one of `AI`, `DEV`, `INFRA`, `PROD` 201 on success: { "productId": "...", "ticker": "LNR", "url": "https://linear.app", "domain": "linear.app", "receipt": { "ticker": "LNR", "reserve": "50000000", "shares": "100000000", "lockedShares": "100000000", "price": "1000000", "founderBalance": "950000000", "listedAt": 1756700000000, "tR": 1756700000000 } } The ticker is minted by the server — you cannot request one. One domain can only be listed once: a replay returns `DUPLICATE_DOMAIN` and does not charge a second bond, which makes the domain its own idempotency key. ## Public endpoints (no credentials, edge-cached) GET /api/index?tab=cap|gainers|wolves&cursor= the three boards, paginated GET /api/p/{ticker} one market, full detail GET /api/p/{ticker}/candles?tf=1h&limit=168 hourly candles GET /api/u/{handle} a trader's public profile GET /api/search?q= product search GET /api/totals site totals, ~5 min old GET /api/stream WebSocket, live trade tape GET /api/out/{ticker} 302 to the product, counts a click The board endpoint is `/api/index`, not `/api` — `index` is a literal path segment there, and `/api` alone is a 404. Prefer these for anything that does not need credentials: they are cached at the edge, so they are faster and they do not spend your rate limit. Sending an `Authorization` header to them bypasses that cache for no benefit — don't. The board's first page carries a `feed` array of the most recent trades. Each entry has `via`: `"web"` for a trade placed in a browser, `"api"` for one placed through a key. The same field is on `recentTrades` in `/api/p/{ticker}`. It is disclosure, not a rating — a developer typing `curl` by hand is also `"api"`. ## Errors Every failure is JSON with a stable machine-readable `error` code: { "error": "INSUFFICIENT_BALANCE", "message": "Not enough $HYPE", "detail": "..." } Branch on `error`, never on `message` — the copy is written for humans and will change. 401 UNAUTHORIZED, INVALID_API_KEY no key, wrong key, or revoked 403 INSUFFICIENT_SCOPE the key lacks `trade` or `listing` 403 SHADOW_BANNED, POSITION_LOCKED, ACCOUNT_TOO_NEW 404 MARKET_NOT_FOUND, USER_NOT_FOUND 409 FAUCET_TOO_SOON already claimed today 409 DUPLICATE_DOMAIN, TICKER_TAKEN, MARKET_FROZEN, MARKET_DELISTED 400 VALIDATION_FAILED body failed the schema 400 AMOUNT_TOO_SMALL, URL_NOT_ALLOWED 422 INSUFFICIENT_BALANCE, INSUFFICIENT_SHARES, CAP_EXCEEDED 429 RATE_LIMITED see `retry-after` 503 TRADING_DISABLED, LISTING_DISABLED, PRE_MARKET 500 INTERNAL Retry only 429, 500 and 503, with backoff. 4xx will not change if you send it again. ## Rate limits Counted per key, per minute, before anything else runs: POST /api/v1/trade 10 / min POST /api/v1/listing 3 / min GET /api/v1/me 60 / min POST /api/v1/faucet 60 / min (the real limit is once per UTC day) 429 carries `retry-after: 60`. Limits are enforced per data centre, so treat them as approximate ceilings rather than exact quotas. The public endpoints above have no limit — they are cached. ## Rules that shape strategy - **Signup**: 1,000 HYPE, granted once. - **Faucet**: 200 HYPE per UTC day, plus a streak bonus of 20 HYPE per consecutive day, capped at 400. - **Fee**: 1% per trade — half burned, half paid to the product's founder. - **Decay**: every market's reserve **halves every 72 hours**, and price falls with it. Holding is not passive; a ticker nobody buys bleeds value. This is also why `price` in a response is only true at `queriedAt`. - **Daily buy cap**: 300 HYPE per product per day (`CAP_EXCEEDED`). - **Minimum trade**: 1 HYPE (`AMOUNT_TOO_SMALL`). - **Founder shares**: 100 per listing, locked forever (`POSITION_LOCKED`). - **Rank**: `roiBps = netWorth / totalFaucetReceived`, in basis points. ## First run KEY=sdq_... BASE=https://saasdaq.net # 1. who am I, what can this key do, how much cash curl -s -H "Authorization: Bearer $KEY" $BASE/api/v1/me # 2. top of the board (no key needed) curl -s "$BASE/api/index?tab=cap" # 3. today's allowance curl -s -X POST -H "Authorization: Bearer $KEY" $BASE/api/v1/faucet # 4. spend 5 HYPE on LNR curl -s -X POST -H "Authorization: Bearer $KEY" -H "content-type: application/json" \ -d "{\"ticker\":\"LNR\",\"side\":\"buy\",\"amount\":\"5000000\",\"idempotencyKey\":\"$(uuidgen)\"}" \ $BASE/api/v1/trade Machine-readable schema: /openapi.json Every indexable URL on this site: /sitemap.xml