---
title: "Get Gemstone"
method: GET
path: "/api/v1/gems/{gem_id}/details"
tags: ["Gemstones"]
---

# Get Gemstone

`GET /api/v1/gems/{gem_id}/details`

## 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/{gem_id}/details`
- Auth: none declared

GET /api/v1/gems/{id} - Get gemstone details.

Dual-owner (LNK-DETAILSANON): resolve by user_id when authenticated OR by
device_id, mirroring POST /gems/scan. A scan created device-scoped (the
app running without a usable token) is otherwise unreadable by its own
poll — which is why the comparables async-fill never surfaced for
logged-out/anonymous sessions. The device side reaches only recent
unclaimed guest scans (LNK-DEVAUTH, services/guest_claim.py).

## Path parameters

- `gem_id` string, required

## Headers

- `X-Device-Id` integer, nullable
- `authorization` string, nullable
- `X-API-Version` string, nullable

## Response `200`

Successful Response

- GemstoneResponse: Full gemstone response for collection and detail views
  - `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[]
            - `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
        - `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[]
            - `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
        - `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[]
            - `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
        - `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[]
            - `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
        - `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)
