Skip to main content
Subscribe via this page’s RSS feed (Mintlify-generated) for release notifications. Public LMN Open API contract versions start at v1.0.

v1.42 — 2026-06-22

Order resource and webhook order.* payloads now include a pricing breakdown. The Order resource returned by POST /v1/orders, GET /v1/orders/{id}, and GET /v1/orders (list) now carries a top-level pricing field with the same shape as GET /v1/vehicles/{id} pricing: listing_price, sold_price, discount_config, breakdown (including estimated_landed), auction_fee_config, history, and market_assessment. The snapshot is captured at order-creation time — it reflects the FX rate and cost config at the moment POST /v1/orders was called and does not change afterward.
  • Null for legacy orders. Orders created before this release, and any order where the FX provider was unavailable at creation, return pricing: null.
  • Auction orders. history is [] and market_assessment is null (auction sale history is not snapshotted onto orders). auction_fee_config is populated.
  • Dealer orders. auction_fee_config is null; breakdown.dealer_fee reflects the dealer fee at creation time.
  • Webhook parity. Per the v1.18 guarantee, data.order.pricing in every order.* webhook payload is byte-identical to GET /v1/orders/{id}.pricing. No separate webhook update is needed.
  • Backfill. POST /v1/orders/backfill now returns a populated pricing for delisted dealer cars — the FX rate is locked at backfill time and landed cost is computed from the stored vehicle snapshot.
  • Additive. No existing fields were renamed or removed. Clients that ignore unknown fields are unaffected.

v1.41 — 2026-06-19

