---
title: "List User Recent Scans"
method: GET
path: "/api/v1/gems/recent-scans"
tags: ["Gemstones"]
---

# List User Recent Scans

`GET /api/v1/gems/recent-scans`

## Request

- Base URL: `https://api.gemstoneaiapp.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.gemstoneaiapp.com/api/v1/gems/recent-scans`
- Auth: HTTP bearer

GET /api/v1/gems/recent-scans
Get ALL recent scans by the authenticated user (both saved and unsaved).

This endpoint returns all gemstones scanned by the user, regardless of
whether they have been saved to the collection or not.

## Query parameters

- `page` integer
- `page_size` integer

## Headers

- `X-API-Version` string, nullable

## Response `200`

Successful Response

- GemListResponse
  - `total` integer, required
  - `page` integer, required
  - `page_size` integer, required
  - `gems` GemstoneResponse[], required
    - `id` integer, required
    - `user_id` integer, nullable
    - `scan_saved_to_collection` boolean
    - `main_heading` string, nullable
    - `sub_heading` string, nullable
    - `description` string, nullable
    - `estimated_value` union
      - EstimatedValue: Quick scan estimated value
        - `currency` string
        - `best` number, required
        - `range_min` number, required
        - `range_max` number, required
        - `bases` ValueBases: Retail replacement / fair market / resale for one piece [LNK-VALBASIS]. Additive and optional: pre-2.0.48 clients never read it and keep rendering estimated_value.best exactly as before.
          - `currency` string
          - `primary` string
          - `derivation` string
          - `bases` object, required
        - `pricing` PricingResult
          - `stage` 'initial' | 'researched'
          - `display_assumption` string, nullable
          - `version` string, required
          - `status` 'pending' | 'ready' | 'partial' | 'failed' | 'deferred', required
          - `subject_key` string, required
          - `currency` string
          - `priced_at` string, nullable
          - `subject` object
          - `purchase` PriceEstimate
            - `amount` number, nullable
            - `basis` string, required
            - `source` 'model' | 'asking_listings' | 'completed_sales' | 'unavailable'
            - `evidence_count` integer
            - `explanation` string
            - `gross_amount` number, nullable
            - `fee_rate` number, nullable
            - `fee_fixed` number, nullable
            - `fee_note` string, nullable
            - `status` 'available' | 'pending' | 'unsupported'
            - `range_min` number, nullable
            - `range_max` number, nullable
            - `range_kind` string, nullable
            - `method` string
            - `method_version` string, nullable
            - `as_of` string, nullable
            - `currency` string
            - `market` string
            - `condition` string, nullable
            - `subject_version` string, nullable
            - `evidence_ids` string[]
            - `freshness` string, nullable
            - `calculation_plan` object, nullable
            - `components` PriceComponent[]
              - …
          - `resale` PriceEstimate
            - `amount` number, nullable
            - `basis` string, required
            - `source` 'model' | 'asking_listings' | 'completed_sales' | 'unavailable'
            - `evidence_count` integer
            - `explanation` string
            - `gross_amount` number, nullable
            - `fee_rate` number, nullable
            - `fee_fixed` number, nullable
            - `fee_note` string, nullable
            - `status` 'available' | 'pending' | 'unsupported'
            - `range_min` number, nullable
            - `range_max` number, nullable
            - `range_kind` string, nullable
            - `method` string
            - `method_version` string, nullable
            - `as_of` string, nullable
            - `currency` string
            - `market` string
            - `condition` string, nullable
            - `subject_version` string, nullable
            - `evidence_ids` string[]
            - `freshness` string, nullable
            - `calculation_plan` object, nullable
            - `components` PriceComponent[]
              - …
          - `components` PriceComponent[]
            - `currency` string, nullable
            - `exposure` object, nullable
            - `component_id` string, nullable
            - `basis` string, nullable
            - `as_of` string, nullable
            - `evidence_ids` string[]
            - `movement_status` string, nullable
            - `change_amount` number, nullable
            - `reason` string, nullable
            - `label` string, required
            - `amount` number, required
            - `note` string, required
          - `evidence` object[]
          - `assumptions` string[]
          - `requested_inputs` string[]
          - `requested_attributes` string[]
          - `insurance_note` string
          - `valuation_id` string, nullable
          - `previous_valuation` object, nullable
        - `headline_basis` string, nullable
      - string
    - `valuation_basis` object, nullable
    - `stone_quantity` integer, nullable
    - `observed_colors` object[], nullable
    - `gem_species` string, nullable
    - `gem_species_confidence` integer, nullable
    - `top_3_candidates` unknown[], nullable
      - unknown
    - `jewelry_type` string, nullable
    - `metal_type` string, nullable
    - `metal_detected` boolean
    - `gemstone_detected` boolean
    - `loose_gem` boolean
    - `value_trend` string, nullable
    - `primary_stone` string, nullable
    - `scan_quality` string, nullable
    - `alternatives` AlternativeIdentification[], nullable
      - `name` string, required
      - `confidence` integer, required
    - `origin_estimate` OriginEstimate: AI-estimated geographic origin with confidence
      - `likely_origin` string
      - `confidence` integer
      - `basis` string, nullable
    - `clarity_estimate` ClarityEstimate: AI-estimated clarity grade from photos
      - `grade` string
      - `confidence` integer
    - `treatment_estimate` TreatmentEstimate: AI-estimated treatment status with species-aware defaults
      - `status` string
      - `confidence` integer
    - `dimensions_estimate` DimensionsEstimate: AI-estimated physical dimensions from image proportions
      - `length_mm` number, nullable
      - `width_mm` number, nullable
      - `depth_mm` number, nullable
      - `confidence` integer
    - `transparency_estimate` string, nullable
    - `carat_estimate` CaratEstimate: Structured carat weight estimate from range. Two producers write this column and they do NOT use the same key names (LNK-CARATRANGE, 2026-08-21). The legacy quick-scan path emits {low, high, best, confidence}; scan_v2 emits {length_mm, width_mm, depth_mm, best, range_min, range_max}. Because pydantic ignores unknown keys, every scan_v2 row deserialized here lost its range entirely and shipped low=0/high=0 — 100% of the last 500 scans audited 2026-08-21. `best` survived only because the key name happens to match. Both spellings are now carried and kept in sync by `normalize_carat_estimate`, so `GET /gems/{id}/details` returns the same range the inline scan response did. `confidence` stays 0 when the producer did not emit one — scan_v2 has no carat confidence and inventing one would be worse than admitting we lack it.
      - `low` number
      - `high` number
      - `best` number
      - `confidence` integer
      - `range_min` number, nullable
      - `range_max` number, nullable
    - `carat_scope` string, nullable
    - `total_carat_weight` unknown
    - `watch` object, nullable
    - `cut_shape` string, nullable
    - `color_grade` string, nullable
    - `color_grade_confidence` integer, nullable
    - `clarity_grade` string, nullable
    - `clarity_grade_confidence` integer, nullable
    - `cut_grade` string, nullable
    - `cut_grade_confidence` integer, nullable
    - `origin_grade` string, nullable
    - `origin_grade_confidence` integer, nullable
    - `treatment_grade` string, nullable
    - `treatment_grade_confidence` integer, nullable
    - `color_profile` ColorProfile: Primary gemstone color profile
      - `color` string, required
    - `jewelry_specifications` JewelrySpecifications: Jewelry mounting and design specifications
      - `metal` MetalSpec[], nullable
        - `type` string, required
        - `color` string, required
      - `size` string, nullable
      - `setting` string, nullable
      - `width` string, nullable
    - `stone_specifications` StoneSpec[], nullable
      - `stone` string, required
      - `quantity` integer, required
      - `carat` string, required
      - `cut` string, required
      - `color` string, required
      - `clarity` string, nullable
    - `about` AppSchemasGemstoneAbout: Educational content about the gemstone
      - `history_and_lore` string, required
      - `cultural_significance` string, required
    - `market_insights` AppSchemasGemstoneMarketInsights: Market-level insights
      - `trend` string, required
      - `rarity` string, required
      - `supply` string, required
    - `value_breakdown` unknown
    - `authenticity` unknown
    - `comparables` unknown[], nullable
      - unknown
    - `scan_version` integer, nullable
    - `analysis` unknown
    - `composition` unknown
    - `value` unknown
    - `confidence` integer, nullable
    - `specimen_form` string, nullable
    - `id_agreement` string, nullable
    - `photo_guidance` string, nullable
    - `capture_issue` string, nullable
    - `stage1_species` string, nullable
    - `final_species` string, nullable
    - `deep_scan_status` string
    - `deep_scan_started_at` string, date-time, nullable
    - `deep_scan_completed_at` string, date-time, nullable
    - `brand` string, nullable
    - `product_line` string, nullable
    - `brand_source` string, nullable
    - `brand_confidence` number, nullable
    - `brand_evidence` string, nullable
    - `images` GemstoneImageResponse[]
      - `id` integer, required
      - `image_url` string, required
      - `thumb_url` string, nullable
    - `scan_accuracy_feedback` boolean, nullable
    - `market` GemMarketBlock: Additive market-drift block on saved-gem responses (LNK-DRIFT). current_value = scan-time anchor (estimated_value.best) × variety index drift since the scan date. The whole block is null when the gem's variety isn't tracked by the price index; inside the block each field is independently nullable (current_value is null when the gem has no anchor). index_confidence: "high" | "medium" | "low".
      - `current_value` number, nullable
      - `change_pct_since_scan` number, nullable
      - `change_pct_7d` number, nullable
      - `index_confidence` string, nullable
      - `variety` string, nullable
      - `change_1d_pct` number, nullable
      - `change_7d_pct` number, nullable
      - `confidence_tier` string, nullable
    - `investment` GemInvestmentBlock: Additive per-gem "investment" block (LNK-MKT2). The whole block is null when the user never set a purchase price. unrealized_gain_usd is null when the gem has no usable est value; unrealized_gain_pct additionally when the purchase price is 0.
      - `purchase_price_usd` number, required
      - `purchase_date` string, date, nullable
      - `unrealized_gain_usd` number, nullable
      - `unrealized_gain_pct` number, nullable
    - `records` GemRecordsBlock: Additive per-gem "records" block — facts the OWNER stated about their own piece. Null block when they have recorded nothing. Deliberately separate from the *_estimate fields: these are never AI output and a rescan never overwrites them. The piece title is not part of this block — PUT /gems/{id} (LNK-GEMRENAME) owns renaming and writes main_heading.
      - `length_mm` number, nullable
      - `width_mm` number, nullable
      - `depth_mm` number, nullable
      - `carat` number, nullable
      - `stone_quantity` integer, nullable
      - `carat_scope` 'per_stone' | 'total', nullable
      - `lab_name` string, nullable
      - `certificate_number` string, nullable
      - `notes` string, nullable
      - `reference_number` string, nullable
      - `watch_year` integer, nullable
      - `box_papers` 'full_set' | 'papers' | 'box' | 'none', nullable
      - `watch_condition` 'unworn' | 'excellent' | 'good' | 'worn', nullable
      - `updated_at` string, date-time, nullable
    - `identification` GemIdentificationBlock: Additive per-gem "identification" block (LNK-IDFIX). Null unless the owner has overruled the scan's species, so a client that does not know this block sees exactly what it sees today. `ai_species` / `ai_estimated_value` are what WE said before the correction, kept so the app can show the owner what they changed and so a correction never erases the evidence of the miss.
      - `species` string, required
      - `source` 'user', required
      - `off_catalog` boolean
      - `corrected_at` string, date-time, nullable
      - `ai_species` string, nullable
      - `ai_estimated_value` EstimatedValue: Quick scan estimated value
        - `currency` string
        - `best` number, required
        - `range_min` number, required
        - `range_max` number, required
        - `bases` ValueBases: Retail replacement / fair market / resale for one piece [LNK-VALBASIS]. Additive and optional: pre-2.0.48 clients never read it and keep rendering estimated_value.best exactly as before.
          - `currency` string
          - `primary` string
          - `derivation` string
          - `bases` object, required
        - `pricing` PricingResult
          - `stage` 'initial' | 'researched'
          - `display_assumption` string, nullable
          - `version` string, required
          - `status` 'pending' | 'ready' | 'partial' | 'failed' | 'deferred', required
          - `subject_key` string, required
          - `currency` string
          - `priced_at` string, nullable
          - `subject` object
          - `purchase` PriceEstimate
            - `amount` number, nullable
            - `basis` string, required
            - `source` 'model' | 'asking_listings' | 'completed_sales' | 'unavailable'
            - `evidence_count` integer
            - `explanation` string
            - `gross_amount` number, nullable
            - `fee_rate` number, nullable
            - `fee_fixed` number, nullable
            - `fee_note` string, nullable
            - `status` 'available' | 'pending' | 'unsupported'
            - `range_min` number, nullable
            - `range_max` number, nullable
            - `range_kind` string, nullable
            - `method` string
            - `method_version` string, nullable
            - `as_of` string, nullable
            - `currency` string
            - `market` string
            - `condition` string, nullable
            - `subject_version` string, nullable
            - `evidence_ids` string[]
            - `freshness` string, nullable
            - `calculation_plan` object, nullable
            - `components` PriceComponent[]
              - …
          - `resale` PriceEstimate
            - `amount` number, nullable
            - `basis` string, required
            - `source` 'model' | 'asking_listings' | 'completed_sales' | 'unavailable'
            - `evidence_count` integer
            - `explanation` string
            - `gross_amount` number, nullable
            - `fee_rate` number, nullable
            - `fee_fixed` number, nullable
            - `fee_note` string, nullable
            - `status` 'available' | 'pending' | 'unsupported'
            - `range_min` number, nullable
            - `range_max` number, nullable
            - `range_kind` string, nullable
            - `method` string
            - `method_version` string, nullable
            - `as_of` string, nullable
            - `currency` string
            - `market` string
            - `condition` string, nullable
            - `subject_version` string, nullable
            - `evidence_ids` string[]
            - `freshness` string, nullable
            - `calculation_plan` object, nullable
            - `components` PriceComponent[]
              - …
          - `components` PriceComponent[]
            - `currency` string, nullable
            - `exposure` object, nullable
            - `component_id` string, nullable
            - `basis` string, nullable
            - `as_of` string, nullable
            - `evidence_ids` string[]
            - `movement_status` string, nullable
            - `change_amount` number, nullable
            - `reason` string, nullable
            - `label` string, required
            - `amount` number, required
            - `note` string, required
          - `evidence` object[]
          - `assumptions` string[]
          - `requested_inputs` string[]
          - `requested_attributes` string[]
          - `insurance_note` string
          - `valuation_id` string, nullable
          - `previous_valuation` object, nullable
        - `headline_basis` string, nullable
    - `species_source` string, nullable
    - `species_shortlist` ShortlistOption[], nullable
      - `species` string, required
      - `label` string, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/gemstoneaiapp/apis/gemstoneai-api.md) · [All operations](https://skmtc.dev/gemstoneaiapp/apis/gemstoneai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/gemstoneaiapp/apis/gemstoneai-api/revisions/fcb441f55161?raw)
