openapi: 3.1.0

info:
  title: VaultFolio API
  version: 1.0.0
  description: |
    VaultFolio platform API — cert lookup, valuations, enrichment, and live market data
    for physical collectibles across trading cards, coins, vinyl, books, art, gaming, and crypto.

    **Base URL:** https://api.vaultfolio.ai

    **Authentication:** Internal routes require `x-api-key` header.
    Public cert lookup endpoints require no authentication.

  contact:
    name: VaultFolio
    email: luke@vaultfolio.ai
    url: https://vaultfolio.ai

servers:
  - url: https://api.vaultfolio.ai
    description: Production
  - url: http://localhost:3000
    description: Local development

tags:
  - name: Health
    description: Service health check
  - name: Cert Lookup
    description: Grading certificate verification — PSA, BGS, PCGS, BAS
  - name: Identification
    description: Item identification — VIN decode, ISBN, barcode
  - name: Pricing
    description: Market pricing — games, LEGO, art, crypto, vinyl
  - name: Cache
    description: Cert cache management

# ── Security ────────────────────────────────────────────────────────────────
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

  schemas:

    CertResult:
      type: object
      properties:
        cert_number:   { type: string, example: "25503398" }
        grader:        { type: string, enum: [psa, bgs, pcgs, bas, cgc, unknown] }
        grade:         { type: string, nullable: true, example: "PSA 10" }
        grade_numeric: { type: number, nullable: true, example: 10 }
        name:          { type: string, nullable: true, example: "CHARIZARD" }
        year:          { type: integer, nullable: true, example: 1999 }
        brand:         { type: string, nullable: true, example: "Wizards of the Coast" }
        set_name:      { type: string, nullable: true, example: "Base Set" }
        population:    { type: integer, nullable: true, example: 121 }
        source:        { type: string, example: "psa_official_api" }
        cached:        { type: boolean }

    BASCert:
      type: object
      properties:
        cert_number:   { type: string }
        grader:        { type: string, enum: [bas] }
        authenticated: { type: boolean }
        description:   { type: string, nullable: true }
        athlete:       { type: string, nullable: true }
        item_type:     { type: string, nullable: true }
        year:          { type: integer, nullable: true }
        team:          { type: string, nullable: true }
        grade:         { type: string, nullable: true }
        image_url:     { type: string, nullable: true }
        source:        { type: string }
        cached:        { type: boolean }

    PCGSCert:
      type: object
      properties:
        cert_number:       { type: string }
        grader:            { type: string, enum: [pcgs] }
        grade:             { type: string, nullable: true, example: "PCGS MS 65" }
        grade_numeric:     { type: number, nullable: true }
        name:              { type: string, nullable: true, example: "Morgan Dollar" }
        year:              { type: integer, nullable: true }
        mint_mark:         { type: string, nullable: true, example: "S" }
        denomination:      { type: string, nullable: true, example: "$1" }
        population:        { type: integer, nullable: true }
        population_higher: { type: integer, nullable: true }
        pcgs_no:           { type: string, nullable: true }
        designation:       { type: string, nullable: true }
        source:            { type: string }
        cached:            { type: boolean }

    VINResult:
      type: object
      properties:
        vin:    { type: string }
        make:   { type: string, nullable: true }
        model:  { type: string, nullable: true }
        year:   { type: integer, nullable: true }
        name:   { type: string, example: "1993 Toyota Supra" }
        item_fields:
          type: object
          description: Pre-filled vault item fields

    GamePrice:
      type: object
      properties:
        configured:   { type: boolean }
        found:        { type: boolean }
        product-name: { type: string, nullable: true }
        console-name: { type: string, nullable: true }
        loose-price:  { type: integer, nullable: true, description: "Price in cents USD" }
        cib-price:    { type: integer, nullable: true }
        new-price:    { type: integer, nullable: true }
        graded-price: { type: integer, nullable: true }
        bgs-10-price: { type: integer, nullable: true }

    LegoSet:
      type: object
      properties:
        configured:       { type: boolean }
        found:            { type: boolean }
        set_number:       { type: string, example: "75192-1" }
        name:             { type: string, nullable: true }
        year:             { type: integer, nullable: true }
        theme:            { type: string, nullable: true }
        pieces:           { type: integer, nullable: true }
        minifigs:         { type: integer, nullable: true }
        retail_price_usd: { type: number, nullable: true }
        retail_price_gbp: { type: number, nullable: true }
        image_url:        { type: string, nullable: true }
        brickset_url:     { type: string, nullable: true }

    ArtsyArtwork:
      type: object
      properties:
        configured:  { type: boolean }
        found:       { type: boolean }
        title:       { type: string, nullable: true }
        artist:      { type: string, nullable: true }
        year:        { type: string, nullable: true }
        medium:      { type: string, nullable: true }
        dimensions:  { type: string, nullable: true }
        image_url:   { type: string, nullable: true }
        artsy_url:   { type: string, nullable: true }

    Book:
      type: object
      properties:
        found:          { type: boolean }
        isbn:           { type: string }
        title:          { type: string, nullable: true }
        authors:        { type: array, items: { type: string } }
        publisher:      { type: string, nullable: true }
        publish_year:   { type: integer, nullable: true }
        edition:        { type: string, nullable: true }
        pages:          { type: integer, nullable: true }
        subjects:       { type: array, items: { type: string } }
        cover_url:      { type: string, nullable: true }
        openlibrary_url: { type: string, nullable: true }

    VinylRelease:
      type: object
      properties:
        found:            { type: boolean }
        title:            { type: string, nullable: true }
        artist:           { type: string, nullable: true }
        label:            { type: string, nullable: true }
        year:             { type: integer, nullable: true }
        format:           { type: string, nullable: true, example: "12\" Vinyl" }
        country:          { type: string, nullable: true }
        genre:            { type: string, nullable: true }
        tracklist:        { type: array, items: { type: string } }
        cover_url:        { type: string, nullable: true }
        musicbrainz_id:   { type: string, nullable: true }

    CryptoPrice:
      type: object
      properties:
        found:          { type: boolean }
        coin_id:        { type: string, example: "bitcoin" }
        symbol:         { type: string, example: "BTC" }
        name:           { type: string, example: "Bitcoin" }
        price_usd:      { type: number }
        price_aud:      { type: number }
        market_cap_usd: { type: number, nullable: true }
        volume_24h_usd: { type: number, nullable: true }
        change_24h_pct: { type: number, nullable: true }
        change_7d_pct:  { type: number, nullable: true }
        last_updated:   { type: string, format: date-time }
        image_url:      { type: string, nullable: true }

    CacheStats:
      type: object
      properties:
        total_cached:       { type: integer }
        psa_api_configured: { type: boolean }
        bgs_api_configured: { type: boolean }

    Error:
      type: object
      properties:
        error: { type: string }