X-User-Id is now persisted raw on orders (admin-visible) and logged raw alongside the hash. When X-User-Id is sent with POST /v1/orders, the raw value is now persisted as external_user_id on the order row and is visible on the LMN admin order detail page. It is not returned in the partner-facing /v1 order response — the Order JSON shape is unchanged. Additionally, every /v1 request that carries X-User-Id now logs both the raw value (external_user_id) and the existing one-way SHA-256 hash (external_user_id_hash) in the audit log. Previously only the hash was captured. This is a reversal of the prior hash-only stance (#121). No partner-visible response changes — the Order resource and all existing fields are unchanged. See Authentication → X-User-Id for the updated table.

v1.40 — 2026-06-17

Breaking (dealer source): dealer_inspection.insurance_amount is now whole USD, not KRW. GET /v1/vehicles/encar_<id> now returns dealer_inspection.insurance_amount as a whole-USD integer, FX-converted server-side — matching the auction accident_cost_summary.insurance_paid convention. Previously it was raw KRW.
  • Action required if you render this field: stop applying fx_rate client-side and stop labelling it — the value is already USD. A car that previously reported insurance_amount: 1850000 (₩) now reports insurance_amount: 1370 ($).
  • Unchanged: insurance_history (own-damage claim count) is still an integer count; null still means “no data reported” and 0 still means “no claims”.
  • Auction source unaffected — its accident_cost_summary monetary fields were already whole USD.

v1.39 — 2026-06-15

Purchased vehicle snapshots for dealer orders.
  • Added GET /v1/orders/{id}/vehicle, returning the stored VehicleDetail snapshot captured when the order was created.
  • Dealer (encar_*) order creation now stores a full vehicle-detail snapshot so partners can still view condition, inspection, and photos after Encar removes the live listing page.
  • Dealer photos and image URLs inside inspection data are mirrored to LMN-owned https://storage.googleapis.com/lmnauto-auction-data/... objects before the snapshot is persisted.
  • Added 404 order_vehicle_snapshot_not_found for historical orders created before purchased-vehicle snapshots were available.

v1.38 — 2026-06-12

X-User-Id end-user attribution header documented. You can attach an optional X-User-Id header to any request to attribute it to the individual end-user/dealer in your org (per-dealer analytics/curation). No validation, no auth semantics — opaque metadata. LMN’s request logs store a one-way SHA-256 hash of the value, never the raw string. See Authentication → X-User-Id. Capture has been live since 2026-06-09; this entry documents the contract. The header is not persisted on order resources.

v1.37 — 2026-06-11

Eagle Eye: four new filter fields, per-field flexibility, and match-type metadata on every response surface.

New filter fields (filters block, all Eagle Eye request bodies)

  • fuel (string) — single lowercase fuel token. Accepted values: gasoline, diesel, lpg, hybrid, gasoline_hybrid, diesel_hybrid, electric, plug_in_hybrid, hydrogen. Multi-fuel is not supported — the dealer (encar) search DSL cannot express a fuel OR clause; supply exactly one token or omit. An array or unrecognized token returns 400 validation_error.
  • no_accident (boolean) — true filters to strictly clean cars. Semantics differ by source: dealer applies encar’s strict 무사고 predicate (zero frame damage and zero exterior repair records); auction applies grade A (no structural exchange; exterior panel work is permitted). Not source-equivalent — see no_frame_damage for the portable alternative.
  • no_frame_damage (boolean) — true excludes structural frame damage while permitting exterior repair. Source-equivalent: dealer maps to Accident.N or Accident.F (zero frame exchange); auction maps to grades A + B (no structural exchange). Recommended for frame-sensitive buyers across mixed sources.
  • estimated_landed_max_usd (number) — maximum estimated landed cost in USD, computed per-partner using your FX rate and freight configuration. Vehicles whose landed cost cannot be computed are excluded. FX drift can move vehicles in and out of an active watch — set a margin or use flexibility.fields.estimated_landed_max_usd.
No-inspection exclusion: approximately 3% of dealer stock lacks a valid inspection record. When either damage filter is active, those cars are excluded (unknown condition is treated conservatively as not qualifying).

New top-level flexibility block

An optional block that widens filter bounds and expands categorical alternatives without changing the dealer’s stated criteria. When absent, all filters are applied strictly. Numeric / year fields accept a level tolerance (none | small | medium | large):
LevelMax bound wideningYear slack
none0%±0
small+2% (floor)±0
medium+5% (floor)±1 year
large+10% (floor)±2 years
Categorical fields (model, trim, fuel) accept allowed_values — each value adds one parallel search variant. level: "none" is an explicit no-op for per-field override of flexibility.default. Non-flexable fields (no_accident, no_frame_damage, source, make) accept level: "none" only. Caps: max 4 allowed_values per categorical field; max 8 total variant product across all categorical fields. Violations return 400 eagle_eye_invalid_flexibility. A PUT /watches/{id} without a flexibility key clears it to null (full-replace semantics).

New match metadata on every response surface

match_type ("exact" | "flex") and flex_detail are now present on:
  • Search rows from POST /v1/eagle-eye/search
  • Match list items from GET /v1/eagle-eye/watches/{id}/matches
  • additions and price_changes entries in eagle_eye.match webhooks
flex_detail is a keyed object — one entry per field that required the tolerance:
  • Numeric entry: { requested, level, effective, actual } (all numbers)
  • Categorical entry: { requested, allowed_values, actual } (all strings)
match_reason (filters / signals.*) is unchanged — it remains orthogonal to flex classification.

New error code

  • eagle_eye_invalid_flexibility (400) — flexibility block is structurally invalid: field key references an absent filter, non-none level on a non-flexable field, allowed_values on a non-categorical field, categorical non-none level without allowed_values, or variant cap exceeded. details.field names the offending field.
See Eagle Eye filter reference and Eagle Eye overview — flexibility for full documentation and worked examples.

v1.36 — 2026-06-11

Dealer (encar_*) WAIT advertisements are now rejected as unavailable, like SOLD. v1.35 made detail and order creation reject dealer cars whose upstream advertisement status is SOLD. The same treatment now applies to status WAIT: GET /v1/vehicles/encar_<id> and POST /v1/orders return 404 vehicle_not_found. A 100-car sample (2026-06-11) confirmed every WAIT listing is dead inventory: Encar renders its own “sold or deleted” page and its live vehicle API returns 404 for them — only the stale embedded detail-page state still carries vehicle data, which previously let these cars appear buyable.
  • No response-shape change. Active (ADVERTISE) cars are unaffected.
  • Operational meaning unchanged: 404 vehicle_not_found on an encar_* detail/order still means “not available to buy — don’t retry.”

v1.35 — 2026-06-10

Dealer (encar_*) detail and order creation now reject sold upstream advertisements as unavailable.

Partner-visible behavior change (dealer source only)

GET /v1/vehicles/encar_<id> and POST /v1/orders with a dealer vehicle_id now return 404 vehicle_not_found when Encar’s detail page is still reachable and parseable but the embedded advertisement status is SOLD. Previously detail could return a successful VehicleDetail, and order creation could create an order, for a car that was no longer active dealer inventory.
  • Affected endpoints: GET /v1/vehicles/{id} for dealer IDs and POST /v1/orders when vehicle_id is encar_<numeric>.
  • Successful response shapes unchanged: active dealer cars still return the same VehicleDetail / Order objects; no fields were added or removed.
  • List/search unchanged: GET /v1/vehicles response shape and filters are unchanged.
  • Operational meaning: treat 404 vehicle_not_found on an encar_* detail/order as “not available to buy” — either the upstream returned 404 or the upstream detail page marked the advertisement as sold. Retry/backoff is still appropriate for 503 dealer_upstream_unavailable, not for this 404.

v1.34 — 2026-06-09

options_include now filters dealer (encar_*) rows server-side. Previously, options_include was silently dropped for source=dealer — the filter was neither applied nor surfaced, so source=dealer&options_include=sunroof returned every matching dealer car, not just sunroof-equipped ones. It is now applied for dealer rows via Encar’s native Options. search DSL, with the same AND semantics as auction (a car must have all listed options). A mixed source=glovis,dealer request now honors options_include on both halves. Two tokens are unsupported for dealer and now return 400 validation_error (details.field: "options_include", with the offending token named): panoramic_sunroof and dashcam. Encar publishes no search filter for these, so rather than silently drop them, the request fails fast. This 400 is dealer-scoped — auction-only requests are unaffected, and a mixed request carrying one of these tokens returns 400 (not a degraded auction-only 200). All other documented options_include tokens work for dealer. No response-shape change. If you were relying on the previous silent no-op (sending options_include to dealer and ignoring it), your dealer result set will now be narrower; drop the param if you don’t want option filtering.

v1.33 — 2026-06-05

Dealer (encar_*) inspection_report.inspector_notes is now populated (English). The Encar inspector’s free-text opinion (특기사항 및 점검자의 의견) was previously dropped to null on dealer responses: it’s raw Korean, and the partner-response Hangul scrub removes untranslatable Korean. It is now translated to English via Google Translate on the dealer fetch path — the same English-everywhere experience auction sources already provide — so the note (e.g. “Partial putty/paint and corrosion; replaceable frame parts not disclosed. Over 200,000 km — self-warranty. Driver-side inside-panel damage.”) reaches the partner. No shape change — inspector_notes is still string | null. It remains null only when the source genuinely has no opinion note or the inspection couldn’t be fetched. Translation is best-effort: on a transient translate failure the note is omitted rather than shown in Korean.

v1.35 — 2026-06-17

New optional order status inspection_ready — the inspected path is now placed → inspection_in_progress → inspection_ready → acquiring.

Non-breaking additive (new enum value on Order.status)

  • inspection_ready means the optional pre-acquisition inspection has completed and LMN can either continue to acquisition (inspection_ready → acquiring) or cancel for inspection failure (inspection_ready → cancelled, cancellation_reason: "inspection_failed").
  • inspection_in_progress no longer exits directly to acquiring or cancelled in the finalized inspection flow. Admins mark the inspection complete first, then take the acquisition/cancellation decision from inspection_ready.
  • LMN-owned — partners cannot set inspection_ready. Attempting it via POST /v1/orders/{id}/status returns 403 invalid_status_transition with details.reason: "partner_not_authorized", alongside other LMN-owned statuses.
  • order.status_changed webhooks now fire for inspection_in_progress → inspection_ready and for exits from inspection_ready (inspection_ready → acquiring, inspection_ready → cancelled).
This is additive — no fields added or removed, no partner-driven transition changed. Clients with a strict status enum MUST tolerate the new value (handle unknown enum values defensively, per conventions).

v1.32 — 2026-06-05

inspection_report.accident_cost_summary now carries a per-incident incidents[] breakdown.

Added (additive — no fields removed, no shape change to existing fields)

  • inspection_report.accident_cost_summary.incidents[] — one entry per insurance-settled accident (보험사고이력 : 내차 피해): { date: string | null, insurance_paid, repair_cost: number | null }. All monetary values are whole USD, already FX-converted server-side. Currently populated for Glovis; other sources return [] (no per-incident detail upstream).
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 list always reconciles exactly to the total. This lets partners render “N accidents → payout per accident → total” without the figures disagreeing. Existing consumers that only read the aggregate fields are unaffected.

v1.31 — 2026-06-03

New optional order status inspection_in_progress — the order lifecycle is now 10 statuses (was 9), with inspection_in_progress positioned between placed and acquiring.

Non-breaking additive (new enum value on Order.status)

  • inspection_in_progress is an optional pre-acquisition state. Inspection is not mandatory: placed → acquiring directly remains valid for orders that skip inspection; placed → inspection_in_progress → acquiring is the inspected path. So placed now has two forward paths.
  • LMN-owned — partners cannot set it. Attempting it via POST /v1/orders/{id}/status returns 403 invalid_status_transition with details.reason: "partner_not_authorized", alongside acquiring, secured, export_processing, in_transit.
  • Inspection failure resolves to cancelled (reason inspection_failed), never failed. inspection_in_progress → failed is not a valid transition.
  • order.status_changed webhooks now fire on entry to and exit from inspection_in_progress (placed → inspection_in_progress, inspection_in_progress → acquiring, inspection_in_progress → cancelled) and may carry inspection_in_progress in status / previous_status.
This is additive — no fields added or removed, no partner-driven transition changed. Clients with a strict status enum MUST tolerate the new value (handle unknown enum values defensively, per conventions).

v1.30 — 2026-06-03

Dealer (encar_*) vehicles now populate vin, transmission, engine_cc, and trim.

Partner-visible response change (dealer source)

These four fields were previously always null for dealer (encar_*) listings. They are now resolved from the encar listing and its inspection report:
  • transmission (auto | manual | cvt | dct) and engine_cc — from the listing spec.
  • trim — now uses encar’s canonical English grade name (a Korean grade that previously failed translation was being dropped to null).
  • vin — from the listing when present, otherwise promoted from the inspection report’s 차대번호 (Section 1).
vin is still typed string | null. It is null only when encar publishes no VIN for the listing — a minority of cars (e.g. listings whose inspection is an abbreviated record without the structured form). Across a random dealer sample, ~98% of cars now carry a VIN. Treat vin: null as “not available from source” and request the VIN from the dealer where your flow requires it (e.g. funding) — do not infer it. No fields were added or removed and the contract shape is unchanged — these fields simply carry data for dealer rows now. Auction (glovis/sk/aj/lotte/kcar) behaviour is unchanged.

v1.29 — 2026-05-28

pricing.breakdown.ocean_freight now varies by vehicle — it is no longer a flat $1,300 constant.

⚠️ Partner-visible response change (both auction and dealer sources)

ocean_freight (in pricing.breakdown, on both VehicleSummary and VehicleDetail) is now resolved per vehicle from its body type and maker:
  • Mercedes-Benz1800 for any body type.
  • Other makers, SUV-class (SUV, minivan, van, truck) — 1800.
  • Other makers, sedan-class (sedan, compact, wagon, coupe, convertible) — 1500.
  • Unknown / missing body type (non-Benz) — 1500. Dealer (encar_*) cars carry no body type, so they resolve via the Benz rule or this default.
The previous flat 1300 is no longer returned by any vehicle. estimated_landed reflects the per-vehicle freight accordingly. Read pricing.breakdown.ocean_freight from each response rather than hardcoding a value — the rates are operator-tunable and may change without a contract version bump. LMN commission (300),dealerfee(300), dealer fee (300), and discount mechanics are unchanged.

v1.28 — 2026-05-28

Inspection report reaches full dealer parity. The dealer (encar_*) inspection_report now carries every section our own consumer site renders. No fields were removed — category_grades[] and issues[] are retained for backward compatibility.

Added

  • inspection_report.inspection_sheet_url (string | null, all sources) — link to the source’s original inspection-sheet PDF. Populated for SK/AJ auction sources; null elsewhere (including dealer).
  • Dealer (encar_*) full parity. inspection_report.dealer_inspection now additionally carries:
    • vehicle_info (object) — inspection-form-specific particulars only (Valid Period, First Registered, Warranty, Engine Model, Base Price), keys+values EN-translated. Identity fields (make/model, year, plate, VIN, transmission, fuel) are omitted — read those from the top-level resource.
    • overall_status[] ({item, status, detail}) — section-2 rows (emissions, tuning, special history, usage change, recall, odometer, VIN marking, color, major options), EN-translated.
    • photos[] ({url, label}) — inspection front/rear photos.
    • certification ({inspector, notifier, date} | null) — inspector/notifier raw Korean company names; date translated.
    • registration_no (string | null) — 성능번호 inspection registration number.
  • inspection_report.inspector_notes now populated for dealer (previously null) — raw Korean free-text passthrough.
  • dealer_inspection.insurance_history / insurance_amount now populated from Encar’s insurance-record API (own-damage accident count + cost in KRW) — previously always null. Adds one upstream call to the dealer detail path; failures degrade to null and never block the response.
With this, a partner can render the same inspection report for dealer cars that lmnauto.com draws (vehicle info, overall status, accident/repair, exterior/frame diagram, mechanical checklist, inspector notes, photos, certification, insurance history).

v1.27 — 2026-05-28

Clean dealer (encar_*) cars now return a populated inspection_report (was null), and dealer checklist[] is now populated. A dealer car with no panel damage previously returned inspection_report: null — partner UIs showed “No inspection report available” even though the inspection existed and was simply clean. Root cause: the server discarded any inspection that had no damaged panels and no frame/panel status row, conflating “no damage” with “no data.”

Partner-visible response change (dealer source only)

  • Clean cars now return a report. GET /v1/vehicles/encar_* returns a populated inspection_report whenever Encar’s inspection page is reachable. A clean car reads as zero damage (dealer_inspection.critical_frame_damage = 0, exterior_damage = 0, has_accident = false, accident_summary = "No structural damage") with a populated mechanical checklist[]. inspection_report / body_condition are null only when Encar genuinely has no record (upstream 404) or a transient fetch failure occurs.
  • inspection_report.checklist[] is now populated for dealer cars — up to ~35 entries { group, item, result } covering the mechanical systems (engine, transmission, drivetrain, steering, braking, electrical, fuel), KO→EN translated with the same government-form dictionary used for auction sources and the consumer site. Every form row is passed through; an item the inspector left unmarked has an empty-string result.
  • New field inspection_report.dealer_inspection.simple_repair (boolean | null) — Encar’s 단순수리 (simple repair) flag. null when the page doesn’t state it. Additive, non-breaking.
  • has_accident now also reflects the accident-history block, not just frame-damage counts — a car with a cleanly replaced/repaired part (no panel-diagram marker) but recorded accident history now correctly reports has_accident: true / accident_summary: "Accident history reported".
  • Auction sources too: a clean car now returns a non-null body_condition. Previously an auction car whose inspection found no damage and had no pre-rendered diagram image returned body_condition: null; it now returns { image_url, panels: [] }. null is reserved for cars with no inspection data at all. (Glovis was unaffected — it always carries an image_url.)

v1.26 — 2026-05-27

fuel multi-word values now snake_case. Multi-word fuel tokens previously leaked an upstream space-form ("gasoline hybrid", "plug-in hybrid") into the partner contract — partner-visible in VehicleSummary.fuel, in /v1/vehicles/facets fuels[], and in any URL that needed encoding. Aligned to the options_include snake_case convention.

⚠️ Partner-visible response change (both auction and dealer sources)

VehicleSummary.fuel now emits snake_case for every multi-word value across all sources — auction (glovis_*, aj_*, kcar_*, etc.) AND dealer (encar_*). Previously the auction-side path bare-lowercased the BQ-stored string ("gasoline hybrid" survived), while the dealer-side path went through a Korean→English reverse map that emitted TitleCase-then-lowercase (also "gasoline hybrid"). v1.26 routes both through the same toPartnerFuelToken canonicalizer, so both emit "gasoline_hybrid" going forward.
  • Affected values: gasoline_hybrid (was "gasoline hybrid"), diesel_hybrid (was "diesel hybrid"), plug_in_hybrid (was "plug-in hybrid"), plus future multi-word values surfaced by facets (gasoline_plug_in_hybrid, lpg_plug_in_hybrid, dual_fuel_lpg — emission only; filter for these still no-ops until follow-up PRs land their FUEL_MAP entries).
  • Single-word values unchanged: gasoline, diesel, hybrid, electric, lpg, hydrogen.
  • Why no deprecation period: the space-form was never documented in schemas.mdx. Partners writing strict-enum validators against the published contract weren’t accepting the space-form anyway; partners reading values from facets or accepting whatever the response carried receive the new form transparently.
  • Unknown-fuel fallthrough also now canonicalizes. If encar emits a brand-new fuel token before we update FUEL_MAP, the partner receives the snake_cased form (e.g. "new fuel type""new_fuel_type") instead of the bare lowercased raw — keeps the format invariant intact even for forward-compat surface.

Other surfaces

  • Facets. /v1/vehicles/facets fuels[] now emits the same snake_case canonical form. Partners that read facets to build filter UIs receive values that work as ?fuel= filter inputs without further transformation.
  • Filter input is lenient. ?fuel= accepts both the new snake_case form AND the legacy space-form (URL-encoded gasoline%20hybrid still resolves correctly), as well as TitleCase. Mintlify documents only the snake_case form going forward; the lenient input is internal robustness, not a documented alias.
  • Order resource inherits the fix automaticallyOrder.fuel is computed at read time from the vehicle resource, so any hydrogen Nexo order placed during or after v1.26 reflects the new format on both the synchronous response and the webhook data.order payload (v1.18 invariant preserved).

v1.25 — 2026-05-27

Hydrogen added to the fuel enum. gasoline | diesel | hybrid | electric | lpg | hydrogen — partners can now filter for hydrogen vehicles (Hyundai Nexo and similar FCEVs) via ?fuel=hydrogen, and the response fuel field returns "hydrogen" instead of leaking the raw upstream Korean "수소".
  • What broke before. ?fuel=hydrogen was silently dropped (no matching forward map entry), so partners filtering for hydrogen received unfiltered mixed-fuel results. Separately, dealer responses with a Korean source token would emit fuel: "수소" to the partner — partner UIs with strict enum validators rejected the row.
  • What’s fixed. Both directions covered by a single FUEL_MAP entry: lowercase hydrogen translates to encar’s 수소 upstream, and 수소 translates back to hydrogen in responses.
  • Demand. Discovered via partner inventory probes on 2026-05-27 — facets endpoint was already advertising hydrogen as a valid value because two Hyundai Nexos sat in dealer inventory; partners reading facets would reasonably expect the filter to work.

v1.24 — 2026-05-27

Dealer (encar_*) inspection data now populated on GET /v1/vehicles/encar_*. The existing inspection_report and body_condition fields are no longer mostly-empty on dealer cars — they now carry the same per-panel damage data that encar’s own consumer site renders.
  • Partner contract unchanged. Same shape, same field names. Partners reading inspection_report.dealer_inspection.{critical_frame_damage,exterior_damage,frame_status,panel_status} and body_condition.panels[] start seeing populated data on cars where it was previously null. No client changes required.
  • New data source. Internally we switched from encar’s JSON inspection endpoint (which was empty for most dealer listings) to encar’s HTML inspection page (which encar’s own UI uses). Same parser as our consumer apps — single source of truth, no risk of drift between partner API and lmnauto.com.
  • Coverage. Cars with a posted inspection report now return populated data. Cars with no posted inspection (a small minority, typically dealer-direct listings) still return inspection_report: null and body_condition: null — same as today.
  • Insurance fields (insurance_history, insurance_amount) stay null for now. The HTML page does not expose them in structured form; we’ll surface them in a future release if we wire up encar’s separate carHistory API.
Triggered by a partner reporting that dealer cars had no inspection data to render. Now they do.

v1.21 — 2026-05-26

Order.options[] added — vehicle feature names are now snapshotted onto the order. The Order resource now includes an options array of English Title-case feature names (e.g. Sunroof, Leather Seats, LED Headlamps) — byte-equal to VehicleDetail.options on GET /v1/vehicles/{id} for the same vehicle. Partners no longer need a follow-up vehicle lookup to read feature information after creating an order.
  • Captured at order creation. The list is snapshotted from the live vehicle at POST /v1/orders time (BigQuery for auction sources, encar detail parse for encar_* dealer sources) and stored on the order row, mirroring the existing vin / license_plate snapshot pattern.
  • Webhook parity. Per the v1.18 guarantee, the same field appears on data.order in every order.* webhook event — no separate update needed.
  • Format. English Title-case strings for both auction and dealer rows. Dealer (encar_*) values are translated through the same dictionary that powers the consumer site, so what you receive on the API matches what’s rendered at lmnauto.com/.../encar_*. (The options_include query parameter on GET /v1/vehicles still uses snake_case tokens — that’s a separate filter input, not a response format.)
  • Backfill behavior. Orders created before v1.21 return options: []. We do not retroactively populate; the snapshot is creation-time.
  • No breaking changes. Purely additive. Consumers that ignore unknown fields continue to work; consumers that want to read options can do so immediately on new orders.

v1.21 hotfix (same day, included in same release)

Between the initial v1.21 deploy and partner announcement we caught that dealer (encar_*) VehicleDetail.options and the resulting Order.options snapshot were emitting raw Korean strings (e.g. 선루프) instead of English (Sunroof) — inconsistent with the consumer site and the auction-source path, which both translate. Fix:
  • GET /v1/vehicles/encar_* and the order-creation snapshot now both apply the ENCAR_OPTION_KO_EN dictionary used by the consumer site. New dealer orders will carry English names.
  • One-shot DB migration (0065_backfill_dealer_order_options_en.sql) translates the handful of dealer-order rows that snapshotted Korean strings during the brief window before the fix.

v1.20 — 2026-05-26

Landed-cost preview on list view. GET /v1/vehicles now returns pricing.discount_config and pricing.breakdown on every row of VehicleSummary. Same shape, same values as the detail endpoint — your list-view cards can render estimated landed cost (and the underlying line items) without a follow-up GET /v1/vehicles/{id} per row.
  • Strictly additive. No fields renamed or removed. Existing list-view consumers see new fields appear; ignoring them is safe.
  • Parity guaranteed. For a given vehicle, summary.pricing.discount_config and summary.pricing.breakdown are byte-equal to the same fields under vehicle.pricing from the detail endpoint.
  • auction_fee_config remains detail-only. The fee schedule is constant per source/category, so you can hold a static copy locally rather than receive it on every list row.
  • Pilot constants still apply. LMN commission 300,oceanfreight300, ocean freight 1,300, dealer fee/discount $300 each — same as v1.16. Future versions will externalize these per partner/market/source.
Triggered by ValU’s 2026-05-26 ask to surface breakdown + discount on the list response.

v1.19 — 2026-05-26

Dealer (encar_*) listings are now orderable. POST /v1/orders accepts vehicle_id values with the encar_ prefix, completing the dealer source loop that started with v1.15 (read) and v1.16 (pricing).
  • Buy-now semantics. Dealer listings are fixed-price — max_bid_amount_usd MUST be omitted or null. Non-null dealer values return 400 validation_error.
  • No auction cutoff. Dealer orders have auction_date: null and order_cutoff_at: null. The past_order_cutoff error does not apply.
  • No competitive bidding. Responses always carry is_highest_bid: null and current_max_bid_usd: null (dealer orders are not bid against other partners).
  • New error code: dealer_upstream_unavailable (503) can now surface on POST /v1/orders — fired when the encar detail upstream times out, returns 5xx, or returns an unexpected response shape during order creation. Retry-safe; the order is not created. Upstream 404 still maps to 404 vehicle_not_found (listing removed).
  • Idempotency, duplicate-order, and FX-lock semantics are unchanged — the same Idempotency-Key rules apply, (api_key, vehicle_id) active-order uniqueness is enforced, and fx_rate is locked at creation. Settlement-side semantics (no auction-fee config, dealer fee already in pricing.breakdown.dealer_fee) follow the v1.16 dealer pricing shape.
No breaking changes. Auction order behavior is unchanged.

v1.18 — 2026-05-20

Webhook payload ⇔ GET response parity. The data.order field of every order.* webhook event is now structurally identical to the response of GET /v1/orders/{id}. Same fields, same types, same enrichment.
  • is_highest_bid and current_max_bid_usd now populated in webhook payloads (previously always null). They are computed at emit time against the active bid landscape, matching the GET endpoint exactly.
  • Implementation guarantee: both code paths route through the same response mapper, and an invariant test enforces structural equality on every PR. Partners can reuse a single deserializer across both channels.
  • No breaking changes: if your handler was tolerating null for these two fields, it’ll continue to work. Webhook consumers who also call GET /v1/orders/{id} can drop the redundant call.

v1.17 — 2026-05-13

Order state-machine refactor: 9-state linear lifecycle. Replaces the 6-state model + fulfillment_detail field with explicit per-phase statuses. Partner integration must update.
  • proceed renamed to placed everywhere — order resource, filter enum, error messages, sandbox lifecycle.
  • shipping status removed. Logistics phase is now expressed as three explicit statuses:
    • export_processing — Korean export workflow underway (LMN-set on entry from secured).
    • in_transit — Bill of Lading issued and vessel has departed Korean port (LMN-set, not partner-set as previously planned).
    • customs — vessel arrived at destination port; vehicle in customs-bonded area (partner-set via POST /v1/orders/{id}/status).
  • fulfillment_detail field removed from the Order resource and from POST /v1/orders/{id}/status request bodies. Sending it returns 400 validation_error. The awaiting_pickup enum value is dropped — folded into export_processing.
  • order.fulfillment_updated webhook event removed. All logistics-phase changes now fire order.status_changed instead. Update any switch-on-event-type handlers.
  • Partner-driven status pushes simplified to two transitions:
    • in_transit → customs (vehicle arrived destination port).
    • customs → delivered (terminal, mandatory).
  • State machine invariants tightened:
    • failed is reachable from acquiring only. Post-secured failures are out of scope; they go offline.
    • cancelled is reachable from placed only. Post-acquiring cancellation goes offline.
  • Reason routing for auction_cancelled and seller_withdrew: terminal is decided by the order’s current status at the moment of the upstream event. Pre-acquiring discovery routes to cancelled (via cancellation_reason); post-acquiring routes to failed (via failure_reason). Same reason string, different field — webhook consumers should treat the terminal as authoritative.
  • Competing-order cohort rule: when multiple partners hold placed orders on the same auction lot, all transition to acquiring together when LMN starts bidding. No order goes placed → failed directly.
  • Zombie cleanup: orders stuck in placed past auction_date + 24h are auto-resolved to failed with failure_reason: auction_passed. The webhook collapses the synthetic intermediate state — previous_status: "placed" with an additive synthetic_acquiring: true discriminator. Treat as a normal failed event, or branch on the discriminator to suppress noise.
  • Reschedule auto-cancel scope: placed → cancelled (auction_rescheduled_too_soon) only fires from placed. Orders already in acquiring proceed normally on the new auction date (a separate order.auction_rescheduled event still fires).
  • Error taxonomy clarified: invalid_status_transition now distinguishes via HTTP status — 403 = partner attempted an LMN-owned status (details.reason: "partner_not_authorized"), 409 = sequence violation (details.reason: "out_of_sequence"). details.current_status set in both cases.
Migration checklist for integrators:
  1. Replace literal status comparisons: "proceed""placed"; "shipping""export_processing" / "in_transit" / "customs".
  2. Remove all reads of order.fulfillment_detail (always undefined now).
  3. Update POST /v1/orders/{id}/status request bodies to send {"status": "customs"} or {"status": "delivered"} — never fulfillment_detail.
  4. Remove case "order.fulfillment_updated" from webhook handlers; logic moves into the order.status_changed handler.
  5. Add support for the new synthetic_acquiring: true event-payload variant (or ignore it — partners can treat it as a normal failed).

v1.16 — 2026-05-13

Pricing shape rework: symmetric fees + fixed discount. Tightens dealer pricing semantics and makes the fee model uniform across sources.
  • pricing.breakdown.auction_fee and dealer_fee are now always integers. Branch on source, not on null.
    • Auction rows: auction_fee populated as before; dealer_fee: 0 (was null).
    • Dealer rows: auction_fee: 0 (was null); dealer_fee: 300 flat USD (was the KRW-derived 440,000 KRW conversion).
  • pricing.discount_config is now always populated. Was null for dealer.
    • Auction: bid-based fields unchanged.
    • Dealer: bid_threshold, rate_below, rate_at_or_above are all 0 (bid-based logic does not apply).
  • New field: pricing.discount_config.fixed_discount_amount (whole USD). Flat discount subtracted from estimated_landed at quote time, independent of bid.
    • Auction: 0 today.
    • Dealer: 300 — exactly offsets the dealer fee. Net economic effect on estimated_landed is zero, but the two levers are now independent and can be adjusted separately.
  • estimated_landed formula updated to subtract fixed_discount_amount in both auction and dealer paths. Auction landed values are unchanged today (fixed_discount = 0); dealer landed is unchanged today (+300 − 300 cancels).
  • Migration note for the recompute formula: your_total_discount = your_bid_rate_discount + fixed_discount_amount (the bid-rate component is unchanged from v1.15).

v1.15 — 2026-05-13

New feature. source=dealer is now functional — backed by a live proxy to a third-party retail inventory (encar). Roughly 10–18× expansion in accessible inventory for common Korean used-car queries compared to auction-only.
  • source=dealer is queryable. GET /v1/vehicles?source=dealer&make=<make>&... returns dealer listings with id prefix encar_<numeric>, source: "dealer", and a dealer-shaped pricing.breakdown carrying the dealer fee. (Pricing shape further refined in v1.16 — see above.)
  • Dealer constraints (new 400s):
    • source=dealer (alone or mixed) requires make. Multi-value make rejected.
    • Combining source=dealer with any auction-only filter (auction_status, accident_grade, exterior_grade, auction_date_*, auction_count_*) returns 400 validation_error.
    • Mixed source=glovis,dealer is first-page only — passing cursor returns 400 mixed_source_pagination_unsupported. Paginate each source separately.
  • Filter parity gaps for dealer: transmission, multi-value fuel, keyword, body_style are silently dropped from the dealer half (upstream DSL doesn’t support them). Auction half of a mixed request still honors them. (options_include was also dropped at launch but now filters dealer rows server-side — see v1.34.)
  • New response headers:
    • X-LMN-Dealer-Page-Max: 48 — when limit > 48 and dealer is in scope (upstream caps page size). Paginate dealer separately via cursor.
    • X-LMN-Partial-Dealer-Unavailable: <csv> — when at least one dealer source failed. The value is a comma-separated list of the failed dealer source names (encar, danawa, or encar,danawa). On a mixed request the 200 is still valid and auction is authoritative; on a pure dealer request a single source down still returns the survivor’s rows + this header (only an all-sources-down dealer request returns 503).
  • New error codes:
    • 400 mixed_source_pagination_unsupported — see above.
    • 503 dealer_upstream_unavailable — pure dealer request failed because the upstream is unreachable or returned an unexpected response shape. Mixed requests degrade instead of returning 503.
  • Detail endpoint accepts dealer IDs. GET /v1/vehicles/encar_<numeric> returns dealer detail with auction-only fields (comparables, body_condition, inspection_report, pricing.history, pricing.market_assessment, auction_fee_config) all null or empty. 404 vehicle_not_found only on upstream HTTP 404; other failure modes → 503. (discount_config shape further refined in v1.16 — now populated for dealer with zeroed bid-based fields.)
  • Mixed-source sort semantics: When sort=auction_date_asc (the default) is used on a mixed query, the limit budget is split between auction (sorted by date) and dealer (sorted by year desc, since dealer rows have no auction date). Other sort options sort across the merged stream as before.
  • /v1/vehicles/facets sources array now includes "dealer" when the upstream is reachable. Health is probed lazily and cached for 60 seconds; partners can treat the facets response as the canonical “currently queryable sources” signal.

v1.14 — 2026-05-13

Breaking change in pricing.breakdown. korean_export removed.
  • korean_export removed from pricing.breakdown. Korean-side export logistics are folded into ocean_freight. The field was previously deprecated (held at 0 for auction, null for dealer) but no production partner ever consumed the new shape, so the removal lands pre-launch with no migration window.
  • Clients that read pricing.breakdown.korean_export will get undefined; remove those reads and rely on ocean_freight for the combined Korean-export + ocean-freight figure.

v1.13 — 2026-05-12

Breaking change in pricing. Regenerate clients before consuming GET /v1/vehicles/{id}.
  • korean_export deprecated in pricing.breakdown (superseded by v1.14 removal — see above). Held at 0 for auction vehicles, null for dealer vehicles. Korean-side export logistics are now folded into ocean_freight.
  • lmn_commission value changed: $1,500$300.
  • ocean_freight value changed: $2,500$1,300 (now covers Korean export + ocean freight combined).
  • pricing.discount_config added (sibling to auction_fee_config). Shape: { bid_threshold, rate_below, rate_at_or_above }. The discount is bid-dependentbid_threshold is compared against the partner’s bid amount in USD, not the listing price. Partners apply rate_below / rate_at_or_above to their own bid to compute the discount for that bid; the invoice realizes the same rule against the actual hammer price (purchase_price_usd), which may be lower than max_bid_amount_usd. Current values: { bid_threshold: 25000, rate_below: 1.5, rate_at_or_above: 2 }.
  • pricing.breakdown.estimated_landed semantics changed to an illustrative single-bid total. Both bid-dependent fees (auction_fee and the discount derived from discount_config) are now evaluated against the same internal basis price LMN selects: sold_price if available, otherwise LMN’s market-derived final-price estimate, otherwise the auction starting price. For exact totals at YOUR bid, recompute using auction_fee_config and discount_config. estimated_landed is no longer a quote; it is an estimate.
  • See the Vehicles endpoint guide section “Recomputing landed cost” for the exact bid-level formula, rounding rules, and settlement caveats.

v1.12 — 2026-05-11

Classification: non-breaking infrastructure change. No request/response shape changes. fx_rate contract is unchanged: still number | null, still locked at POST /v1/orders, still immutable across PATCH / status updates, still null only when the upstream FX provider was unavailable at creation time.
  • FX source unified. fx_rate and all derived currency conversions now resolve through a single source — openexchangerates.org /historical/{date}.json, where date is yesterday in KST (D-1 close lock, KST midnight rollover). Previous behavior pulled USD/EUR/JPY/etc. from Frankfurter (ECB) and overlaid OXR for select managed-float currencies; consolidation removes that split.
  • fx_rate: null semantics unchanged. When openexchangerates.org is unreachable at POST /v1/orders, the order still creates with fx_rate: null and LMN applies the settlement-day rate offline at secured, exactly as before.
  • No action required for partners. No fields added or removed. No code regeneration needed.

v1.11 — 2026-05-06

vin added as a photo_tags[].tag value. When the API can identify the photo that shows the VIN plate (차대번호), that photo is tagged 'vin' in photo_tags[]. At most one vin tag per car. The position of the vin-tagged photo in photo_tags[] is not positional; it can appear at any index. Other tags remain positional heuristics from photos[] array order. The 'vin' tag may be absent — partners should not assume every car has it. Backwards-compatible: clients that ignored unknown tag values continue to work; clients that enum-validated tags must regenerate from the OpenAPI spec.

v1.10 — 2026-05-01

Vehicle response contract update for the initial partner pilot. Regenerate clients before consuming GET /v1/vehicles or GET /v1/vehicles/{id}.
  • Vehicle pricing stays in pricing, with USD as the documented unit. Every monetary number in partner vehicle responses is a whole USD integer unless explicitly documented otherwise.
  • Nested vehicle pricing fields no longer use _usd suffixes. Renames include:
    • pricing.listing_usdpricing.listing_price
    • top-level sold_price_usdpricing.sold_price
    • pricing.breakdown_usdpricing.breakdown
    • pricing.history[].price_usdpricing.history[].price
    • pricing.similar_sales[]comparables.past_sales[]
    • pricing.similar_upcoming[]comparables.upcoming_sales[]
    • pricing.market_assessment.market_median_usdpricing.market_assessment.market_median
  • pricing.delta_pct was removed. Use pricing.history, comparables.past_sales, and pricing.market_assessment for price movement and market context.
  • pricing.auction_fee_config is reduced to partner-useful fields only: { percentage, minimum, maximum }. minimum and maximum are source/category auction-fee caps converted to whole USD using the response FX snapshot.
  • pricing.breakdown.estimated_landed uses sold_price when available, otherwise listing_price. This makes sold-vehicle landed cost reconcile inside the same pricing object.
  • pricing.history[] now returns same-car auction appearances. Entries are matched internally by Korean license plate and include vehicle_id, auction_date, price, and result. vehicle_id is an auction listing/round ID, so it may differ between history rows for the same physical car. This is current-vehicle history, not comparable-car history.
  • Comparable vehicles moved to comparables. comparables.past_sales[] is sold-only and comparables.upcoming_sales[] contains future auction alternatives. Both include vin and license_plate when available.
  • Vehicle detail now populates body_condition, inspection_report, and photo_tags. body_condition.panels[] uses English labels only, and body_condition.image_url is returned only for LMN-mirrored media. photo_tags[] items are { url, tag }; array order follows photos.
  • Sandbox lifecycle testing uses POST /v1/orders/{id}/sandbox-status. The normal POST /v1/orders/{id}/status endpoint remains restricted to partner-owned fulfillment updates and delivered in every environment.
  • vehicle.transmission is now normalized to auto | manual | cvt | dct. Previously the response surfaced raw upstream tokens (A/T, M/T, CVT, DCT); responses now match the schemas.mdx enum. The filter param ?transmission= continues to accept both lowercase and raw forms — request shape is unchanged.
  • vehicle.pricing.sold_price is now strictly null unless auction_result is sold or negotiation_sold. Stale upstream soldPrice values on negotiation_requested / no_bid / upcoming rows are no longer surfaced.
  • vehicle.drivetrain is now an open string in the spec. Unknown drivetrain tokens pass through unchanged; codegen clients should treat the field as opaque rather than enum-rejecting.
  • GET /v1/vehicles filter docs: mileage_max_km (upper bound) documented. (Correction: an earlier draft of this entry listed mileage_min_km; it is not supported server-side — only mileage_max_km exists.)
  • Vehicle response examples corrected. thumbnail_url / photos[] examples now use the actual upstream lmnauto-auction-data GCS host that the API returns. The inspection_report example body now reflects the real mapper output (source / overall_grade / grade_description / accident_summary / has_accident / has_frame_damage / category_grades / checklist / accident_history / accident_cost_summary / issues / inspector_notes) instead of the previous placeholder fields.

v1.9.2 — 2026-04-30

Non-breaking (new filter + sandbox-only helper).
  • GET /v1/vehicles now supports auction_status. Use auction_status=upcoming to fetch upcoming listings directly. Allowed values: upcoming, sold, negotiation_sold, no_bid, negotiation_requested.
  • Sandbox-only POST /v1/orders/{id}/sandbox-status. Lets pilot partners simulate order status and fulfillment transitions to test webhook delivery. Available only on sandbox hosts with sandbox API keys.

v1.9.1 — 2026-04-29

Classification: non-breaking clarification. No request/response model shape changes. No endpoint path, method, required request field, response object field, or field type changed in this version. Added enum value
  • Order.failure_reason may now include seller_withdrew. This is an additive value on the existing nullable failure_reason field. Treat it as a terminal failed reason.
Clarified enum documentation
  • Order.fulfillment_detail readable values are now documented as: export_processing, awaiting_pickup, in_transit, customs, in_customs, at_destination_port, delivered.
  • Partner-writable fulfillment_detail values remain only in_transit and customs through POST /v1/orders/{id}/status.
  • status: delivered remains the only partner-writable status value. Partners still cannot push status: shipping.
Corrected examples only
  • Postman status-push examples no longer send invalid status: shipping; they now use fulfillment_detail: in_transit.
  • Postman order-creation examples no longer include stale body field idempotency_key; idempotency remains the Idempotency-Key HTTP header.
Corrected operational docs only
  • Webhook delivery testing now documents cron-window delivery instead of a hard 5-second expectation.
  • Pilot webhook destination setup now documents the current partner_webhook_configs DB row with secret_ref; there is no partner-facing webhook config route yet.

v1.9 — 2026-04-29

Behavior change (per-partner). Cutoff timing rule changes; partners using fixed cron schedules need to verify against the new value in order_cutoff_at.
  • order_cutoff_at is now auction_date − N minutes per partner. Replaces the prior “auction_date − 1 day, midnight KST” rule. N is configured per integration on the partner record. Default 1440 (24h) for inspection-heavy integrations; integrations without a secondary inspection step use a shorter lead time (e.g. 60 minutes) covering bid submission only. Stop computing cutoff client-side; always read order_cutoff_at from the response.

v1.8.1 — 2026-04-27 (hotfix)

  • thumbnail_url now always returns a working URL. Previously the field surfaced a derived thumbs/images/... path that 404’d because the upstream thumbnail-generation pipeline isn’t built yet (integration-spec §10 #13). Partner API now returns the raw images/... URL directly — full-size, but always loads. Run your own resize CDN if list-view bandwidth matters.

v1.8 — 2026-04-27

Non-breaking (additive event types + Order resource fields). Existing order.status_changed, order.fulfillment_updated, and order.auction_rescheduled handlers are unaffected; existing Order parsers ignore new fields.
  • vin and license_plate on Order resource. Snapshotted at order creation (immutable for the life of the order). Webhook payloads now carry both fields without a follow-up GET /v1/vehicles/{vehicle_id} round-trip — match dealer records directly from the event. vin is null when the upstream listing has no VIN; license_plate is the Korean plate as-shown (e.g., 12가 3456). Added per a partner integration commitment.
  • New webhook event: order.price_updated. Fires when the vehicle backing an active proceed order has its listing_usd refreshed by the upstream scraper. Payload includes previous_listing_usd, current_listing_usd, delta_usd, delta_pct. Bid-strategy partners can use this to decide whether to PATCH max_bid_amount_usd mid-flight. Does not fire on terminal-state orders. See order.price_updated.
  • New webhook event: order.re_auctioned. Fires when a vehicle from one of your terminal orders (failed / cancelled / delivered) reappears in a new auction round. Matched by Korean license plate so the same physical car is caught even when it moves between auction houses. Useful for offering the dealer a second chance. Deduped via (partner_id, license_plate, new_auction_date) so re-scrapes don’t double-fire. See order.re_auctioned.
  • GET /v1/orders/{id}/events documented in /endpoints/orders. The endpoint already shipped in earlier pilot builds; the partner-facing docs were missing. Returns LMN-side webhook delivery log (delivery_status, attempts, last_response_code, payload) for self-service “did I miss a webhook?” debugging. See GET /v1/orders//events.
  • OpenAPI OrderEvent schema corrected. Previously declared fields (event_type, from_status, to_status, actor, metadata) that did not match the live response. Now reflects the actual shape (type, delivery_status, attempts, last_response_code, next_retry_at, created_at, delivered_at, payload). Codegen clients should regenerate.

v1.7 — 2026-04-24

Non-breaking (additive fields + new endpoint). Pilot partners on v1.6 can keep working against the old response shape; the new fields are ignorable.
  • New endpoint: PATCH /v1/orders/{id}. Update max_bid_amount_usd on an existing order. Allowed only while status=proceed and before order_cutoff_at. No Idempotency-Key required. See PATCH /v1/orders/ — Update max bid.
  • fx_rate is now locked at POST /v1/orders time (previously: at secured). The KRW-per-USD rate is captured once at order creation, persisted on the row, and does not change via PATCH or status pushes. Gives partners a deterministic maximum settlement exposure from commit time. null on rare FX-provider outages.
  • Comparable sales on GET /v1/vehicles/{id}. Up to 20 comparable cars (same make+model · year ±1 · mileage ±20,000 km · past 4 weeks). Excludes the target vehicle and any same-plate re-listings. See Similar-sales definition.
  • pricing.market_assessment on GET /v1/vehicles/{id}. One-line server-side price verdict with five labels (great / good / fair / wait / limited) over the sold comparable set, plus price_drop when the same vehicle has appeared in prior auction rounds. See Market assessment.
  • thumbnail_url now populated on both GET /v1/vehicles and GET /v1/vehicles/{id} (was always null pre-2.7). Derived from the first auction-bucket image. Returns null only when the listing has no images at all. Past-auction vehicles may surface a URL that 404s until a backfill runs — treat 404 as “no thumbnail”, not a broken API.
  • VehicleSummary.fuel is now nullable. Previously declared non-null in the spec but could be absent in rare upstream data. OpenAPI now marks it nullable: true; partners with strict enum validators must accept null. Post fuel-null crash fix deployed 2026-04-23.

v1.6 — 2026-04-18

Breaking for integrators still on v1.5 drafts.
  • Dealer-deposit removed. amounts.dealer_deposit_usd, amounts.dealer_refund, amounts.cancellation_refund no longer exist. POST /v1/orders is a bid commitment, not a deposit attestation; payment is owed only on secured.
  • Error codes split into resource-specific. not_foundvehicle_not_found / order_not_found. unauthorizedmissing_api_key / invalid_api_key. forbidden and invalid_transition unified to invalid_status_transition (with HTTP 403 vs 409 distinguishing partner-attempt vs state-machine).
  • New 422 idempotency_key_reused. Reusing an Idempotency-Key with a different vehicle_id now returns this distinct error rather than silently returning the original order.
  • GET /v1/orders filters extended. Added ids (CSV batch lookup, max 100), from/to (half-open [from, to) on created_at, replaces the previous created_after/created_before), sort enum (created_desc default, created_asc, auction_date_asc, updated_desc). Cursor is now sort-specific.
  • Facets response uses plural field names. fuels, transmissions, sources (was fuel, transmission, source). Explicit “no per-value counts” — UIs surface plain values.
  • Timestamp convention made explicit. Source UTC offset must always be preserved — never Z-normalized. Node Date.toISOString() flagged as not acceptable.

v1.5 — 2026-04-16

QA + business review fixes: all USD amounts are whole dollars (not cents); idempotency keys are permanent (no TTL); API key length standardized to 32 chars; fulfillment_detail write permissions clarified (LMN: export_processing/awaiting_pickup; partner: in_transit/customs optional); added auction_passed failure_reason for zombie order cleanup; added vehicle_unavailable (410) for buy-now vehicles; clarified cancellation_reason vs failure_reason mutual exclusivity; added FX risk note (auction-time rate); added post-secured disputes note (offline only).

v1.4 — 2026-04-16

Revised competitive bidding: all orders accepted (removed bid_too_low). Response includes is_highest_bid + current_max_bid_usd. outbid_internally happens at auction time, not at order creation.

v1.3 — 2026-04-16

Add competitive bidding: bid_too_low (409) with current_max_bid_usd disclosure. New failure_reason: outbid_internally. Webhook fires when lower bid is replaced.

v1.2 — 2026-04-16

Add auction_fee_usd, failure_reason, shipment fields to Order. Remove fx_snapshot from API. Updated webhook payload examples with realistic data per transition.

v1.1 — 2026-04-16

Add max_bid_amount_usd to order creation. Add auction_count_min/max vehicle filters. Add price_drop_pct, first_price_usd to vehicle response. Full webhook spec.

v1.0 — 2026-04-16

6-state order model. Collapsed 14-state lifecycle to 6 states: proceed → acquiring → secured/failed → shipping → delivered + cancelled. Removed inspection from order lifecycle (secondary inspection is optional post-purchase service). Removed POST /v1/orders/{id}/decision and GET /v1/orders/{id}/inspection endpoints. Added fulfillment_detail field for granular shipping tracking. Added order_cutoff_at. Renamed eta_lagoseta_destination with destination_port. Removed inspection_fee from Order amounts. Simplified cancellation to proceed-only via DELETE.