1# RareIndex Public API (v1)23Base URL (production): `https://www.rareindex.io/api/v1` — the web app proxies `/api/v1/*` to the4Fastify service (`apps/api`, port `API_PORT`, default 8211). Direct access in development:5`http://localhost:8211/v1`. Machine-readable contract: `GET /v1/openapi.json` (also `docs/openapi.json`).67## Authentication & tiers89`Authorization: Bearer ri_live_<prefix>_<secret>`. Requests without a key use the **public** tier.10Only the SHA-256 hash of a key is stored (`api_keys.key_hash`); mint keys with1112```13pnpm --filter @rareindex/api create-key -- --name "Research desk" --tier professional [--user usr_…]14```1516| Tier | req/min | req/day |17|---|---|---|18| public (no key) | 20 | 500 |19| free | 60 | 1 000 |20| hobby | 120 | 10 000 |21| professional | 600 | 100 000 |22| research | 600 | 250 000 |23| enterprise | 3 000 | 5 000 000 |2425Rate-limit headers: `x-ratelimit-limit`, `x-ratelimit-remaining`, `x-ratelimit-reset`; every response26carries `x-request-id`. Usage is counted per key/day/endpoint in `api_usage`.2728## Conventions2930- Envelope: `{ "data": …, "meta": { "count", "cursor", "as_of", "attribution", … } }`.31- Pagination: `limit` (≤ 200) + opaque `cursor` (from `meta.cursor`, `null` when exhausted).32- Errors: RFC 9457 `application/problem+json` (`title`, `status`, `detail`, `instance`, `request_id`).33- Export: add `?format=csv` on list endpoints for a flat CSV download.34- Caching: read endpoints send `cache-control` and weak ETags; gzip/brotli negotiated.35- Money: `price` + `currency` are the native transaction values; `price_usd` uses the FX rate of the36 transaction date. Valuations (`riv_usd`, `riv_low_usd`, `riv_high_usd`) always come with37 `riv_confidence` and `riv_sample_size`. Listing prices are asks, never transactions.38- Attribution is required (see `meta.attribution`).3940## Endpoints4142| Method & path | Purpose |43|---|---|44| `GET /v1/assets/search?q&category&limit&cursor` | Search canonical assets (FTS + trigram + identifiers) |45| `GET /v1/assets/{idOrSlug}` | Asset detail: attributes, stats, variants with their own valuations, latest valuation, sources |46| `GET /v1/assets/{id}/sales?variant&include_flagged` | Observed sales (valid only by default) |47| `GET /v1/assets/{id}/listings?availability` | Listings (asks) with `discount_to_riv` |48| `GET /v1/assets/{id}/history?variant&from&to` | Daily series: RIV, latest sale, median, sales count, volume, listings, min ask |49| `GET /v1/assets/{id}/depth?variant` | Market depth (asks within ±5/10/20 % of RIV, below/above, best & median ask, RareIndex spread), days on market (sold vs withdrawn), median time-to-sale by asking band (≤ 90 %, 90–100 %, 100–110 %, > 110 % of RIV; null under 5 observations), fair buy/sell ladder. `liquidation.level` is `category` when the asset has < 10 completed lifecycles. Model estimates, not advice |50| `GET /v1/categories` | Taxonomy with tracked asset counts |51| `GET /v1/indices` | RARE + subindices: latest value, 1d/7d/30d/YTD/1y changes, breadth, market cap estimate + confidence; `published=false` until breadth threshold |52| `GET /v1/indices/{ticker}/history?from&to` | Index history |53| `GET /v1/markets` | Latest category snapshot per family |54| `GET /v1/markets/{slug}` | Market detail: counts, gainers/losers (≥ 3 sales), most valuable, most liquid, recent sales, history |55| `GET /v1/trending?category` | Assets ranked by trending score |56| `GET /v1/sales/latest?category&min_usd` | Latest observed sales across the platform |57| `GET /v1/records` | Highest verified sale per family (bundles and low-confidence excluded) |58| `GET /v1/stats` | Platform counts |59| `GET /healthz` | Liveness (DB ping) |6061## Internal endpoints (loopback only)6263`POST /internal/lookup { url }` and `GET /internal/lookup/sources` are used by the web Scanner to64resolve a marketplace URL through the connector framework (connector modules are loaded65dynamically, which the Next.js bundler cannot do). Auth: header `x-internal-token` =66HMAC-SHA256(`SESSION_SECRET`, `"internal"`), and the caller must be 127.0.0.1.6768## Running6970```71pnpm --filter @rareindex/api dev # tsx watch, port 821172pnpm --filter @rareindex/api start73pnpm --filter @rareindex/api test # fastify.inject integration tests against DATABASE_URL74pnpm --filter @rareindex/api exec tsx scripts/gen-openapi.ts # refresh docs/openapi.json75```7677Environment: `DATABASE_URL`, `API_PORT`, `API_HOST` (default 127.0.0.1 — put it behind the web proxy),78`SESSION_SECRET` (shared with the web app for internal calls), `FIRECRAWL_API_KEY`/`SCRAPFLY_API_KEY`79(URL lookups), `LOG_LEVEL`.80