Skip to main content
This guide is everything you need to render the two inspection panels that appear on lmnauto.com car-detail pages — using only the JSON returned by GET /v1/vehicles/{id}. It is intentionally exhaustive: a partner engineer should be able to build a pixel-faithful rendering from scratch using only this page plus the response.
Using React? Don’t build this from scratch. LMN ships a drop-in React package, @lmnauto/inspection-ui, that renders the entire inspection UI — grades, body-damage diagrams, checklist, accident/insurance, inspector notes — exactly as lmnauto.com does, for all six sources. You pass the GET /v1/vehicles/{id} payload and it does the mapping:
import { InspectionReport } from '@lmnauto/inspection-ui';

<InspectionReport vehicle={vehicle} />;
The mapping logic lives with LMN (who own the payload), so your UI stays correct as sources evolve — no per-source damage-code/panel-ID handling on your side. It’s zero-runtime-dependency (React peer only), themeable via one CSS variable, and every section is toggleable. The package is distributed privately (tarball or git) — contact LMN for access.This page remains the reference for non-React partners, custom rendering, or understanding the underlying data semantics.

Why a dedicated guide?

The inspection data is the densest, most source-variable part of the response. The unified shape covers six inspectors (Glovis, SK, AJ, Lotte, KCar, Encar dealer) that all expose different raw data, normalized into a single response contract. The shape is mostly consistent across sources, with a few documented asymmetries:
  • Shape asymmetries — auction and dealer both carry checklist[]; only dealer adds the dealer_inspection damage-count block. accident_history[] remains auction-only for now (dealer surfaces accident state via has_accident / accident_summary).
  • Value asymmetries — SK and Lotte return a mirrored diagram image_url (damage baked in) and AJ a blueprint base image; Glovis, K-Car, and dealer return null and need a client-rendered diagram from panels[]. Damage codes and panel IDs differ per source (see Damage Code Dictionary and Panel ID Catalog) — render-time partner code must tolerate the variations.
Getting the rendering right depends on understanding where these asymmetries surface in your JSON. If you only need the API reference (path, query params, error codes), see Vehicles. This page is the rendering companion.

1. Anatomy of the response

The detail endpoint returns these two top-level objects relevant to inspection:
JSON pathDrivesType
body_conditionThe car-diagram panel (“Body Inspection”)Object | null
inspection_reportThe grade-summary card (“Inspection Report” / ” Grade”)Object | null
Either can be null independently — feature-detect before rendering.
const { body_condition: bc, inspection_report: ir } = vehicle;
if (!bc && !ir) return <EmptyState text="Inspection report not yet available." />;
return (
  <>
    {ir && <InspectionReportCard data={ir} />}
    {bc && <BodyInspectionCard data={bc} />}
  </>
);

Why both can be null

Scenariobody_conditioninspection_report
Auction listing freshly scraped, inspector hasn’t filed report yetnullnull
Auction with grade filed but no panel damagepopulated (panels: [])populated
Dealer car where Encar has no inspection record (upstream 404)nullnull
Dealer car where Encar returned data but no panel damage (clean car)populated (panels: [])populated
Dealer car with both inspection metadata and panel damagepopulatedpopulated
The full report and the diagram are independent ingestion pipelines, so partial states are expected. Always feature-detect at the field level too — accident_cost_summary may be null even when inspection_report itself is populated.

2. Per-source field matrix

Every field below is part of the same response shape, but populated differently depending on inspection_report.source. Knowing which fields you can expect per source lets you build skeletons and empty states that don’t flicker.
FieldGlovisSKAJLotteKCarDealer (Encar)
body_condition.image_url✅ mirror✅ blueprint✅ mirror
body_condition.panels[]✅ ~12-20 entries✅ 4-8 entries✅ 6-14 entries✅ 4-10 entries✅ 4-12 entries✅ 0-10 entries
inspection_report.overall_grade"A/7""A/A""A/A""A/A""A/A"
inspection_report.grade_description✅ accident grade / 1-9 condition score✅ accident grade / exterior letter✅ frame grade / exterior letter (digit)✅ accident grade / exterior letter✅ accident grade / exterior letter"Encar dealer inspection"
inspection_report.accident_summary✅ (“No structural damage” or “Frame damage reported”)
inspection_report.has_accident
inspection_report.has_frame_damage
inspection_report.exterior_summary✅ “6 panels: 4× needs bodywork, 1× scratch”✅ short✅ short✅ short✅ short✅ N exterior damage cases
inspection_report.accident_reason✅ “Welded (W): rear door (L), trunk (L)”
inspection_report.category_grades[]✅ 5-8 categories✅ 3-5✅ 5-7✅ 3-5✅ 3-5[{frame},{exterior}] only
inspection_report.checklist[]17 items✅ limited (~5-8)up to 39 items✅ limited✅ limited✅ up to ~35 items (mechanical, EN-translated)
inspection_report.accident_history[][]
inspection_report.accident_cost_summary✅ when accidents present✅ when accidents presentnull
inspection_report.issues[][]
inspection_report.inspection_sheet_url✅ original sheet PDF✅ original sheet PDFnull
inspection_report.inspector_notes✅ EN-translated✅ EN-translated✅ EN-translated✅ EN-translated✅ EN-translated✅ EN-translated (live Google Translate)
inspection_report.dealer_inspectionabsentabsentabsentabsentabsent
Practical reading: Glovis and AJ are the most data-rich (full checklist + accident history + cost breakdown). SK / Lotte / KCar return enough to render a graded card but no checklist depth. Dealer (Encar) now returns the mechanical checklist[] (engine, transmission, steering, braking, electrical, …, EN-translated) alongside the dealer_inspection damage counts. Render whatever is non-empty; the renderer below handles all six.
Clean dealer cars now return a populated inspection_report. Previously a dealer car with no panel damage returned inspection_report: null (and the UI showed “No inspection report available”), even though the inspection existed and was simply clean. As of the change below, a successfully-fetched dealer inspection always returns a report — a clean car reads as zero damage with a populated mechanical checklist[]. inspection_report is still null only when the inspection genuinely doesn’t exist upstream (Encar 404) or the fetch failed transiently.

3. The Body Inspection card

The card titled “Body Inspection” on lmnauto.com. For live comparison, see any dealer listing at https://lmnauto.com/en/auction?source=retail&tab=encar_<id> — the worked example in §12 uses encar_41657860 (BMW M5 2019).

3.1 Visual structure

┌───────────────────────────────────────────────────────────────────┐
│ 📄  Body Inspection                                  View Report → │
│                                                                   │
│  ┌─────────────────────────┐    ┌─────────────────────────┐      │
│  │ 🛡  Frame Damage         │    │ ⚠  Exterior Damage       │      │
│  │  0                       │    │  1                       │      │
│  │ No structural damage     │    │ Minor damage             │      │
│  └─────────────────────────┘    └─────────────────────────┘      │
│                                                                   │
│                                              [x] Exchange         │
│  ┌─ Exterior Panels ──────┐    ┌─ Frame Structure ──────┐         │
│  │                        │    │                        │         │
│  │       [Top-down        │    │      [Top-down         │         │
│  │        car silhouette  │    │       chassis          │         │
│  │        with markers]   │    │       silhouette]      │         │
│  │                        │    │                        │         │
│  └────────────────────────┘    └────────────────────────┘         │
│                                                                   │
│  Rank 1   1 cases               Rank A   None                    │
│  Rank 2   None                  Rank B   None                    │
│                                  Rank C   None                    │
└───────────────────────────────────────────────────────────────────┘

3.2 Response shape

{
  "body_condition": {
    "image_url": null,
    "panels": [
      {
        "panel_id": "hood",
        "panel_type": "exterior",
        "damage_code": "CHANGE",
        "damage_codes": ["CHANGE"],
        "label": "Hood",
        "description": "Exchange",
        "severity": "high"
      },
      {
        "panel_id": "rear-bumper",
        "panel_type": "exterior",
        "damage_code": "SCRATCH",
        "damage_codes": ["SCRATCH"],
        "label": "Rear Bumper",
        "description": "Scratch",
        "severity": "low"
      }
    ]
  }
}

3.3 Field reference

FieldTypeMeaning
image_urlstring | nullHTTPS URL to the source’s mirrored diagram image — populated for SK and Lotte (damage baked in) and AJ (a blueprint base image drawn under the panel markers). null for Glovis, K-Car, and dealer — render those from panels[].
panels[].panel_idstringStable identifier — see the Panel ID Catalog below. Use as marker-position key.
panels[].panel_type"exterior" | "frame"Determines which of the two silhouettes the marker goes on.
panels[].damage_codestringThe primary damage code. See the Damage Code Dictionary.
panels[].damage_codesstring[]All damage codes for this panel — one panel may have multiple non-rank codes (e.g. welded + sheet-metalled). Order is most-significant first. Dealer-only caveat: when a panel has both a rank attribute (e.g. RANK_ONE) and a non-rank damage code (e.g. CHANGE) upstream, only the non-rank codes survive in this array; rank attrs are reflected in the aggregate dealer_inspection counts instead. See Damage Code Dictionary §4.6.
panels[].labelstringEnglish display label (e.g. “Hood”, “Front Door (L)”, “Rear Quarter Panel (R)”).
panels[].descriptionstringHuman-readable description of damage_code. Localized to English.
panels[].severity"high" | "medium" | "low" | "none"Roll-up severity for color coding. See the Severity Computation Rules. "none" only appears in rare cases where a panel is listed for completeness without actual damage.

3.4 Count derivation

Compute the two count boxes from the panels array:
const damagedPanels = body_condition.panels.filter(p => p.severity !== "none");

const frameCount = damagedPanels.filter(p => p.panel_type === "frame").length;
const exteriorCount = damagedPanels.filter(p => p.panel_type === "exterior").length;

const maxFrameSeverity = maxSeverity(damagedPanels.filter(p => p.panel_type === "frame"));
const maxExteriorSeverity = maxSeverity(damagedPanels.filter(p => p.panel_type === "exterior"));

