Skip to main content
Eagle Eye is LMN’s saved-condition and alerting surface. It lets a partner preview a buying condition, save it as a watch, review the current match list, and receive webhook batches when new opportunities or changes appear. For product context, see the partner-facing overview: Eagle Eye overview.

API surfaces

SurfaceEndpointWhat it does
Search previewPOST /v1/eagle-eye/searchEvaluate a condition once and return current matches.
Saved watchesPOST /v1/eagle-eye/watchesSave a condition and seed its current match list.
Watch managementGET /v1/eagle-eye/watches, GET/PUT/DELETE /v1/eagle-eye/watches/{watch_id}List, inspect, update, or delete watches.
Watch controlsPOST /v1/eagle-eye/watches/{watch_id}/mute, /unmute, /pause, /resumeControl notifications and collection.
Match inboxGET /v1/eagle-eye/watches/{watch_id}/matchesRead current matches for a watch.
Match triagePOST /v1/eagle-eye/watches/{watch_id}/matches/hide, /matches/unhideHide or restore reviewed vehicles in bulk.
Event recoveryGET /v1/eagle-eye/watches/{watch_id}/eventsRead the webhook history for a watch.

Schema reuse

Eagle Eye reuses the existing Open API vehicle model. Search results, match rows, and webhook additions / price_changes embed the same VehicleSummary shape returned by GET /v1/vehicles; Eagle Eye metadata such as watch_id, match_reason, and signal_detail wraps that vehicle object instead of replacing it. The vehicle_id in a match is the same ID used by GET /v1/vehicles/{id} and the existing order API.

Webhook

Eagle Eye emits one webhook type:
eagle_eye.match
See webhook event types for the full payload shape, and webhook signing & retries for delivery behavior. The event is batched and can include:
  • additions — new vehicles that matched the watch,
  • price_changes — watched vehicles whose price dropped enough to notify,
  • removals — vehicles that disappeared or no longer qualify.
Signal matches include signal_detail so the partner can explain the alert:
SignalDetail
repeat_listinglisting_count, cumulative_drop_pct, and window_days.
undervaluedreference_price_krw, listing_price_krw, and delta_pct.
price_dropprevious_price_krw, current_price_krw, and drop_pct.
price_drop is drop-only. If a vehicle price increases while still qualifying, the current match updates silently and loses the stale signals.price_drop reason. If the increase makes it fail the watch’s filters, such as rising above price_max_krw, LMN sends a removal instead. Webhook delivery uses the same HMAC signing and retry behavior as order webhooks.

Acting on a match

Eagle Eye does not create orders directly. A match carries the same vehicle_id used by the existing order API. When the dealer wants to act, send that vehicle_id to POST /v1/orders.

Filter reference

New filter fields (search and watch bodies)

The following fields are accepted in the filters block of POST /v1/eagle-eye/search, POST /v1/eagle-eye/watches, and PUT /v1/eagle-eye/watches/{watch_id}:
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. Supply exactly one token — an array or an unrecognized token returns 400 validation_error. The single-token constraint exists because the dealer (encar) search DSL cannot express a fuel OR clause; allowing multiple values would silently produce zero dealer results.
no_accident
boolean
true filters to clean cars only. false or absent applies no filter.Per-source semantics differ — see the damage filter semantics section below before using this field across mixed sources. For frame-sensitive buyers, no_frame_damage is the source-equivalent alternative.
no_frame_damage
boolean
true excludes cars with structural frame damage; exterior panel repair is permitted. false or absent applies no filter.This filter is source-equivalent — it maps to the same structural-integrity standard on both dealer and auction sources. Recommended for buyers who care about frame integrity but accept repaired exterior panels.
estimated_landed_max_usd
number
Maximum estimated landed cost in USD (positive integer). Computed per-vehicle using your partner’s FX rate and freight configuration.Two implications:
  • Null exclusion: vehicles whose landed cost cannot be computed are excluded when this filter is active.
  • FX drift: a pure exchange-rate move (no listing price change) can admit or remove vehicles from an active watch. Set your bound with a buffer for normal FX volatility, or use the flexibility.fields.estimated_landed_max_usd tolerance (see flexibility).

Damage filter semantics