# ── Paths ────────────────────────────────────────────────────────────────────
paths:

  /health:
    get:
      summary: Service health check
      tags: [Health]
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: "ok" }
                  version: { type: string }

  # ── Cert Lookup ────────────────────────────────────────────────────────────

  /api/v1/cert/psa:
    post:
      summary: PSA cert lookup
      description: |
        Look up a PSA-graded card by cert number or barcode.
        Accepts PSA QR code URLs (psacard.com/cert/XXXXXXXX).
        Three-tier lookup: cert_cache → PSA official API → eBay title parse.
        **Live — PSA_API_KEY active. 100 lookups/day free tier.**
      tags: [Cert Lookup]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                cert_number: { type: string, example: "25503398" }
                barcode:     { type: string, description: "PSA Code 39 barcode value" }
      responses:
        "200":
          description: Cert lookup result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CertResult' }
              example:
                cert_number: "25503398"
                grader: psa
                grade: "PSA 10"
                grade_numeric: 10
                name: "CHARIZARD"
                year: 1999
                brand: "Wizards of the Coast"
                set_name: "Base Set"
                population: 121
                source: psa_official_api
                cached: false

  /api/v1/cert/bgs:
    post:
      summary: BGS cert lookup
      description: |
        Look up a BGS-graded card by cert number.
        No official Beckett API exists — uses cert_cache + eBay title parse fallback.
        **Active fallback — functional today.**
      tags: [Cert Lookup]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                cert_number: { type: string, example: "0013709120" }
                barcode:     { type: string }
      responses:
        "200":
          description: Cert lookup result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CertResult' }

  /api/v1/cert/pcgs:
    post:
      summary: PCGS coin cert lookup
      description: |
        Look up a PCGS-graded coin by cert number.
        Returns grade, denomination, mint mark, population data.
        **Requires PCGS_API_KEY — registration pending.**
      tags: [Cert Lookup]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                cert_number: { type: string, example: "12345678" }
      responses:
        "200":
          description: PCGS cert result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PCGSCert' }

  /api/v1/cert/bas:
    post:
      summary: Beckett Authentication Services (BAS) verify
      description: |
        Verify a BAS autograph/memorabilia certificate.
        Uses Beckett's public verification endpoint — no API key required.
        Cert format: alphanumeric prefix (e.g. A123456, AA1234567).
        **Live — no key needed.**
      tags: [Cert Lookup]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [cert_number]
              properties:
                cert_number: { type: string, example: "A123456" }
      responses:
        "200":
          description: BAS verification result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BASCert' }

  /api/v1/cert/cache-stats:
    get:
      summary: Cert cache statistics
      description: Returns total cached certs and API configuration status.
      tags: [Cache]
      responses:
        "200":
          description: Cache stats
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CacheStats' }

  # ── Identification ─────────────────────────────────────────────────────────

  /api/v1/cert/vin:
    post:
      summary: VIN decode
      description: |
        Decode a Vehicle Identification Number via NHTSA free API.
        Returns make, model, year, engine, series — ready to pre-fill vault item.
        **Live — no key required (NHTSA public API).**
      tags: [Identification]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [vin]
              properties:
                vin: { type: string, example: "JZA80-0012345" }
      responses:
        "200":
          description: VIN decode result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VINResult' }

  /api/v1/cert/book:
    get:
      summary: Book lookup by ISBN
      description: |
        Look up book metadata by ISBN-10 or ISBN-13 via Open Library.
        Returns title, authors, publisher, year, cover image.
        **Live — no key required.**
      tags: [Identification]
      parameters:
        - in: query
          name: isbn
          required: true
          schema: { type: string }
          example: "9780743273565"
          description: ISBN-10 or ISBN-13 (dashes optional)
      responses:
        "200":
          description: Book metadata
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Book' }
              example:
                found: true
                isbn: "9780743273565"
                title: "The Great Gatsby"
                authors: ["F. Scott Fitzgerald"]
                publisher: "Scribner"
                publish_year: 1925
                cover_url: "https://covers.openlibrary.org/b/id/..."

  /api/v1/cert/vinyl:
    get:
      summary: Vinyl + album lookup
      description: |
        Look up vinyl record metadata via MusicBrainz.
        Search by barcode (EAN/UPC on sleeve) or artist + album title.
        Returns title, artist, label, year, format, tracklist, cover art.
        **Live — no key required.**
      tags: [Identification]
      parameters:
        - in: query
          name: barcode
          schema: { type: string }
          example: "5099749519823"
          description: EAN-13 or UPC barcode from vinyl sleeve
        - in: query
          name: q
          schema: { type: string }
          example: "pink floyd dark side of the moon"
          description: Artist + album title search query
      responses:
        "200":
          description: Vinyl release metadata
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VinylRelease' }
              example:
                found: true
                title: "The Dark Side of the Moon"
                artist: "Pink Floyd"
                label: "Harvest"
                year: 1973
                format: "12\" Vinyl"
                country: "GB"

  # ── Pricing ────────────────────────────────────────────────────────────────

  /api/v1/cert/game:
    get:
      summary: Retro game price lookup
      description: |
        Look up retro video game prices via PriceCharting.
        Returns loose, CIB, graded, and sealed prices in cents USD.
        Also covers trading card prices by grade tier (PSA/BGS/CGC).
        **Live — PRICECHARTING_TOKEN active.**
      tags: [Pricing]
      parameters:
        - in: query
          name: q
          schema: { type: string }
          example: "earthbound super nintendo"
          description: Game title + platform
        - in: query
          name: upc
          schema: { type: string }
          description: UPC barcode
        - in: query
          name: condition
          schema: { type: string, enum: [loose, cib, new, graded], default: loose }
      responses:
        "200":
          description: Game price data
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GamePrice' }
              example:
                configured: true
                found: true
                product-name: "EarthBound"
                console-name: "Super Nintendo"
                loose-price: 38500
                cib-price: 269221
                graded-price: 947700

  /api/v1/cert/lego:
    get:
      summary: LEGO set data + retail pricing
      description: |
        Look up LEGO set metadata and retail prices via Brickset API.
        Returns name, year, theme, piece count, minifigs, US/UK retail prices, image.
        **Requires BRICKSET_API_KEY — registration pending.**
      tags: [Pricing]
      parameters:
        - in: query
          name: set
          required: true
          schema: { type: string }
          example: "75192"
          description: LEGO set number (with or without variant suffix)
      responses:
        "200":
          description: LEGO set data
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LegoSet' }

  /api/v1/cert/art:
    get:
      summary: Fine art identification + pricing
      description: |
        Look up artwork metadata via Artsy API.
        Returns title, artist, year, medium, dimensions, auction history, image.
        **Requires ARTSY_CLIENT_ID + ARTSY_CLIENT_SECRET — registration pending.**
      tags: [Pricing]
      parameters:
        - in: query
          name: q
          required: true
          schema: { type: string }
          example: "andy warhol marilyn"
          description: Artist + artwork title search query
      responses:
        "200":
          description: Artwork data
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ArtsyArtwork' }

  /api/v1/cert/crypto:
    get:
      summary: Live crypto asset prices
      description: |
        Fetch live cryptocurrency prices via CoinGecko.
        Returns USD + AUD prices, 24h/7d change, market cap, volume.
        Supports single coin by ID, search by name, or batch of up to 20 coins.
        **Live — no key required (50 req/min free tier).**
      tags: [Pricing]
      parameters:
        - in: query
          name: id
          schema: { type: string }
          example: "bitcoin"
          description: CoinGecko coin ID
        - in: query
          name: q
          schema: { type: string }
          example: "ethereum"
          description: Search by coin name or symbol
        - in: query
          name: ids
          schema: { type: string }
          example: "bitcoin,ethereum,solana"
          description: Comma-separated list of CoinGecko IDs (max 20)
      responses:
        "200":
          description: Crypto price data
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CryptoPrice' }
              example:
                found: true
                coin_id: "bitcoin"
                symbol: "BTC"
                name: "Bitcoin"
                price_usd: 66877
                price_aud: 104123
                change_24h_pct: 2.4
                change_7d_pct: -1.2