function maxSeverity(panels) {
  const order = ["none", "low", "medium", "high"];
  return panels.reduce((max, p) => (order.indexOf(p.severity) > order.indexOf(max) ? p.severity : max), "none");
}

3.5 Status text per count box

CountMax severityFrame box textExterior box text
0“No structural damage""No exterior damage”
≥1low”Minor damage""Minor damage”
≥1medium”Repair history""Repair history”
≥1high”Major damage""Major damage”

3.6 Count box color

CountSeverityBackgroundText
0#ECFDF5 (emerald-50)#047857 (emerald-700)
≥1low#FEFCE8 (yellow-50)#A16207 (yellow-700)
≥1medium#FFFBEB (amber-50)#B45309 (amber-700)
≥1high#FEF2F2 (red-50)#B91C1C (red-700)

3.7 Rank breakdown (dealer-source only)

When inspection_report.source === "dealer", render a small summary beneath the diagrams using the server-computed totals in inspection_report.dealer_inspection:
const di = inspection_report.dealer_inspection;
// di.critical_frame_damage  → total count across RANK_A + RANK_B + RANK_C
// di.exterior_damage        → total count across RANK_ONE + RANK_TWO
// di.simple_repair          → boolean | null — Encar "단순수리" flag, null when not stated
// di.frame_status / di.panel_status → KO status string | null (only when the page carries a status row)
The mechanical condition of the car (engine, transmission, steering, braking, electrical, fuel) is in inspection_report.checklist[] for dealer cars too — each entry is { group, item, result }, already KO→EN translated. Render it with the same checklist component used for auction sources. Display under each diagram:
Under "Exterior Panels":   Total ranked panels — {di.exterior_damage}   (or "None" when 0)
Under "Frame Structure":   Total ranked panels — {di.critical_frame_damage}   (or "None" when 0)
Per-rank breakdown is available at dealer_inspection.rank_counts. To reproduce the reference UI’s “Rank 1 / Rank 2 / Rank A / Rank B / Rank C” lines, read dealer_inspection.rank_counts ({ rank_one, rank_two, rank_a, rank_b, rank_c }, integers, dealer-only). By construction rank_a + rank_b + rank_c === critical_frame_damage and rank_one + rank_two === exterior_damage, so the aggregate count fields above remain valid for older clients.Do not try to compute per-rank counts by scanning body_condition.panels[].damage_codes. When a panel has both a rank attribute and a regular damage code (the common case — RANK_ONE together with CHANGE), the rank attribute is dropped from damage_codes in favor of the more specific damage. Counting from damage_codes would systematically undercount — use rank_counts, which is tallied before that collapse.
For auction sources, omit this section — they don’t use rank vocabulary.

4. Damage Code Dictionary

The damage_code vocabulary is source-specific. Each inspector emits its own codes — Glovis uses lowercase mnemonics, SK/AJ/Lotte use uppercase letter codes, and the Encar dealer path uses descriptive uppercase tokens. The unified panel.severity and panel.description fields paper over this — those are normalized for display. Use damage_code for icon mapping and case-by-case handling; rely on description for textual rendering.
Practical rendering rule: Map damage_code → icon using the source-specific tables below. Render panel.description as the primary user-visible text. That way new codes that aren’t yet in your icon map degrade to “no icon + correct text” rather than dropping the panel.
The full set of codes you may receive, per inspection_report.source:

4.1 Glovis (source: "glovis")

Lowercase mnemonics derived from Korean damage type names.
CodeEnglishKorean originSeverity (server)Icon glyph
xxReplaced교환high× (white on red)
wSheet metal / Welding판금/용접, 용접mediumW (white on amber)
ppSheet metal repair판금mediumP (white on amber)
fBent / Crumpled꺾임mediumF (white on amber)
mAdjusted조정lowoutlined dot

4.2 SK (source: "sk")

Uppercase single- or double-letter codes.
CodeEnglishKorean originSeverity (server)Icon glyph
XXReplacement history교환이력high× (white on red)
XReplaced교환high× (white on red)
WWelded판금용접mediumW (white on amber)
QPanel work쿼터패널mediumQ (white on amber)
PPaint도장lowoutlined dot
MAdjusted조정lowoutlined dot
Codes G, G1, G2 may appear in raw upstream data but are filtered out before reaching the partner API — you will not see them.

4.3 AJ (source: "aj")

Uppercase letter codes. Shares some letters with SK (e.g. X, W) — never mix the two dictionaries; always switch on source first.
CodeEnglishKorean originSeverity (server)Icon glyph
XXReplacement history교환이력high× (white on red)
XReplaced교환high× (white on red)
WPanel / Weld판금/용접highW (white on amber)
AScratch상처lowoutlined dot
PPaint도장lowoutlined dot

4.4 Lotte (source: "lotte")

Lotte uses a code set similar to SK. Treat unknown codes by falling back to description text — the inspector’s free-text describes the damage in English.

4.5 KCar (source: "kcar")

Same fallback rule as Lotte — handle unknown codes gracefully via description. KCar emits a sparse, mostly-letter-code set; expect coverage to grow over time.

4.6 Dealer (Encar) (source: "dealer")

Descriptive uppercase tokens derived from Encar’s outers[].attributes[] raw values.
CodeEnglishSeverity (server)Notes
CHANGEReplaced (Exchange)highPanel was swapped out. Rendered with the × exchange overlay.
WELDWeldedmediumStructural repair via welding. See ‡ below.
METALSheet metal repairmediumBody work without replacement.
CORROSIONCorrosionmediumRust / oxidation.
SCRATCHScratchlowSurface scratch.
DAMAGEDamage (general)mediumCatch-all for unspecified damage.
HILLSDentlowEncar’s term for a dent / dimple. See ‡ below.
RANK_AFrame rank AhighFrame panel (most severe of A/B/C). See note below.
RANK_BFrame rank BhighFrame panel.
RANK_CFrame rank ChighFrame panel.
RANK_ONEExterior rank 1lowhighSeverity is set by the accompanying statusTypes (W → medium, X → high). Bare rank-only panels with no statusTypes upstream emit low.
RANK_TWOExterior rank 2lowhighSame rule as RANK_ONE — severity is set by the canonical damage code derived from statusTypes.
WELD and HILLS are recognized by the server’s severity and description logic for forward-compatibility, but are not currently produced by any upstream mapping. The dealer codes you can actually receive today are CHANGE, METAL, CORROSION, SCRATCH, DAMAGE, and the RANK_* attributes. WELD / HILLS are listed so your renderer tolerates them if they start appearing — fall back to panel.description for any code you don’t recognize.
Rank attrs are stripped from damage_codes when a non-rank code is also present. A panel with ["RANK_ONE", "CHANGE"] upstream emits damage_codes: ["CHANGE"] from this API — the rank attribute is collapsed in favor of the more specific damage code. You will only see RANK_ONE/RANK_TWO/RANK_A/RANK_B/RANK_C in damage_codes when it is the only code for that panel (i.e., when Encar reported a rank with no accompanying statusTypes entry — rare in practice).For per-rank breakdown, do not derive from damage_codes. Use the aggregate counts in inspection_report.dealer_inspection.{critical_frame_damage, exterior_damage}. See section 3.7.

4.7 Severity is server-computed — trust the emitted field

The severity field on each panel is set server-side and is the only signal you should use for color coding. Severity rules differ per source (each source has its own code-to-severity table at ingest time, e.g. Glovis maps wmedium while AJ maps Whigh), and frame panels may be promoted at the unified-mapping layer for some sources. The compounding rules are not stable contract — they may evolve as we ingest new inspectors. What is stable contract:
FieldStability
The set of possible severity values: "high" | "medium" | "low" | "none"Stable
The color you should pick for each valueStable (see Color tokens)
The per-source code-to-severity mapping at ingest timeNot stable — may evolve
Whether frame panels are auto-promotedNot stable — varies per source
Use panel.severity for color picking. Do not re-derive from damage_code or damage_codes — your local re-derivation will drift from server behavior as we tune the per-source tables, and you’ll either over-color (false alarms) or under-color (silent damage) edge cases. If you need to display a textual rationale alongside the marker, render panel.description — it is the localized English explanation of the panel’s damage state, regardless of source.

5. Panel ID Catalog

panel_id values use two different naming conventions depending on the source:
SourceConventionExample
Glovis, SK, AJ, Lotte, KCar (auction)kebab-casefront-door-l, front-wheel-house-r
Encar (dealer)camelCase, passed through from Encar’s raw outers[].partId / panelIdrearDoorRight, frontDoor, hood
The dealer values are not normalized — they’re whatever Encar’s API returns for that listing. We expose them verbatim so your client can correlate against Encar’s own UI if needed.
Build two PANEL_COORDS maps, one for kebab-case auction panels and one for camelCase dealer panels. Switching on inspection_report.source is the cleanest way to choose. We recognize this asymmetry is friction — a future API revision is planned to add canonical kebab-case IDs alongside the raw values. Until then, your client carries both maps.

5.1 Auction panel IDs (kebab-case)

This is the complete set currently emitted by the auction inspectors. New IDs may be added over time (additive); your client should fall back gracefully on unknowns (see the Note at the end of this section).

Exterior panels

panel_idEnglish labelMarker placement (top-down body view)
hoodHoodFront-center, just behind the front bumper
roofRoofCenter, above the cabin
trunkTrunk / TailgateRear-center, just ahead of the rear bumper
front-bumperFront BumperFront edge, full width
rear-bumperRear BumperRear edge, full width
front-door-l / front-door-rFront Door (L/R)Mid sides, ahead of B-pillar
rear-door-l / rear-door-rRear Door (L/R)Mid-rear sides, behind B-pillar
front-fender-l / front-fender-rFront Fender (L/R)Front sides, between bumper and door
rear-fender-l / rear-fender-rRear Fender (L/R)Rear sides, between door and bumper
side-sill-l / side-sill-rSide Sill (L/R)Lower body edge, full length under the doors
front-light-l / front-light-rFront Light (L/R)Headlamp positions on front fascia
rear-light-l / rear-light-rRear Light (L/R)Tail-lamp positions on rear fascia

Frame panels