no_accident and no_frame_damage map to different underlying predicates depending on inventory source:
FilterDealer (encar)Auction (Glovis / SK / AJ / Lotte / KCar)
no_accident: trueStrictly 무사고 — zero frame damage AND zero exterior repair recordsGrade A only — no structural panel exchange; exterior panel work is permitted
no_frame_damage: true무사고 or 단순수리 — zero frame exchange; simple exterior repair acceptedGrades A + B — no structural exchange in either grade
D7 asymmetry: no_accident is not semantically equivalent across sources. On dealer it means zero exterior repairs; on auction it means no structural exchange (exterior work is permitted because grade A is the strongest clean-accident classification auction sources express). no_frame_damage is source-equivalent and is the recommended filter for buyers who need a consistent structural-integrity guarantee across both dealer and auction inventory.
Approximately 3% of dealer stock lacks a valid vehicle inspection record (성능기록부). When either damage filter is active, those cars are excluded — unknown condition is treated conservatively as not qualifying. This exclusion is not surfaced as a separate indicator in the response.
If both a damage boolean (no_accident or no_frame_damage) and accident_grade_in are present in the same request, the engine intersects the two. A contradictory combination — for example no_accident: true with accident_grade_in: ["B"] — yields zero matches rather than a 400 error.

Buyer-fit tolerance (flexibility)

flexibility is an optional top-level block in the search and watch request body. It widens numeric filter bounds and expands categorical filter alternatives so a watch catches cars that fall just outside its strict criteria. When absent, all filters are applied strictly. Flex matches are tagged match_type: "flex" in every response surface (search rows, match list items, and webhook entries). Strict matches carry match_type: "exact".

Request shape

{
  "filters": {
    "source": ["dealer"],
    "make": "Hyundai",
    "model": "Santa Fe",
    "fuel": "gasoline",
    "year_min": 2020,
    "mileage_max_km": 225308,
    "estimated_landed_max_usd": 13700,
    "no_frame_damage": true
  },
  "flexibility": {
    "default": "none",
    "fields": {
      "estimated_landed_max_usd": { "level": "medium" },
      "mileage_max_km":           { "level": "small" },
      "year_min":                 { "level": "none" },
      "fuel":                     { "level": "none" },
      "no_frame_damage":          { "level": "none" },
      "model": {
        "level": "small",
        "allowed_values": ["Santa Fe Hybrid"]
      }
    }
  }
}
flexibility.default
FlexLevel
Applies to all numeric and year filter fields that are present in the request and not overridden by flexibility.fields. Does not apply to categorical fields (model, trim, fuel). Accepted values: none | small | medium | large. Default none.
flexibility.fields
object
Per-field overrides. Each key must reference a filter field that is present in the same request — a key referencing an absent filter returns 400 eagle_eye_invalid_flexibility.
flexibility.fields[field].level
FlexLevel
required
Tolerance level for this field. none | small | medium | large.
flexibility.fields[field].allowed_values
string[]
For categorical fields (model, trim, fuel) only. Each value creates one additional query variant. Required when level != 'none' on a categorical field. Maximum 4 values per field.

Numeric and year tolerance

These fields accept a level tolerance: year_min, year_max, mileage_max_km, price_max_krw, estimated_landed_max_usd, auction_count_min, auction_count_max.
LevelPercent tolerance (max bounds)Year slack
none0%±0 years
small+2% (floor)±0 years
medium+5% (floor)±1 year
large+10% (floor)±2 years
Year fields always use the slack column. Non-year max bounds apply floor(value × (1 + tolerance)); min bounds apply ceil(value × (1 − tolerance)). Santa Fe example — strict estimated_landed_max_usd: 13700 with level: "medium":
  • Effective bound: floor(13700 × 1.05) = 14385
  • A vehicle priced at $13,782 is admitted and tagged match_type: "flex"
  • A vehicle priced at $13,200 is admitted and tagged match_type: "exact"

Categorical tolerance

Categorical fields (model, trim, fuel) do not have a numeric percentage tolerance. To widen them, supply allowed_values — each value creates one additional search variant that runs alongside the strict base query. level must be none (explicit no-op) or paired with non-empty allowed_values. Supplying level: "small" (or any non-none level) without allowed_values on a categorical field returns 400 eagle_eye_invalid_flexibility.

Non-flexable fields

no_accident, no_frame_damage, source, and make only accept level: "none". This serves as an explicit per-field override of flexibility.default — useful to pin a boolean outside the default without setting a tolerance. Passing any other level for these fields returns 400 eagle_eye_invalid_flexibility.

Caps

LimitValue
allowed_values per categorical field4
Total variant product (∏ (len(allowed_values) + 1) across all categorical fields)8
Exceeding either cap returns 400 eagle_eye_invalid_flexibility with details.field naming the offending field.

Response metadata — match_type and flex_detail

