SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
17.5 KB · 98 lines typescript
Raw Blame History
1/** OpenAPI 3.1 document for the public API (§157). Served at /v1/openapi.json and written to docs/openapi.json by scripts/gen-openapi.ts. */23const paging = [4  { name: 'limit', in: 'query', schema: { type: 'integer', minimum: 1, maximum: 200, default: 50 } },5  { name: 'cursor', in: 'query', schema: { type: 'string' }, description: 'Opaque cursor from meta.cursor' },6  { name: 'format', in: 'query', schema: { type: 'string', enum: ['json', 'csv'] }, description: 'csv returns a flat export of the rows' },7];8const range = [9  { name: 'from', in: 'query', schema: { type: 'string', format: 'date' } },10  { name: 'to', in: 'query', schema: { type: 'string', format: 'date' } },11  { name: 'format', in: 'query', schema: { type: 'string', enum: ['json', 'csv'] } },12];13const envelopeOf = (ref: string, list = true) => ({14  type: 'object',15  properties: {16    data: list ? { type: 'array', items: { $ref: ref } } : { $ref: ref },17    meta: { $ref: '#/components/schemas/Meta' },18  },19});20const ok = (ref: string, list = true) => ({ 200: { description: 'OK', content: { 'application/json': { schema: envelopeOf(ref, list) } } } });21const errors = {22  400: { description: 'Invalid query', content: { 'application/problem+json': { schema: { $ref: '#/components/schemas/Problem' } } } },23  401: { description: 'Invalid API key', content: { 'application/problem+json': { schema: { $ref: '#/components/schemas/Problem' } } } },24  404: { description: 'Not found', content: { 'application/problem+json': { schema: { $ref: '#/components/schemas/Problem' } } } },25  429: { description: 'Rate limit or quota exceeded', content: { 'application/problem+json': { schema: { $ref: '#/components/schemas/Problem' } } } },26};2728export function openapiDocument() {29  return {30    openapi: '3.1.0',31    info: {32      title: 'RareIndex Public API',33      version: '1.0.0',34      description:35        'Structured market data for collectible assets: canonical assets, observed sales, live listings, price history, category markets and the RareIndex indices. Valuations are estimates with confidence and sample size; listing prices are not confirmed transactions. Attribution to RareIndex and the original sources is required.',36      contact: { name: 'RareIndex', url: 'https://www.rareindex.io/api-docs', email: 'api@rareindex.io' },37      termsOfService: 'https://www.rareindex.io/about#terms',38    },39    servers: [{ url: 'https://www.rareindex.io/api', description: 'Production (proxied by the web app)' }, { url: 'http://localhost:8211', description: 'Local API service' }],40    security: [{ bearerAuth: [] }, {}],41    tags: [42      { name: 'assets' },43      { name: 'markets' },44      { name: 'indices' },45      { name: 'sales' },46      { name: 'reference' },47      { name: 'auctions' },48    ],49    paths: {50      '/v1/assets/search': { get: { tags: ['assets'], summary: 'Search canonical assets', parameters: [{ name: 'q', in: 'query', schema: { type: 'string' }, description: 'Natural language query, e.g. "1999 Charizard PSA 10"' }, { name: 'category', in: 'query', schema: { type: 'string' }, description: 'Category or family slug' }, ...paging], responses: { ...ok('#/components/schemas/AssetSummary'), ...errors } } },51      '/v1/assets/{id}': { get: { tags: ['assets'], summary: 'Asset detail with variants, latest valuation and sources', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' }, description: 'Asset id (rare_…) or slug' }], responses: { ...ok('#/components/schemas/AssetDetail', false), ...errors } } },52      '/v1/assets/{id}/sales': { get: { tags: ['assets', 'sales'], summary: 'Observed sales for an asset', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }, { name: 'variant', in: 'query', schema: { type: 'string' } }, { name: 'include_flagged', in: 'query', schema: { type: 'boolean' } }, ...paging], responses: { ...ok('#/components/schemas/Sale'), ...errors } } },53      '/v1/assets/{id}/listings': { get: { tags: ['assets'], summary: 'Listings for an asset (asks, not transactions)', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }, { name: 'availability', in: 'query', schema: { type: 'string', enum: ['available', 'sold', 'ended', 'removed'], default: 'available' } }, ...paging], responses: { ...ok('#/components/schemas/Listing'), ...errors } } },54      '/v1/assets/{id}/depth': { get: { tags: ['assets'], summary: 'Market depth (asks around RIV, ±5/10/20 %, RareIndex spread), days on market, time-to-sale by asking band and fair buy/sell ladder — model estimates', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }, { name: 'variant', in: 'query', schema: { type: 'string' } }], responses: { ...ok('#/components/schemas/AssetDepth', false), ...errors } } },55      '/v1/assets/{id}/history': { get: { tags: ['assets'], summary: 'Daily price history (RIV, latest sale, median, volume, listings)', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }, { name: 'variant', in: 'query', schema: { type: 'string' } }, ...range], responses: { ...ok('#/components/schemas/PricePoint'), ...errors } } },56      '/v1/categories': { get: { tags: ['reference'], summary: 'Taxonomy', parameters: [paging[2]!], responses: { ...ok('#/components/schemas/Category'), ...errors } } },57      '/v1/indices': { get: { tags: ['indices'], summary: 'RARE and subindices with latest values and changes', parameters: [paging[2]!], responses: { ...ok('#/components/schemas/Index'), ...errors } } },58      '/v1/indices/{ticker}/history': { get: { tags: ['indices'], summary: 'Index daily history', parameters: [{ name: 'ticker', in: 'path', required: true, schema: { type: 'string' }, example: 'RARE-TCG' }, ...range], responses: { ...ok('#/components/schemas/IndexPoint'), ...errors } } },59      '/v1/markets': { get: { tags: ['markets'], summary: 'Category markets overview (latest snapshot per family)', parameters: [paging[2]!], responses: { ...ok('#/components/schemas/Market'), ...errors } } },60      '/v1/markets/{slug}': { get: { tags: ['markets'], summary: 'Category market detail: movers, most valuable, most liquid, recent sales, history', parameters: [{ name: 'slug', in: 'path', required: true, schema: { type: 'string' }, example: 'pokemon' }], responses: { ...ok('#/components/schemas/MarketDetail', false), ...errors } } },61      '/v1/trending': { get: { tags: ['markets'], summary: 'Trending assets', parameters: [{ name: 'category', in: 'query', schema: { type: 'string' } }, ...paging], responses: { ...ok('#/components/schemas/AssetSummary'), ...errors } } },62      '/v1/sales/latest': { get: { tags: ['sales'], summary: 'Latest observed sales across the platform', parameters: [{ name: 'category', in: 'query', schema: { type: 'string' } }, { name: 'min_usd', in: 'query', schema: { type: 'number' } }, ...paging], responses: { ...ok('#/components/schemas/SaleWithAsset'), ...errors } } },63      '/v1/assets/{id}/auctions': { get: { tags: ['assets', 'auctions'], summary: 'Auction lots for an asset with all-in (buyer-pays) assessment vs RIV', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }, { name: 'status', in: 'query', schema: { type: 'string', enum: ['live', 'upcoming', 'ended'] } }, ...paging], responses: { ...ok('#/components/schemas/AuctionLot'), ...errors } } },64      '/v1/auctions/lots': { get: { tags: ['auctions'], summary: 'Live/upcoming auction lots across houses with all-in bid or estimate vs RIV (§33–§35)', parameters: [{ name: 'status', in: 'query', schema: { type: 'string', enum: ['live', 'upcoming', 'ended'] } }, { name: 'ending_within_hours', in: 'query', schema: { type: 'integer', minimum: 1 } }, { name: 'category', in: 'query', schema: { type: 'string' } }, { name: 'house', in: 'query', schema: { type: 'string' } }, { name: 'below_riv', in: 'query', schema: { type: 'boolean' }, description: 'Only lots whose buyer-pays bid is ≥ 10 % below the variant RIV (verdict deal)' }, { name: 'asset', in: 'query', schema: { type: 'string' }, description: 'Asset id or slug' }, { name: 'sort', in: 'query', schema: { type: 'string', enum: ['ending', 'discount'] } }, ...paging], responses: { ...ok('#/components/schemas/AuctionLot'), ...errors } } },65      '/v1/records': { get: { tags: ['sales'], summary: 'Record sale per family (verified transactions only)', parameters: [paging[2]!], responses: { ...ok('#/components/schemas/SaleWithAsset'), ...errors } } },66      '/v1/stats': { get: { tags: ['reference'], summary: 'Platform counts', responses: { ...ok('#/components/schemas/Stats', false) } } },67    },68    components: {69      securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', description: 'API key `ri_live_…`. Requests without a key use the public tier (20 req/min).' } },70      schemas: {71        Meta: { type: 'object', properties: { count: { type: 'integer' }, cursor: { type: ['string', 'null'] }, as_of: { type: 'string', format: 'date-time' }, attribution: { type: 'string' } }, additionalProperties: true },72        Problem: { type: 'object', properties: { type: { type: 'string' }, title: { type: 'string' }, status: { type: 'integer' }, detail: { type: 'string' }, instance: { type: 'string' }, request_id: { type: 'string' } } },73        AssetSummary: {74          type: 'object',75          properties: {76            id: { type: 'string' }, slug: { type: 'string' }, title: { type: 'string' }, name: { type: 'string' }, category_slug: { type: 'string' }, family_slug: { type: 'string' }, brand: { type: ['string', 'null'] }, franchise: { type: ['string', 'null'] }, set_name: { type: ['string', 'null'] }, number: { type: ['string', 'null'] }, year: { type: ['integer', 'null'] }, variant: { type: ['string', 'null'] }, hero_image_url: { type: ['string', 'null'] },77            riv_usd: { type: ['number', 'null'], description: 'RareIndex Valuation (estimate)' }, riv_low_usd: { type: ['number', 'null'] }, riv_high_usd: { type: ['number', 'null'] }, riv_confidence: { type: ['number', 'null'] }, riv_sample_size: { type: 'integer' },78            latest_sale_usd: { type: ['number', 'null'] }, latest_sale_at: { type: ['string', 'null'] }, change_7d: { type: ['number', 'null'] }, change_30d: { type: ['number', 'null'] }, change_1y: { type: ['number', 'null'] }, sales_count: { type: 'integer' }, sales_30d: { type: 'integer' }, active_listings: { type: 'integer' }, min_ask_usd: { type: ['number', 'null'] }, liquidity_score: { type: ['number', 'null'] }, rarity_score: { type: ['number', 'null'] }, trending_score: { type: ['number', 'null'] },79          },80        },81        AssetDetail: { allOf: [{ $ref: '#/components/schemas/AssetSummary' }, { type: 'object', properties: { description: { type: ['string', 'null'] }, identifiers: { type: 'object' }, variants: { type: 'array', items: { type: 'object' } }, valuation: { type: ['object', 'null'] }, sources: { type: 'array', items: { type: 'object' } } } }] },82        Sale: { type: 'object', properties: { id: { type: 'string' }, source_id: { type: 'string' }, source_url: { type: 'string' }, sale_type: { type: 'string' }, sale_date: { type: 'string' }, price: { type: 'number' }, currency: { type: 'string' }, price_usd: { type: 'number' }, grader: { type: ['string', 'null'] }, grade: { type: ['string', 'null'] }, condition: { type: ['string', 'null'] }, status: { type: 'string' }, confidence: { type: 'number' } } },83        SaleWithAsset: { allOf: [{ $ref: '#/components/schemas/Sale' }, { type: 'object', properties: { asset_id: { type: 'string' }, asset_slug: { type: 'string' }, title: { type: 'string' } } }] },84        Listing: { type: 'object', properties: { id: { type: 'string' }, source_id: { type: 'string' }, source_url: { type: 'string' }, listing_type: { type: 'string' }, price: { type: ['number', 'null'] }, currency: { type: ['string', 'null'] }, price_usd: { type: ['number', 'null'] }, availability: { type: 'string' }, discount_to_riv: { type: ['number', 'null'] } } },85        PricePoint: { type: 'object', properties: { date: { type: 'string', format: 'date' }, riv_usd: { type: ['number', 'null'] }, latest_sale_usd: { type: ['number', 'null'] }, median_usd: { type: ['number', 'null'] }, sales_count: { type: 'integer' }, volume_usd: { type: ['number', 'null'] }, listings_count: { type: 'integer' }, min_ask_usd: { type: ['number', 'null'] } } },86        Category: { type: 'object', properties: { slug: { type: 'string' }, parent_slug: { type: ['string', 'null'] }, family_slug: { type: 'string' }, name: { type: 'string' }, level: { type: 'integer' }, phase: { type: 'integer' }, index_ticker: { type: ['string', 'null'] }, tracked_assets: { type: 'integer' } } },87        Index: { type: 'object', properties: { ticker: { type: 'string' }, name: { type: 'string' }, is_flagship: { type: 'boolean' }, as_of: { type: ['string', 'null'] }, value: { type: ['number', 'null'] }, published: { type: 'boolean' }, change_1d: { type: ['number', 'null'] }, change_7d: { type: ['number', 'null'] }, change_30d: { type: ['number', 'null'] }, change_ytd: { type: ['number', 'null'] }, change_1y: { type: ['number', 'null'] }, constituents_count: { type: ['integer', 'null'] }, transactions: { type: ['integer', 'null'] }, market_cap_est_usd: { type: ['number', 'null'] }, market_cap_confidence: { type: ['string', 'null'] } } },88        IndexPoint: { type: 'object', properties: { date: { type: 'string', format: 'date' }, value: { type: 'number' }, constituents_count: { type: 'integer' }, transactions: { type: 'integer' }, volume_usd: { type: ['number', 'null'] } } },89        Market: { type: 'object', properties: { slug: { type: 'string' }, name: { type: 'string' }, as_of: { type: ['string', 'null'] }, index_value: { type: ['number', 'null'] }, tracked_assets: { type: ['integer', 'null'] }, sales: { type: ['integer', 'null'] }, volume_usd: { type: ['number', 'null'] }, change_30d: { type: ['number', 'null'] } } },90        AuctionLot: { type: 'object', properties: { id: { type: 'string' }, auction_id: { type: 'string' }, auction_house: { type: 'string' }, auction_name: { type: ['string', 'null'] }, asset_id: { type: ['string', 'null'] }, asset_slug: { type: ['string', 'null'] }, asset_title: { type: ['string', 'null'] }, variant_id: { type: ['string', 'null'] }, lot_number: { type: ['string', 'null'] }, title: { type: 'string' }, url: { type: 'string' }, estimate_low: { type: ['number', 'null'] }, estimate_high: { type: ['number', 'null'] }, current_bid: { type: ['number', 'null'] }, hammer_price: { type: ['number', 'null'] }, currency: { type: ['string', 'null'] }, bid_count: { type: ['integer', 'null'] }, ends_at: { type: ['string', 'null'] }, status: { type: 'string' }, grader: { type: ['string', 'null'] }, grade: { type: ['string', 'null'] }, estimate_low_usd: { type: ['number', 'null'] }, estimate_high_usd: { type: ['number', 'null'] }, current_bid_usd: { type: ['number', 'null'] }, hammer_price_usd: { type: ['number', 'null'] }, fx_rate: { type: ['number', 'null'] }, fx_date: { type: ['string', 'null'] }, buyer_premium_rate: { type: ['number', 'null'], description: 'Effective buyer premium applied (0.27 = 27 %)' }, fee_basis: { type: ['string', 'null'], enum: ['included', 'added_published', 'added_approximate', 'added_default', 'none', 'unknown', null] }, all_in_bid_usd: { type: ['number', 'null'], description: 'Current bid + premium (only when ≥ 1 bid)' }, all_in_estimate_low_usd: { type: ['number', 'null'] }, all_in_estimate_high_usd: { type: ['number', 'null'] }, riv_usd: { type: ['number', 'null'], description: 'RIV of the lot’s variant at assessment time' }, bid_vs_riv: { type: ['number', 'null'], description: '(all-in bid − RIV) / RIV; negative = below RIV; null when ungated' }, estimate_vs_riv: { type: ['number', 'null'] }, assessment_verdict: { type: ['string', 'null'], enum: ['deal', 'fair', 'premium', 'review', 'anomaly', 'ungated', null] }, assessed_at: { type: ['string', 'null'] } } },91        AssetDepth: { type: 'object', properties: { asset_id: { type: 'string' }, variant_id: { type: ['string', 'null'] }, scope: { type: 'string', enum: ['variant', 'asset'] }, riv_usd: { type: ['number', 'null'] }, depth: { type: ['object', 'null'], properties: { asks: { type: 'integer' }, within5: { type: 'integer' }, within10: { type: 'integer' }, within20: { type: 'integer' }, belowRiv: { type: 'integer' }, aboveRiv: { type: 'integer' }, lowestAsk: { type: ['number', 'null'] }, medianAsk: { type: ['number', 'null'] }, askRivSpread: { type: ['number', 'null'], description: '(best ask − RIV) / RIV' } } }, liquidation: { type: ['object', 'null'], properties: { level: { type: 'string', enum: ['asset', 'category'] }, lifecycles: { type: 'integer' }, sold: { type: 'object' }, withdrawn: { type: 'object' }, bands: { type: 'array', items: { type: 'object' } }, sellThrough: { type: ['number', 'null'] } } }, fair_prices: { type: ['object', 'null'] }, note: { type: 'string' } } },92        MarketDetail: { type: 'object', properties: { category: { type: 'object' }, snapshot: { type: ['object', 'null'] }, counts: { type: 'object' }, gainers: { type: 'array', items: { $ref: '#/components/schemas/AssetSummary' } }, losers: { type: 'array', items: { $ref: '#/components/schemas/AssetSummary' } }, most_valuable: { type: 'array', items: { $ref: '#/components/schemas/AssetSummary' } }, most_liquid: { type: 'array', items: { $ref: '#/components/schemas/AssetSummary' } }, recent_sales: { type: 'array', items: { type: 'object' } }, history: { type: 'array', items: { type: 'object' } } } },93        Stats: { type: 'object', properties: { assets: { type: 'integer' }, sales: { type: 'integer' }, listings: { type: 'integer' }, sources: { type: 'integer' }, connectors: { type: 'integer' }, categories: { type: 'integer' } } },94      },95    },96  };97}98