panel_idEnglish labelMarker placement (top-down chassis view)
radiator-supportRadiator SupportFront-center, behind front panel
cross-memberCross MemberFront, transverse beam
front-side-member-l / front-side-member-rFront Side Member (L/R)Front longitudinal beams
side-member-l / side-member-rSide Member (L/R)Mid rocker / frame rails
side-member-frameSide Member (Frame)Truck-style frame rail when present
front-side-memberFront Side MemberSingle-rail front variant when present
rear-side-member-l / rear-side-member-rRear Side Member (L/R)Rear longitudinal beams
front-wheel-house-l / front-wheel-house-rFront Wheel House (L/R)Around front wheel arches
rear-wheel-house-l / rear-wheel-house-rRear Wheel House (L/R)Around rear wheel arches
wheel-house-l / wheel-house-rWheel House (L/R)Generic wheel-arch variant
a-pillar-l / a-pillar-rA-Pillar (L/R)Between hood and front door
b-pillar-l / b-pillar-rB-Pillar (L/R)Between front and rear doors
c-pillar-l / c-pillar-rC-Pillar (L/R)Between rear door and quarter
inside-panel-l / inside-panel-rInside Panel (L/R)Left/right longitudinal inner beams
rear-inside-panel-l / rear-inside-panel-rRear Inside Panel (L/R)Rear inner beams
dash-panelDash PanelMid, between engine bay and cabin
floor-panelFloor PanelCenter, full underbody
trunk-floorTrunk FloorRear, behind rear seats
rear-panelRear PanelRear-center under trunk
package-trayPackage TrayBehind rear seats, under rear glass
seat-back-panelSeat Back PanelVertical panel behind rear seats

5.2 Dealer panel IDs (camelCase, derived from Encar type.code)

Dealer responses come from Encar’s https://api.encar.com/v1/readside/inspection/vehicle/{id} endpoint. Each outers[] entry has a nested type.code (Encar’s panel identifier, e.g. P062) and a type.title (Korean label). The partner API reads these and emits:
  1. panel_id — translated via the internal Encar panel-code map to a canonical camelCase token (quarterPanelRight for P062, frontFenderLeft for P021). When the upstream type.code is not yet in the map, the raw code passes through verbatim ("P099") so partners still have a stable per-panel identifier.
  2. raw_panel_code — the unmodified Encar type.code value (e.g. "P062"). Always present when upstream provided it.
  3. raw_label_ko — the unmodified Encar type.title value (Korean, e.g. "쿼터 패널(우)"). Always present when upstream provided it.
  4. label — the English label from the map when canonical-mapped, otherwise the raw Korean title.
The canonical map is seeded with the codes observed in real responses and grows additively as new codes are encountered. Adding a code never breaks existing partner integrations.
Handling unknown panel_id values. If your PANEL_COORDS map does not contain the panel_id you receive (because it’s a raw P-code that we haven’t yet added to our canonical map, or a new code Encar emitted that we haven’t seen), fall back to listing the panel by label in a textual “Affected panels” list under the diagram. Use the raw_label_ko field if you want to display the Korean label alongside or instead. Do not drop the panel silently.A pattern that handles both canonical and raw passthrough:
const drawable = body_condition.panels.filter(p => PANEL_COORDS[p.panel_id]);
const textual  = body_condition.panels.filter(p => !PANEL_COORDS[p.panel_id]);
// Render `drawable` on the diagram, `textual` in a list below
// (with `p.label || p.raw_label_ko` as the display).
Synthetic panel_${index+1} IDs are rare today — they only appear when Encar’s response omits both type.code and all legacy flat fields, which is unusual. Most dealer panels you receive will have either a canonical camelCase ID or a raw P-code.
The canonical camelCase tokens you can expect to see (set grows as we add map entries):
Example panel_id (camelCase)Equivalent English label
hoodHood
frontDoor / frontDoorLeft / frontDoorRightFront Door (L/R)
rearDoor / rearDoorLeft / rearDoorRightRear Door (L/R)
frontFender / frontFenderLeft / frontFenderRightFront Fender (L/R)
rearFender / rearFenderLeft / rearFenderRightRear Fender (L/R)
trunkLidTrunk Lid
roofRoof
frontBumper / rearBumperFront / Rear Bumper
quarterPanel / quarterPanelLeft / quarterPanelRightQuarter Panel (L/R)
The exact set of camelCase IDs Encar returns varies by listing — we pass through verbatim rather than normalizing, so any value Encar produces is the value you receive. The English panel.label field carries a human-readable rendering when available; when it is not, it falls back to the raw panel_id string.

5.3 Catalog completeness and fallbacks

This catalog reflects the set of IDs currently in use as of the latest release. New IDs may be added (additive — never a breaking change) as inspector data formats evolve. Your client must handle unknown IDs gracefully:
If panel_id arrives that is not in your PANEL_COORDS map, do not drop the panel silently. Fall back to listing it by label in a textual “Affected panels” section under the diagram. The damage is real even when you don’t have a marker position for it; suppressing the entry hides information from your customer.A change-resilient pattern:
const known = body_condition.panels.filter(p => PANEL_COORDS[p.panel_id]);
const unknown = body_condition.panels.filter(p => !PANEL_COORDS[p.panel_id]);

// Render `known` on the diagram, `unknown` in a textual list below.

6. The Inspection Report card

The card titled “Inspection Report” or Grade” on lmnauto.com. For live comparison, see any auction listing at https://lmnauto.com/en/auction?source=auctions&tab=glovis_<date>_<auction_id>_<lot_id> — the worked example in §11 uses glovis_20260521_1078_1005 (Hyundai Sonata 2020).

6.1 Visual structure

┌───────────────────────────────────────────────────────────────────┐
│ Inspection Report  ⓘ                                          ⌄ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ┌────┐                                                       │ │
│ │ │A/A │  AJ Sellcar Grade               ✓ No Accident         │ │
│ │ └────┘                                                       │ │
│ │                                                              │ │
│ │ ┌────────────────────────────────────────────────────────┐  │ │
│ │ │ A  Accident Grade  —  No Accident                       │  │ │
│ │ └────────────────────────────────────────────────────────┘  │ │
│ │ ┌────────────────────────────────────────────────────────┐  │ │
│ │ │ A  Exterior Grade  —  4-5 panels                        │  │ │
│ │ │      This car: 2 panels need attention                  │  │ │
│ │ └────────────────────────────────────────────────────────┘  │ │
│ │                          Learn more about grading systems → │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│   ─── Checklist (auction + dealer, EN-translated) ───           │
│   • Engine — Oil leak: Good                                     │
│   • Brakes — Master cylinder: Good                              │
│   • …                                                           │
│                                                                 │
│   ─── Issues Found (when present) ───                           │
│   • Body — caution — Hood needs sheet-metal work                │
│                                                                 │
│   ─── Accident Cost (when present) ───                          │
│   Total: $4,231 USD (2 incidents)                               │
│     Parts: $2,140    Labor: $1,580    Paint: $511               │
│   Insurance paid: $3,890                                        │
│                                                                 │
│   ─── Inspector Notes (when present) ───                        │
│   > "Cosmetic only — no structural concerns."                   │
│                                                                 │
│   ─── Dealer Inspection (dealer only) ───                       │
│   Frame damage: 0      Exterior damage: 1                       │
│   Insurance: 0 claims  Frame: 이상없음   Panel: 교환 1             │
└───────────────────────────────────────────────────────────────────┘

6.2 Response shape

{
  "inspection_report": {
    "source": "aj",
    "overall_grade": "A/A",
    "grade_description": "Frame grade / Exterior letter grade (digit-converted)",
    "accident_summary": "No structural damage",
    "has_accident": false,
    "has_frame_damage": false,
    "exterior_summary": "4-5 panels: this car: 2 panels need attention",
    "accident_reason": null,
    "category_grades": [
      { "category": "frame", "status": "good" },
      { "category": "exterior", "status": "caution" }
    ],
    "checklist": [
      { "group": "Engine", "item": "Oil leak", "result": "양호" }
    ],
    "accident_history": [],
    "accident_cost_summary": null,
    "issues": [],
    "inspection_sheet_url": "https://.../aj_inspection_sheet.pdf",
    "inspector_notes": null
  }
}
This is an auction (source: "aj") response, so dealer_inspection is omitted entirely from the JSON — it would only appear when source === "dealer".

6.3 Field reference

FieldTypeMeaning
sourcestring"glovis" | "sk" | "aj" | "lotte" | "kcar" | "dealer". Drives the badge label (“Glovis Grade” / “SK Grade” / “AJ Sellcar Grade” / “Lotte Grade” / “KCar Grade” / “Encar Dealer Inspection”).
overall_gradestring | nullTwo-part grade rendered in the top-left badge. Examples below. null for dealer.
grade_descriptionstringOne-line explanation of what the two parts of overall_grade represent for this source. Render as a small italic line under the badge or as a tooltip on hover.
accident_summarystring | nullOne-line accident verdict. Display next to the Accident Grade row.
has_accidentbooleanDrives the green/red top-right pill.
has_frame_damagebooleanMore specific — distinguishes accident history from current frame damage (a repaired accident may leave has_accident=true but has_frame_damage=false). Useful if you want a separate visual signal for “structural risk” vs “any accident in the past”.
exterior_summarystring | nullOne-line exterior verdict. Display next to the Exterior Grade row.
accident_reasonstring | nullMore detailed accident description. Glovis and AJ only. Renders as a smaller secondary line under accident_summary.
category_grades[]arrayOne row per category (frame, exterior, engine, suspension, electrical, interior, etc.). Each { category, status }. See section 6.4 for status colors.
checklist[]arrayPer-item checklist. Each { group, item, result }. Populated for auction and dealer (encar_*) sources, EN-translated. See section 6.5.
accident_history[]arrayPer-incident records. Glovis + AJ. Each entry has structured incident fields (date, description, severity). See section 6.6.
accident_cost_summaryobject | null{ total_incidents, total_repair_cost, breakdown: { parts, labor, paint }, insurance_paid, incidents[] }. All values are USD whole integers. Already FX-converted server-side. incidents[] (Glovis; [] when the source has no per-incident breakdown) lists one entry per insurance-settled accident: { date: string | null, insurance_paid, repair_cost: number | null }. insurance_paid is the sum of incidents[].insurance_paid — i.e. the per-incident payouts always reconcile exactly to the total (rounding is applied per incident, then summed). Render as: accident count → per-incident payout → total.
issues[]arrayNormalized “issues found” entries from all sources. Each { category, status, detail, faults: string[], type }. See section 6.7.
inspection_sheet_urlstring | nullHTTPS link to the original inspection-sheet PDF. Populated for SK and AJ auction sources; null for all other sources, including dealer. Render as a “View original inspection sheet” link when present.
inspector_notesstring | nullFree-text observations, in English. Auction reports carry the LMN inspector’s notes (translated offline). Dealer (encar_*) reports carry the Encar inspector’s opinion (특기사항 및 점검자의 의견), translated to English via Google Translate on the fetch path. Render as a quote block.
dealer_inspectionobjectDealer-only sub-object — absent (not null) on auction responses. Present only when source === "dealer". See section 6.8.