Every match-bearing surface now carries match_type, plus flex_detail describing any applied tolerance — surfaces differ on whether an exact match omits flex_detail or includes it as null, so see each surface’s example below for its exact-match shape. These fields are orthogonal to match_reasonmatch_reason tells you why a vehicle is in the watch (filters / signals); match_type / flex_detail tell you how closely it matched.

Search row

match_type
"exact" | "flex"
required
"exact" when all filters matched strictly. "flex" when at least one filter matched only within its tolerance.
flex_detail
object
Present only when match_type is "flex". One key per field that required the tolerance. Each value is either a numeric entry or a categorical entry:Numeric entry: { requested: number, level: string, effective: number, actual: number }Categorical entry: { requested: string, allowed_values: string[], actual: string }
Numeric flex example — landed cost just above the strict limit:
{
  "id": "encar_12345",
  "source": "dealer",
  "pricing": {
    "breakdown": { "estimated_landed": 13782 }
  },
  "match_type": "flex",
  "flex_detail": {
    "estimated_landed_max_usd": {
      "requested": 13700,
      "level": "medium",
      "effective": 14385,
      "actual": 13782
    }
  }
}
Categorical flex example — alternative model admitted:
{
  "id": "encar_67890",
  "source": "dealer",
  "match_type": "flex",
  "flex_detail": {
    "model": {
      "requested": "Santa Fe",
      "allowed_values": ["Santa Fe Hybrid"],
      "actual": "Santa Fe Hybrid 1.6T"
    }
  }
}
An exact match returns "match_type": "exact" with no flex_detail key.

Match list item

GET /v1/eagle-eye/watches/{watch_id}/matches now includes match_type and flex_detail on every row. Unlike search rows, match list items always include the flex_detail key; it is null for exact matches.
{
  "vehicle_id": "encar_12345",
  "source": "dealer",
  "price_krw": 46000000,
  "match_reason": ["filters"],
  "signal_detail": null,
  "match_type": "flex",
  "flex_detail": {
    "estimated_landed_max_usd": {
      "requested": 13700,
      "level": "medium",
      "effective": 14385,
      "actual": 13782
    }
  },
  "first_matched_at": "2026-06-11T09:00:00+09:00",
  "last_seen_at": "2026-06-11T09:00:00+09:00",
  "hidden": false
}

Webhook eagle_eye.match

additions and price_changes entries in the eagle_eye.match webhook now carry match_type and flex_detail:
{
  "type": "eagle_eye.match",
  "data": {
    "additions": [
      {
        "vehicle": { "id": "encar_12345", "source": "dealer" },
        "match_reason": ["filters"],
        "match_type": "flex",
        "flex_detail": {
          "estimated_landed_max_usd": {
            "requested": 13700,
            "level": "medium",
            "effective": 14385,
            "actual": 13782
          }
        }
      }
    ],
    "price_changes": [
      {
        "vehicle": { "id": "encar_12345", "source": "dealer" },
        "previous": { "price_krw": 47000000, "observed_at": "2026-06-10T09:00:00+09:00" },
        "match_reason": ["filters", "signals.price_drop"],
        "match_type": "flex",
        "flex_detail": {
          "estimated_landed_max_usd": {
            "requested": 13700,
            "level": "medium",
            "effective": 14385,
            "actual": 13782
          }
        }
      }
    ],
    "removals": []
  }
}
Exact-match webhook entries omit the flex_detail key (same omission behavior as search rows). removals entries are not changed — they carry only vehicle_id and reason.

Watch response

The watch resource (GET/POST/PUT /v1/eagle-eye/watches) now echoes back the stored flexibility configuration:
{
  "id": "wat_01HZEXAMPLE",
  "status": "active",
  "filters": { "source": ["dealer"], "make": "Hyundai", "model": "Santa Fe" },
  "flexibility": {
    "default": "none",
    "fields": {
      "estimated_landed_max_usd": { "level": "medium" }
    }
  }
}
flexibility is null when not configured. A PUT request without a flexibility key clears it to null (full-replace semantics, same as signals).

Eagle Eye error codes

CodeHTTPWhen
eagle_eye_filters_required400filters block absent; neither make nor model present; or make missing when source includes dealer.
eagle_eye_invalid_filter_for_dealer400Auction-only filter present when source includes dealer (e.g., accident_grade_in, auction_count_min).
eagle_eye_invalid_flexibility400flexibility block is structurally invalid: a field key references an absent filter, level != 'none' 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.
eagle_eye_invalid_signals400Signal bound or shape violation (e.g., min_listings out of range).