6.4 category_grades[] rendering

statusEnglish labelPill backgroundPill text
good”Good”#ECFDF5 (emerald-50)#047857 (emerald-700)
caution”Caution”#FFFBEB (amber-50)#B45309 (amber-700)
bad”Needs attention”#FEF2F2 (red-50)#B91C1C (red-700)
Each row is laid out as:
[ {status-pill} ]  {Category label}  —  {accident_summary or exterior_summary}
The “Category label” is human-readable English derived from category:
category raw valueDisplay label
frame”Accident Grade”
exterior”Exterior Grade”
engine”Engine”
transmission”Transmission”
brakes”Brakes”
suspension”Suspension”
electrical”Electrical”
interior”Interior”
glass”Glass”
lighting”Lighting”
(unknown)Fall back to capitalize-first (engine → “Engine”)

6.5 checklist[] rendering

The checklist is grouped by group. Render as a collapsible list, with group as section header and one row per item. The result field contains the raw inspector verdict and is often in Korean — display as-is unless you maintain a translation map (see Korean Glossary). Item count by source:
  • Glovis: exactly 17 items. Groups: Engine, Transmission, Brakes, Steering, Electrical, Body, Glass, Interior.
  • AJ: up to 39 items. Same group taxonomy plus suspension, fuel system, cooling system.
  • SK / Lotte / KCar: 5-8 items, sparse coverage.
  • Dealer: always [].
<Accordion title={`Checklist (${ir.checklist.length} items)`}>
  {groupBy(ir.checklist, "group").map(([group, items]) => (
    <Section key={group} title={group}>
      {items.map((it, i) => (
        <Row key={i}>
          <span>{it.item}</span>
          <span>{translateKR(it.result)}</span>
        </Row>
      ))}
    </Section>
  ))}
</Accordion>

6.6 accident_history[] rendering

Each entry has structured fields (subject to source variation):
{
  "date": "2022-08-15",
  "type": "owner_self",
  "description": "Welded: rear door (L), trunk (L)",
  "severity": "medium"
}
Render as a timeline or table. The type field distinguishes “owner_self” (the current/past owner caused it) from “third_party” (someone else was at fault for the damage). Korean carfax convention treats these very differently for pricing.

6.7 issues[] rendering

Normalized “issues found” across all sources. Each entry:
{
  "category": "Body",
  "status": "caution",
  "detail": "Hood needs sheet-metal work",
  "faults": ["pp"],
  "type": "body"
}
FieldMeaning
categoryDisplay label for the issue’s category.
status"bad" | "caution" — color coding.
detailOne-line description of the issue.
faults[]Damage codes associated (links back to the Damage Code Dictionary).
type"mechanical" | "body" | "functional" — drives the icon (wrench / car-outline / power-cog).
Render as a vertical list of pills with the detail text. Color the pill by status.

6.8 dealer_inspection (dealer-only)

When source === "dealer", this sub-object carries Encar-specific aggregate data:
{
  "dealer_inspection": {
    "critical_frame_damage": 0,
    "exterior_damage": 1,
    "frame_status": "이상없음",
    "panel_status": "교환 1",
    "simple_repair": false,
    "insurance_history": 1,
    "insurance_amount": 1370,
    "registration_no": "20-12345678",
    "vehicle_info": {
      "Valid Period": "Apr 29, 2025 ~ Apr 28, 2030",
      "First Registered": "Apr 29, 2025",
      "Warranty": "Manufacturer warranty",
      "Engine Model": "S63B44T4",
      "Base Price": "-"
    },
    "overall_status": [
      { "item": "Emissions", "status": "Pass", "detail": "" },
      { "item": "Tuning", "status": "None", "detail": "" },
      { "item": "Odometer", "status": "Normal", "detail": "" },
      { "item": "Color", "status": "Same", "detail": "" }
    ],
    "photos": [
      { "url": "https://.../front.jpg", "label": "Front" },
      { "url": "https://.../rear.jpg", "label": "Rear" }
    ],
    "certification": {
      "inspector": "한국자동차진단보증",
      "notifier": "○○모터스",
      "date": "2026-05-12"
    }
  }
}
FieldTypeMeaning
critical_frame_damageintegerCount of frame-rank panels (RANK_A/B/C). Matches the Frame Damage count box.
exterior_damageintegerCount of exterior-rank panels (RANK_ONE/TWO). Matches the Exterior Damage count box.
frame_statusstring | nullRaw Encar frame status string in Korean. Common values: "이상없음" (no issues), "교환" (replaced), "판금" (sheet metal).
panel_statusstring | nullRaw Encar panel status string. Often includes a count, e.g. "교환 1" (1 replacement), "이상없음".
simple_repairboolean | nullEncar’s 단순수리 (simple repair) flag. null when the page doesn’t state it.
insurance_historyinteger | nullOwn-damage accident claim count, sourced from a separate insurance-record upstream. null means “no data reported”, 0 means “no claims”.
insurance_amountinteger | nullTotal own-damage insurance payout in whole USD, FX-converted server-side (same convention as accident_cost_summary). Do not re-apply fx_rate. null means “no data reported”, 0 means “no claims”.
registration_nostring | null성능번호 — the inspection-sheet registration number.
vehicle_infoobjectInspection-form-specific particulars only — Valid Period, First Registered, Warranty, Engine Model, Base Price (KO→EN translated). Identity fields that duplicate the top-level resource (make/model, year, plate, VIN, transmission, fuel) are intentionally omitted — read those from the top-level Vehicle fields. Render as a key-value list.
overall_statusarray of { item, status, detail }Section-2 rows (emissions, tuning, special history, usage change, recall, odometer, VIN marking, color, major options). item and status are translated; detail is raw.
photosarray of { url, label }Inspection front/rear photos. label is translated.
certificationobject { inspector, notifier, date } | nullInspecting body and notifier. inspector and notifier are raw Korean company names; date is translated. null when the sheet carries no certification block.
Render as a small key-value list at the bottom of the report card. The raw Korean strings can be displayed verbatim (your end customers may include Korean speakers) or translated using the Korean Glossary.

7. Overall grade interpretation

The overall_grade field is a two-part string "X/Y". The semantics differ per source. The grade_description field tells you which is which for the current row, but here’s the master decoder:
SourceFormatLeft partRight part
Glovis"A/7" to "D/1"Letter: accident grade (A = no accident, B/C/D = increasing severity)Digit 1-9: condition score (9 = best, 1 = worst)
SK"A/A" to "C/C"Letter: accident gradeLetter: exterior letter grade (A = best)
AJ"A/A" to "C/C" (digit-converted from raw numbers)Letter: frame grade (A/B/C)Letter: exterior letter grade (converted from a 1-5 digit scale internally)
Lotte"A/A" to "C/C"Letter: accident gradeLetter: exterior letter grade
KCar"A/A" to "C/C"Letter: accident gradeLetter: exterior letter grade
Dealernull
In the badge, render the two parts stacked or with a / separator. The exact pixel layout in the LMN reference UI is a 56×56 square with both characters rendered in a serif font centered.

Pill colors by overall grade

A quick heuristic for badge background:
PatternColor
Both parts A or score ≥ 7Emerald (#10B981)
One part B / score 5-6Amber (#D97706)
Any part C/D or score ≤ 4Red (#DC2626)
null (dealer)Slate (#475569)

8. Color tokens reference

Exact colors used in the lmnauto.com reference UI. Use these or your design-system equivalents.
TokenHexTailwindPurpose
--inspection-good-bg#ECFDF5bg-emerald-50Good status background
--inspection-good-fg#047857text-emerald-700Good status text
--inspection-caution-bg#FFFBEBbg-amber-50Caution status background
--inspection-caution-fg#B45309text-amber-700Caution status text
--inspection-minor-bg#FEFCE8bg-yellow-50Minor severity background
--inspection-minor-fg#A16207text-yellow-700Minor severity text
--inspection-bad-bg#FEF2F2bg-red-50Bad status background
--inspection-bad-fg#B91C1Ctext-red-700Bad status text
--inspection-marker-high#DC2626bg-red-600High-severity panel marker
--inspection-marker-medium#D97706bg-amber-600Medium-severity panel marker
--inspection-marker-low#CA8A04bg-yellow-600Low-severity panel marker
--inspection-marker-exchange#DC2626bg-red-600Exchange (CHANGE) overlay
--inspection-text-muted#64748Btext-slate-500Tooltip / “Learn more” links
--inspection-card-bg#FFFFFFbg-whiteCard background
--inspection-card-border#E2E8F0border-slate-200Card border

9. Korean Glossary

The following raw Korean strings may appear in dealer_inspection.frame_status, dealer_inspection.panel_status, inspection_report.checklist[].result, and in some accident_history[].description entries. Maintain a client-side translation map if you want English-only UI.

9.1 Status terms

KoreanEnglishWhere it appears
이상없음”No issues”frame_status, panel_status, checklist result
양호”Good” / “Pass”checklist result
불량”Bad” / “Fail”checklist result
이음”Joint” / “Caution”checklist result
교환”Replaced”panel_status
판금”Sheet metal”panel_status, description
용접”Welded”panel_status, description
부식”Corrosion”panel_status, description
손상”Damaged”panel_status
균열”Cracked”panel_status
누유”Oil leak”checklist result
누수”Water leak”checklist result
소음”Noise”checklist result
미세누유”Minor oil leak”checklist result
미세누수”Minor water leak”checklist result

9.2 Category terms (in accident_history[].description)

KoreanEnglish
본넷Hood
트렁크Trunk
도어Door
펜더Fender
휀더Fender (alt spelling)
쿼터패널Quarter panel
루프Roof
프론트범퍼Front bumper
리어범퍼Rear bumper
라디에이터서포트Radiator support
사이드멤버Side member
인사이드패널Inside panel
휠하우스Wheelhouse
대시패널Dash panel
플로어패널Floor panel
Left
Right
Front
Rear

9.3 Translation strategy

The safest approach is client-side translation with raw fallback — translate known terms, leave unknowns intact:
const KR_EN = {
  "이상없음": "No issues",
  "양호": "Pass",
  "불량": "Fail",
  // … full map from sections 9.1-9.2
};
const translateKR = (s) => KR_EN[s?.trim()] ?? s;
This keeps you safe when LMN adds new Korean status terms (you’ll see them in raw form rather than dropping them).

10. Full reference renderer (React)

This is a complete, production-ready React component that consumes the JSON and renders both panels. Adapt to your component library — the logic is the contract.
import React from "react";

const SEVERITY_ORDER = ["none", "low", "medium", "high"];

function maxSeverity(panels) {
  return panels.reduce(
    (max, p) => (SEVERITY_ORDER.indexOf(p.severity) > SEVERITY_ORDER.indexOf(max) ? p.severity : max),
    "none"
  );
}

function statusText({ count, severity, kind /* "frame" | "exterior" */ }) {
  if (count === 0) return kind === "frame" ? "No structural damage" : "No exterior damage";
  if (severity === "high") return "Major damage";
  if (severity === "medium") return "Repair history";
  return "Minor damage";
}

function statusColor({ count, severity }) {
  if (count === 0) return { bg: "#ECFDF5", fg: "#047857" };
  if (severity === "high") return { bg: "#FEF2F2", fg: "#B91C1C" };
  if (severity === "medium") return { bg: "#FFFBEB", fg: "#B45309" };
  return { bg: "#FEFCE8", fg: "#A16207" };
}

function severityMarkerColor(severity) {
  return { high: "#DC2626", medium: "#D97706", low: "#CA8A04", none: "transparent" }[severity];
}

function categoryLabel(cat) {
  const map = {
    frame: "Accident Grade",
    exterior: "Exterior Grade",
    engine: "Engine",
    transmission: "Transmission",
    brakes: "Brakes",
    suspension: "Suspension",
    electrical: "Electrical",
    interior: "Interior",
    glass: "Glass",
    lighting: "Lighting",
  };
  return map[cat] ?? cat.charAt(0).toUpperCase() + cat.slice(1);
}

function sourceLabel(source) {
  return {
    glovis: "Glovis Grade",
    sk: "SK Grade",
    aj: "AJ Sellcar Grade",
    lotte: "Lotte Grade",
    kcar: "KCar Grade",
    dealer: "Encar Dealer Inspection",
  }[source] ?? `${source} Grade`;
}

function badgeColor(grade) {
  if (!grade) return { bg: "#475569", fg: "white" };
  const [left, right] = grade.split("/");
  const numRight = parseInt(right, 10);
  if (left === "A" && (right === "A" || numRight >= 7)) return { bg: "#10B981", fg: "white" };
  if (left === "C" || left === "D" || numRight <= 4) return { bg: "#DC2626", fg: "white" };
  return { bg: "#D97706", fg: "white" };
}

// ─── Body Inspection ──────────────────────────────────────────────────────

export function BodyInspectionCard({ data, source, dealerInspection }) {
  if (!data) return null;
  const { panels, image_url } = data;
  const exteriorPanels = panels.filter((p) => p.panel_type === "exterior" && p.severity !== "none");
  const framePanels = panels.filter((p) => p.panel_type === "frame" && p.severity !== "none");

  const exteriorCount = exteriorPanels.length;
  const frameCount = framePanels.length;
  const exteriorSev = maxSeverity(exteriorPanels);
  const frameSev = maxSeverity(framePanels);

  return (
    <Card title="Body Inspection">
      <div className="counts">
        <CountBox
          label="Frame Damage"
          icon="shield"
          count={frameCount}
          color={statusColor({ count: frameCount, severity: frameSev })}
          text={statusText({ count: frameCount, severity: frameSev, kind: "frame" })}
        />
        <CountBox
          label="Exterior Damage"
          icon="warn"
          count={exteriorCount}
          color={statusColor({ count: exteriorCount, severity: exteriorSev })}
          text={statusText({ count: exteriorCount, severity: exteriorSev, kind: "exterior" })}
        />
      </div>

      <DamageLegend panels={panels} />

      <div className="diagrams">
        <DiagramPanel
          title="Exterior Panels"
          imageUrl={image_url}
          panels={exteriorPanels}
          silhouette="body"
        />
        <DiagramPanel
          title="Frame Structure"
          imageUrl={image_url}
          panels={framePanels}
          silhouette="chassis"
        />
      </div>

      {source === "dealer" && dealerInspection && <DealerRankTotals data={dealerInspection} />}
    </Card>
  );
}

function CountBox({ label, count, color, text, icon }) {
  return (
    <div style={{ background: color.bg, color: color.fg }}>
      <header>{icon === "shield" ? "🛡" : "⚠"} {label}</header>
      <strong>{count}</strong>
      <small>{text}</small>
    </div>
  );
}

function DiagramPanel({ title, imageUrl, panels, silhouette }) {
  return (
    <section>
      <h4>{title}</h4>
      {imageUrl ? (
        <img src={imageUrl} alt={title} />
      ) : (
        <svg viewBox="0 0 400 300">
          <use href={`#silhouette-${silhouette}`} />
          {panels.map((p) => {
            const coords = PANEL_COORDS[p.panel_id];
            if (!coords) return null;
            const isExchange = p.damage_codes.includes("CHANGE");
            return (
              <g key={p.panel_id}>
                <circle
                  cx={coords.x}
                  cy={coords.y}
                  r={10}
                  fill={severityMarkerColor(p.severity)}
                />
                {isExchange && (
                  <text x={coords.x} y={coords.y + 4} textAnchor="middle" fill="white" fontSize="12">
                    ×
                  </text>
                )}
              </g>
            );
          })}
        </svg>
      )}
      <UnknownPanels panels={panels} />
    </section>
  );
}

function UnknownPanels({ panels }) {
  const unknown = panels.filter((p) => !PANEL_COORDS[p.panel_id]);
  if (unknown.length === 0) return null;
  return (
    <details>
      <summary>{unknown.length} affected panel(s) not shown on diagram</summary>
      <ul>
        {unknown.map((p) => (
          <li key={p.panel_id}>
            {p.label}{p.description}
          </li>
        ))}
      </ul>
    </details>
  );
}

// Severity-based legend. Source-agnostic: we group by `panel.severity` and
// label each group by its representative `panel.description`. This avoids
// hard-coding any per-source damage-code vocabulary into the renderer.
function DamageLegend({ panels }) {
  const bySeverity = new Map(); // severity → first-seen description
  panels.forEach((p) => {
    if (p.severity === "none") return;
    if (!bySeverity.has(p.severity)) bySeverity.set(p.severity, p.description);
  });
  if (bySeverity.size === 0) return null;
  const order = ["high", "medium", "low"];
  return (
    <div className="legend">
      {order
        .filter((sev) => bySeverity.has(sev))
        .map((sev) => (
          <span key={sev}>
            <i style={{ background: severityMarkerColor(sev) }} />
            {bySeverity.get(sev)}
          </span>
        ))}
      {/* The Exchange overlay is shown separately because it applies on top of any severity. */}
      {panels.some((p) => p.damage_codes?.includes("CHANGE")) && (
        <span>
          <i style={{ background: "#DC2626" }}>×</i> Exchange
        </span>
      )}
    </div>
  );
}

// Dealer-only totals from `inspection_report.dealer_inspection`. This helper
// renders the aggregate totals; for the per-rank lines (Rank 1/2/A/B/C) read
// `dealer_inspection.rank_counts` ({ rank_one, rank_two, rank_a, rank_b, rank_c }).
function DealerRankTotals({ data }) {
  const fmt = (n) => (n === 0 ? "None" : `${n}`);
  return (
    <div className="ranks">
      <div>
        <strong>Exterior</strong>
        <span>Ranked panels — {fmt(data.exterior_damage)}</span>
      </div>
      <div>
        <strong>Frame</strong>
        <span>Ranked panels — {fmt(data.critical_frame_damage)}</span>
      </div>
    </div>
  );
}

// ─── Inspection Report ────────────────────────────────────────────────────

export function InspectionReportCard({ data }) {
  if (!data) return null;
  const ir = data;
  const badge = badgeColor(ir.overall_grade);

  return (
    <Card title="Inspection Report" tooltip={ir.grade_description}>
      <header>
        <div className="badge" style={{ background: badge.bg, color: badge.fg }}>
          {ir.overall_grade ?? "—"}
        </div>
        <h3>{sourceLabel(ir.source)}</h3>
        <span
          className="pill"
          style={
            ir.has_accident
              ? { background: "#FEF2F2", color: "#B91C1C" }
              : { background: "#ECFDF5", color: "#047857" }
          }
        >
          {ir.has_accident ? "Accident History" : "No Accident"}
        </span>
      </header>

      <small className="muted">{ir.grade_description}</small>

      {ir.category_grades.map((row) => (
        <CategoryRow
          key={row.category}
          status={row.status}
          label={categoryLabel(row.category)}
          summary={
            row.category === "frame"
              ? ir.accident_summary
              : row.category === "exterior"
              ? ir.exterior_summary
              : null
          }
        />
      ))}

      {ir.accident_reason && <p className="reason">{ir.accident_reason}</p>}

      <a className="learn-more">Learn more about grading systems →</a>

      {ir.inspection_sheet_url && (
        <a className="sheet-link" href={ir.inspection_sheet_url} target="_blank" rel="noreferrer">
          View original inspection sheet →
        </a>
      )}

      {ir.checklist.length > 0 && <Checklist items={ir.checklist} />}
      {ir.issues.length > 0 && <IssuesFound items={ir.issues} />}
      {ir.accident_cost_summary && <AccidentInsurance data={ir.accident_cost_summary} />}
      {ir.accident_history.length > 0 && <AccidentHistory items={ir.accident_history} />}
      {ir.inspector_notes && <Quote>{ir.inspector_notes}</Quote>}
      {ir.dealer_inspection && <DealerSummary data={ir.dealer_inspection} />}
    </Card>
  );
}

function CategoryRow({ status, label, summary }) {
  const color =
    status === "good"
      ? { bg: "#ECFDF5", fg: "#047857" }
      : status === "caution"
      ? { bg: "#FFFBEB", fg: "#B45309" }
      : { bg: "#FEF2F2", fg: "#B91C1C" };
  return (
    <div className="row" style={{ background: color.bg, color: color.fg }}>
      <span className="pill">{status === "good" ? "A" : status === "caution" ? "B" : "C"}</span>
      <strong>{label}</strong>
      {summary && <span>{summary}</span>}
    </div>
  );
}

function Checklist({ items }) {
  const groups = items.reduce((acc, it) => {
    (acc[it.group] ??= []).push(it);
    return acc;
  }, {});
  return (
    <details>
      <summary>Checklist ({items.length} items)</summary>
      {Object.entries(groups).map(([group, list]) => (
        <section key={group}>
          <h5>{group}</h5>
          {list.map((it, i) => (
            <div key={i} className="checklist-row">
              <span>{it.item}</span>
              <span>{translateKR(it.result)}</span>
            </div>
          ))}
        </section>
      ))}
    </details>
  );
}

function IssuesFound({ items }) {
  return (
    <section>
      <h4>Issues Found</h4>
      <ul>
        {items.map((it, i) => (
          <li key={i}>
            <span className={`pill status-${it.status}`}>{it.category}</span>
            {it.detail}
            {it.faults?.length > 0 && <span className="faults">[{it.faults.join(", ")}]</span>}
          </li>
        ))}
      </ul>
    </section>
  );
}

function AccidentInsurance({ data }) {
  const incidents = data.incidents ?? [];
  // Most-recent first; source dates are "YYYY.MM.DD" so a lexical sort works.
  const sorted = [...incidents].sort((a, b) => (b.date ?? "").localeCompare(a.date ?? ""));
  // The total is the sum of the rows shown, so the figures always reconcile.
  const total = incidents.length
    ? incidents.reduce((sum, i) => sum + i.insurance_paid, 0)
    : data.insurance_paid;
  const count = incidents.length || data.total_incidents;
  return (
    <section>
      <h4>Accident History</h4>
      <div><strong>{count} accident{count === 1 ? "" : "s"}</strong></div>
      {sorted.map((inc, i) => (
        <div key={i} style={{ display: "flex", justifyContent: "space-between" }}>
          <span>{inc.date ?? `Accident ${i + 1}`}</span>
          <span>${inc.insurance_paid.toLocaleString()}</span>
        </div>
      ))}
      <div style={{ display: "flex", justifyContent: "space-between", fontWeight: 700 }}>
        <span>Total</span>
        <span>${total.toLocaleString()}</span>
      </div>
    </section>
  );
}

function AccidentHistory({ items }) {
  return (
    <section>
      <h4>Accident History</h4>
      <ul>
        {items.map((a, i) => (
          <li key={i}>
            <strong>{a.date}</strong>{a.description} ({a.type === "owner_self" ? "self" : "third-party"})
          </li>
        ))}
      </ul>
    </section>
  );
}

function Quote({ children }) {
  return <blockquote>{children}</blockquote>;
}

function DealerSummary({ data }) {
  return (
    <section className="dealer-summary">
      <div>Frame damage: {data.critical_frame_damage}</div>
      <div>Exterior damage: {data.exterior_damage}</div>
      <div>
        Insurance: {data.insurance_history ?? 0} claim{data.insurance_history === 1 ? "" : "s"}
        {data.insurance_amount != null && `, $${data.insurance_amount.toLocaleString()}`}
      </div>
      <div>Frame status: {translateKR(data.frame_status) ?? "—"}</div>
      <div>Panel status: {translateKR(data.panel_status) ?? "—"}</div>
    </section>
  );
}

// ─── PANEL_COORDS (excerpt — fill in per your silhouette) ─────────────────

const PANEL_COORDS = {
  hood: { x: 200, y: 60 },
  roof: { x: 200, y: 150 },
  "trunk-lid": { x: 200, y: 240 },
  "front-fender-l": { x: 130, y: 80 },
  "front-fender-r": { x: 270, y: 80 },
  "front-door-l": { x: 130, y: 140 },
  "front-door-r": { x: 270, y: 140 },
  "rear-door-l": { x: 130, y: 190 },
  "rear-door-r": { x: 270, y: 190 },
  "rear-fender-l": { x: 130, y: 240 },
  "rear-fender-r": { x: 270, y: 240 },
  "front-bumper": { x: 200, y: 20 },
  "rear-bumper": { x: 200, y: 280 },
  // … extend per your silhouette
};

// ─── translateKR (excerpt — extend per Korean Glossary) ───────────────────

const KR_EN = {
  이상없음: "No issues",
  양호: "Pass",
  불량: "Fail",
  교환: "Replaced",
  판금: "Sheet metal",
  용접: "Welded",
  부식: "Corrosion",
  손상: "Damaged",
  누유: "Oil leak",
  // … extend per section 9
};
function translateKR(s) {
  if (!s) return s;
  return KR_EN[s.trim()] ?? s;
}

11. Worked example A — Glovis auction (real API response)

This is a verbatim slice of a real GET /v1/vehicles/{id} response from sandbox at the time of writing. Sandbox base URL is https://sandbox-api.lmnauto.com; production is https://api.lmnauto.com (see Quickstart for both). The response shape is identical across environments — sandbox just runs against the staging data snapshot. Request
curl -H "x-api-key: $LMN_KEY" "https://sandbox-api.lmnauto.com/v1/vehicles/glovis_20260521_1078_1005"
Response (inspection portion) — note image_url: null and dealer_inspection absent. This is a Hyundai Sonata 2020 with significant frame damage history (overall grade C/1).
{
  "id": "glovis_20260521_1078_1005",
  "make": "Hyundai",
  "model": "Sonata",
  "year": 2020,
  "vin": "KMHL341DBLA053529",
  "body_condition": {
    "image_url": null,
    "panels": [
      { "panel_id": "rear-fender-l",       "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Rear Fender (L)",          "description": "Replaced",           "severity": "high"   },
      { "panel_id": "rear-fender-r",       "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Rear Fender (R)",          "description": "Replaced",           "severity": "high"   },
      { "panel_id": "trunk",               "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Trunk",                    "description": "Replaced",           "severity": "high"   },
      { "panel_id": "side-sill-l",         "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Side Sill (L)",            "description": "Replaced",           "severity": "high"   },
      { "panel_id": "side-sill-r",         "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Side Sill (R)",            "description": "Replaced",           "severity": "high"   },
      { "panel_id": "rear-panel",          "panel_type": "frame",    "damage_code": "xx", "damage_codes": ["xx"], "label": "Rear Panel",               "description": "Replaced",           "severity": "high"   },
      { "panel_id": "a-pillar-l",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "A-Pillar (L)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "a-pillar-r",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "A-Pillar (R)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "b-pillar-l",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "B-Pillar (L)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "b-pillar-r",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "B-Pillar (R)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-wheel-house-l",  "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어휠하우스(좌)",         "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-wheel-house-r",  "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어휠하우스(우)",         "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "seat-back-panel",     "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "시트백패널",                "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "trunk-floor",         "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "Trunk Floor",              "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "side-member-frame",   "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "사이드멤버(프레임)",       "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-inside-panel-l", "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어인사이드패널(좌)",     "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-inside-panel-r", "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어인사이드패널(우)",     "description": "Sheet Metal/Welding","severity": "medium" }
    ]
  },
  "inspection_report": {
    "source": "glovis",
    "overall_grade": "C/1",
    "grade_description": "Accident/Condition (1=Worst, 9=Best)",
    "accident_summary": null,
    "has_accident": false,
    "has_frame_damage": true,
    "exterior_summary": "Engine: oil leak, major part replacement needed, oil leak (turbo). Transmission: major part replacement needed",
    "accident_reason": null,
    "category_grades": [
      { "category": "Engine",       "status": "bad"  },
      { "category": "Brakes",       "status": "good" },
      { "category": "Steering",     "status": "good" },
      { "category": "Electrical",   "status": "good" },
      { "category": "Shift Issue",  "status": "bad"  },
      { "category": "HVAC",         "status": "good" },
      { "category": "동력",          "status": "good" },
      { "category": "Interior",     "status": "good" },
      { "category": "Lighting",     "status": "good" }
    ],
    "checklist": [
      "… 48 items elided …"
    ],
    "accident_history": [],
    "accident_cost_summary": null,
    "issues": [
      "… 2 entries elided …"
    ],
    "inspection_sheet_url": null,
    "inspector_notes": ", Consultation Unavailable; Rear Fender (Left) Replacement, Rear Fender (Right) Replacement, Trunk Replacement, Rear Panel Replacement, Side Sill Panel (Left) Replacement, Side Sill Panel (Right) Replacement, A-Pillar (Left) Sheet Metal/Welding, A-Pillar (Right) Sheet Metal/Welding, B-Pillar (Left) Sheet Metal/Welding, B-Pillar (Right) Sheet Metal/Welding, Rear Wheelhouse (Left) Sheet Metal/Welding, Rear Wheelhouse (Right) Sheet Metal/Welding, Seat Back Panel Sheet Metal/Welding, Trunk Floor Sheet Metal/Welding, Side Member (Frame) Sheet Metal/Welding, Rear Inside Panel (Left) Sheet Metal/Welding, Rear Inside Panel (Right) Sheet Metal/Welding"
  }
}
Things to notice about this real response (vs. documented ideal):
  • image_url is null for Glovis. Glovis (and K-Car, dealer) have no mirrored image — render the diagram from panels[] directly. SK and Lotte do return a mirrored image_url (damage baked in), and AJ returns a blueprint base image drawn under the markers.
  • accident_summary and accident_reason are null even though has_frame_damage: true. Don’t gate the “Accident History” pill on accident_summary being non-null; use has_accident / has_frame_damage directly.
  • checklist has 48 items, not the canonical “17 items” that older documentation mentions. The count varies per inspection — render whatever the array contains.
  • Some panel.label values are Korean (e.g. "리어휠하우스(좌)") even though others are English. The Korean-to-English mapping is incomplete; display label as-is and translate client-side if you need an English-only UI. See the Korean Glossary.
  • category_grades[].category can be Korean ("동력" in this row) for categories not yet in the English mapping.
  • exterior_summary here describes mechanical issues (engine/transmission), not exterior panels. The field name is historically misleading on Glovis — it’s effectively a free-text “summary of issues found” rather than strictly panel-related.
UI mapping
  • Badge: C/1 on red (C is left, 1 is the worst condition score).
  • Pill: green No Accident (has_accident: false) — but a separate caution callout for has_frame_damage: true is healthier UX, since the car has significant frame welding.
  • Body Inspection card:
    • Counts: Frame 11 panels (mostly medium w, one high xx = rear-panel), Exterior 6 panels (high xx only — five rear/side replacements).
    • Diagram: render from panels[] because image_url is null. Markers: 6 red on exterior silhouette, 1 red + 10 amber on frame silhouette.
  • Category rows: 9 entries — render two reds (“Engine”, “Shift Issue”) and seven greens. The Korean 동력 row renders as-is unless you translate client-side.
  • Checklist: collapsible list with 48 items (group-by-group).
  • Issues Found: 2 entries.
  • Inspector notes block: long run-on string of replacement summaries.
  • No accident_cost_summary, no accident_history entries (the frame damage exists but no structured accident record is currently associated).

12. Worked example B — Encar dealer (real API response)

This is a verbatim slice of a real GET /v1/vehicles/{id} response from sandbox. It is a BMW M5 2019 listing with two ranked exterior panels — a clean worked example of the canonical dealer shape described in §5.2 and §4.6: nested type.code translated to a drawable camelCase panel_id, and statusTypes[] normalized into canonical damage codes. Request
curl -H "x-api-key: $LMN_KEY" "https://sandbox-api.lmnauto.com/v1/vehicles/encar_41657860"
Response (inspection portion) — verbatim from sandbox post-fix.
{
  "id": "encar_41657860",
  "make": "BMW",
  "model": "M5",
  "year": 2019,
  "vin": null,
  "body_condition": {
    "image_url": null,
    "panels": [
      {
        "panel_id": "quarterPanelRight",
        "raw_panel_code": "P062",
        "raw_label_ko": "쿼터 패널(우)",
        "panel_type": "exterior",
        "damage_code": "METAL",
        "damage_codes": ["METAL"],
        "label": "Quarter Panel (R)",
        "description": "Sheet metal / Welding",
        "severity": "medium"
      },
      {
        "panel_id": "frontFenderLeft",
        "raw_panel_code": "P021",
        "raw_label_ko": "프론트 휀더(좌)",
        "panel_type": "exterior",
        "damage_code": "CHANGE",
        "damage_codes": ["CHANGE"],
        "label": "Front Fender (L)",
        "description": "Exchange (replaced)",
        "severity": "high"
      }
    ]
  },
  "inspection_report": {
    "source": "dealer",
    "overall_grade": null,
    "grade_description": "Encar dealer inspection",
    "accident_summary": "No structural damage",
    "has_accident": false,
    "has_frame_damage": false,
    "exterior_summary": "2 exterior damage cases",
    "accident_reason": null,
    "category_grades": [
      { "category": "frame", "status": "good" },
      { "category": "exterior", "status": "caution" }
    ],
    "checklist": [
      { "group": "Engine", "item": "Engine oil leak", "result": "No issues" },
      { "group": "Transmission", "item": "Oil leak (auto)", "result": "No issues" }
    ],
    "accident_history": [],
    "accident_cost_summary": null,
    "issues": [],
    "inspection_sheet_url": null,
    "inspector_notes": "No notable defects beyond the recorded exterior damage.",
    "dealer_inspection": {
      "critical_frame_damage": 0,
      "exterior_damage": 2,
      "frame_status": null,
      "panel_status": null,
      "simple_repair": false,
      "insurance_history": 1,
      "insurance_amount": 1370,
      "registration_no": "20-12345678",
      "vehicle_info": {
        "Valid Period": "Apr 29, 2025 ~ Apr 28, 2030",
        "First Registered": "Apr 29, 2025",
        "Warranty": "Manufacturer warranty",
        "Engine Model": "S63B44T4",
        "Base Price": "-"
      },
      "overall_status": [
        { "item": "Emissions", "status": "Pass", "detail": "" },
        { "item": "Tuning", "status": "None", "detail": "" },
        { "item": "Odometer", "status": "Normal", "detail": "" },
        { "item": "Color", "status": "Same", "detail": "" }
      ],
      "photos": [
        { "url": "https://.../front.jpg", "label": "Front" },
        { "url": "https://.../rear.jpg", "label": "Rear" }
      ],
      "certification": {
        "inspector": "한국자동차진단보증",
        "notifier": "○○모터스",
        "date": "2026-05-12"
      }
    }
  }
}
Things to notice:
  • Both panels have canonical camelCase panel_id values (quarterPanelRight, frontFenderLeft) drawable directly on the standard Encar silhouette.
  • raw_panel_code preserves the upstream Encar P-code (P062, P021) so you can correlate against Encar’s own admin tooling or display the raw value alongside the canonical one.
  • raw_label_ko carries the upstream Korean label verbatim — useful if your customer base includes Korean speakers or for showing both languages side-by-side.
  • damage_code is the canonical normalized code derived from Encar’s statusTypes[] (W → METAL, X → CHANGE) — not the rank attribute. The rank attribute (RANK_TWO, RANK_ONE) remains preserved in the aggregate dealer_inspection.exterior_damage count (2).
  • severity is computed from the canonical damage code: METALmedium (sheet metal repair), CHANGEhigh (replacement). Use the emitted value directly; do not re-derive locally.
  • frame_status: null, panel_status: null for this car — Encar didn’t return the descriptive status strings. Render the dealer summary with placeholder dashes for those fields.
  • insurance_history: 1, insurance_amount: 1370 — own-damage accident history is populated for dealer cars from a separate insurance-record upstream; the amount is whole USD (FX-converted server-side), matching the auction accident_cost_summary convention — don’t re-apply fx_rate. A null (not 0) still means “no data reported”; 0 means “no claims”.
  • vehicle_info, overall_status, photos, certification carry the full inspection-sheet detail. Render vehicle_info as a key-value list, overall_status as a status table, photos as a thumbnail row, and certification as a small attribution line.
UI mapping
  • Badge: slate (no overall_grade). Title: “Encar Dealer Inspection”.
  • Pill: green No Accident (has_accident: false).
  • Two grade rows: green “Accident Grade” + “No structural damage”, amber “Exterior Grade” + “2 exterior damage cases”.
  • Mechanical checklist[] present (engine, transmission, steering, braking, electrical, …, EN-translated); inspector notes present; no auction-only accident-cost summary.
  • Dealer summary block at bottom: frame_status / panel_status dash (null this car); exterior_damage: 2, insurance history (1 claim, $1,370), plus the vehicle_info / overall_status / photos / certification sheet detail carry the real signal.
  • Body Inspection card:
    • Count boxes: Frame 0 (green “No structural damage”), Exterior 2 (red “Major damage” — driven by the high-severity CHANGE on the front-fender-left).
    • Diagram: amber marker on the right quarter panel (quarterPanelRight, METAL/medium), red marker with × overlay on the left front fender (frontFenderLeft, CHANGE/high).
    • Affected panels list: empty — both panels are drawable.
  • Rank summary below diagrams: “Exterior — 2 ranked panels”, “Frame — None”.

13. Edge cases (full list)

Some auction listings haven’t been inspected yet (ingestion can lag by minutes to hours, especially for KCar). Render a neutral empty state: “Inspection report not yet available. Check back in a few hours.” Do not show a “report” card with all blank fields — it implies “we inspected and found nothing”, which is wrong.
Render only the populated card. Specifically:
  • body_condition null but inspection_report populated → no diagram, show the grade card alone with all available fields.
  • body_condition populated but inspection_report null → diagram alone with the count boxes; omit any reference to grade.
If we emit a panel_id not in your PANEL_COORDS map (we extend the catalog as inspector data evolves), do not drop the panel. Render it in a textual list under the diagram via the UnknownPanels component in section 10. The damage is real even if you don’t have a marker position for it.
dealer_inspection.frame_status and panel_status are raw Encar Korean. Either display as-is (your end customers may include Korean speakers) or translate via section 9’s dictionary. The panel_status value sometimes includes a count suffix ("교환 1") — strip and translate the term, preserve the digit.
All monetary fields here are already FX-converted to USD whole integers (computed server-side using the day’s exchange rate). Do not re-convert. If fx_rate on the vehicle response is null, this field is also null.incidents[] (Glovis) lists one row per insurance-settled accident — { date, insurance_paid, repair_cost }. The aggregate insurance_paid equals the sum of incidents[].insurance_paid: rounding to whole USD is applied per incident and then summed, so a per-incident breakdown always reconciles exactly to the total (don’t recompute from the won amounts client-side). When incidents[] is [] (a source with no per-incident detail), fall back to showing the count + aggregate only.
Only Glovis returns image_url. SK / AJ / Lotte / KCar all return null — draw your own diagram from panels[]. Dealer is the same.
Some checklist[].result values may be empty strings (the inspector marked the item but left the result blank). Treat as missing — render the item with a placeholder, do not skip the item.
For dealer rows, upstream Encar data may classify a panel with both a rank attribute (e.g., RANK_ONE) and a damage marker (e.g., CHANGE). When this happens the API collapses damage_codes to the non-rank code(s) only — so you will see damage_codes: ["CHANGE"], not ["RANK_ONE", "CHANGE"].The rank attribute is not lost — it is reflected both in the aggregate counts under inspection_report.dealer_inspection.{critical_frame_damage, exterior_damage} and, per-rank, in inspection_report.dealer_inspection.rank_counts ({ rank_one, rank_two, rank_a, rank_b, rank_c }). rank_counts is tallied from the rank attributes before the damage_codes collapse described above, so it stays accurate even for panels that carry both a rank and a damage code.This means: on a single dealer panel you see one marker on the diagram colored by the panel’s severity (computed from the strongest code present), and the totals beneath the diagram come from dealer_inspection, not from damage_codes.
accident_history may be populated while accident_cost_summary is null (the source recorded an accident but the cost details weren’t captured). Render the history regardless.
Glovis occasionally updates its inspection format (e.g., adds new items). Older inspections may have an item from the previous taxonomy that doesn’t match the current 17-item set. Just render whatever’s in the array; we don’t filter for “canonical” items server-side.

14. Localization

The inspection text is in English by default (translated server-side from Korean source data). For RTL languages (Arabic in particular):
SourceOutput
inspection_report.exterior_summaryEnglish-only
inspection_report.accident_summaryEnglish-only
inspection_report.accident_reasonEnglish-only
inspection_report.inspector_notesEnglish — auction translated offline, dealer translated live via Google Translate
inspection_report.grade_descriptionEnglish-only
inspection_report.checklist[].resultKorean — translate client-side
inspection_report.checklist[].itemEnglish
dealer_inspection.frame_status / panel_statusKorean — translate client-side
accident_history[].descriptionMixed; often Korean for older records — translate client-side
For Arabic UI, mirror the layout (badges on the right, summary text left-aligned) and run the English strings through your translation pipeline. The reference UI on lmnauto.com supports Arabic via next-intl; equivalent client-side translation is straightforward.

15. Performance and caching

  • The detail response is cached server-side for 30 minutes per ID (Encar dealer detail) or until the next inspection ingest (auction). Refetching more often than every 5 minutes per car is wasteful.
  • The inspection diagram (image_url) is served from GCS with a 7-day cache header. Safe to use directly in <img src> without your own caching layer.
  • The full response size with inspection populated runs 8-25KB. Keep that in mind if you’re streaming many details client-side.

16. Verification checklist

Before you ship your rendering, verify each of these against the reference UI:
1

Empty state renders correctly

Hit a freshly-scraped Glovis listing where inspection hasn’t been filed yet (body_condition and inspection_report both null). Confirm a single neutral message appears, not an empty card.
2

Dealer-source renders correctly

Hit encar_* ID. Confirm:
  • No overall_grade badge (slate )
  • “Encar Dealer Inspection” source label
  • Dealer summary block at bottom (vehicle info, overall status, photos, certification, insurance history)
  • Rank breakdown table under diagrams
  • Mechanical checklist[] present (EN-translated); inspector notes present; no auction-only accident-cost summary
3

Glovis rich auction renders correctly

Hit glovis_* ID with frame damage. Confirm:
  • overall_grade badge in correct color band
  • Red “Accident History” pill
  • All category rows render with correct status colors
  • Checklist with 17 items, collapsible
  • Accident History with at least one entry
  • Accident Cost section with USD amounts
  • Inspector notes as quote block
  • image_url rendered as <img>, not SVG
4

Severity coloring matches

For at least 3 cars with mixed damage:
  • High-severity panels show red markers
  • Medium-severity show amber
  • Low-severity show yellow outline
  • CHANGE panels have an × overlay
  • Count boxes color-match their max severity
5

Unknown panel fallback works

Manually inject a panel_id not in your PANEL_COORDS (e.g., temporarily delete one entry). Confirm the panel appears in the “X affected panels not shown on diagram” expandable list, not dropped silently.
6

Korean fallback works

For a dealer car, confirm raw Korean frame_status / panel_status either translates correctly or displays as-is (no undefined, no empty render).
7

USD amounts not re-converted

For an auction with accident_cost_summary, confirm the values render exactly as returned — no client-side division by FX, no rounding errors.
8

Refresh behavior

Hit the same car twice within 30 seconds. The second render should produce identical output. If your client adds metadata (timestamp, request ID), exclude it from the comparison.

17. FAQ

Glovis ships a pre-rendered SVG/PNG with damage markers as part of their inspection package; we mirror it to GCS. SK, AJ, Lotte, KCar provide only structured panel data — no diagram image — so we expect the client to render. Encar dealer follows the same pattern (structured outers[] from their API, no diagram). If you can’t build SVG rendering, fall back to listing damaged panels by label and skipping the diagram entirely; the count boxes still convey the high-signal info.
Encar’s inspection API doesn’t return an overall letter grade. They publish the panel attributes and statuses but no composite grade. We could synthesize one (“Letter equivalent to RANK_X count”) but chose not to — it would mix our editorial judgment with raw data, and partners are better served by the raw numbers and a clear “no overall grade” signal.
It’s only as accurate as what Encar’s dealer ecosystem self-reports. Encar inspectors do physically examine cars, and the response now carries near-full detail: vehicle info, overall status, the mechanical checklist[] (engine, transmission, steering, braking, electrical, …), inspection photos, certification, insurance history, and inspector notes. The remaining gap versus an LMN auction inspector’s report is the auction-only accident_history[] per-incident breakdown and accident_cost_summary. For high-stakes decisions (export, customs), encourage your customer to request an LMN secondary inspection — that’s available on the auction tab but not for dealer listings.
Yes — server-side we always emit in this order: frame, exterior, engine, transmission, brakes, suspension, electrical, interior, glass, lighting. Categories not applicable to the source are omitted. Don’t sort client-side; just render in array order.
has_accident = there’s at least one accident in the car’s history (could be a long-ago repair, or a current visible issue). has_frame_damage = there’s structural (frame) damage currently visible. A repaired car may have has_accident: true but has_frame_damage: false. Use both signals for nuanced messaging — “previously repaired” vs “currently damaged” are very different stories to a buyer.
By date descending (most recent first). If a record has a null date (rare), it sorts to the end.
That shouldn’t happen — we dedupe server-side by panel_id. If you see it, file a bug; meanwhile, render the first occurrence and ignore subsequent.
Render the English from accident_reason, exterior_summary, etc. directly. For fields that are raw Korean (dealer_inspection.frame_status, checklist[].result), show both: {translateKR(value)} ({value}) — gives Korean speakers the original and English speakers the translation simultaneously.
Use panel.severity directly; do not infer from panel_type or damage_code. Severity is set server-side per inspector source — each source has its own code-to-severity table at ingest time, and frame-panel promotion (when it happens) varies by source. Local re-derivation will drift from server behavior as we tune the per-source tables. See section 4.7. The stable contract is the set of possible values ("high" \| "medium" \| "low" \| "none") and the color you pick for each, not the rule that produced it.
For auction sources we maintain a Korean-to-English mapping for panel names; when a panel name isn’t in the mapping (typically a niche or recently-added panel), the Korean falls through to panel.label. For dealer sources, panel.label is what Encar’s API returns — usually English or the raw camelCase ID. In both cases, your client should display label as-is and not assume it is always English. If you spot consistently-untranslated values, email integrations@lmnauto.com — additions are quick.
This guide is for v1.18+ (the unified inspection shape that landed alongside the dealer-inspection enrichment). Earlier versions used a flatter structure; consult the Changelog for the migration table.

18. Versioning and stability

This is part of GET /v1/vehicles/{id} and follows our versioning policy:
  • Additive changes (new fields on body_condition.panels[], new fields on inspection_report, new fields on dealer_inspection) ship without a version bump. Your renderer must ignore unknown fields safely.
  • New panel IDs ship without notice (additive). Your PANEL_COORDS should have a graceful fallback.
  • New damage codes ship without notice (additive). Your damage legend should tolerate unknown codes by falling back to panels[].description for display.
  • Removing a field is a breaking change → new major version + 90-day deprecation window + changelog notice.
  • Renaming a field is a breaking change → same policy.
  • Changing a severity rule is treated as additive (the rule is server-side; you receive the computed value). We may move panels between severity buckets — the partner-visible contract is the field name and type, not the computation.
The inspection_report.dealer_inspection sub-object is new in v1.18 (shipped 2026-05-20). Earlier versions of the doc may not show it.

19. Where to look on lmnauto.com

For pixel-faithful comparison, open the same ID in both places — the consumer site at lmnauto.com and the partner API at sandbox-api.lmnauto.com — and diff the JSON against the rendered UI.
SourceConsumer URL patternWorked example in this guide
Glovis / SK / AJ / Lotte / KCar (auction)https://lmnauto.com/en/auction?source=auctions&tab=<id>§11glovis_20260521_1078_1005
Encar (dealer)https://lmnauto.com/en/auction?source=retail&tab=<id>§12encar_41657860
The IDs in the worked examples are real listings drawn from sandbox at the time of writing; they may roll out of the active inventory window. For a fresh comparison, pick any current ID from GET /v1/vehicles?source=<src>&limit=1 and use that same ID in both URLs. If your rendering ever looks materially different from the lmnauto.com reference, diff against the JSON returned from GET /v1/vehicles/{id} for the same ID rather than trying to reverse-engineer the UI. The JSON is the contract; the UI is one possible rendering of it.

20. Support

If you encounter rendering edge cases this guide doesn’t cover, or you find an inconsistency between the JSON and the reference UI, email integrations@lmnauto.com with:
  • The vehicle ID (auction or dealer)
  • The raw JSON response from GET /v1/vehicles/{id} (paste in full)
  • A screenshot or description of how your UI rendered it
  • A link or screenshot of the corresponding lmnauto.com page
We’ll diagnose within one business day and either patch the rendering guide, fix the data, or both.