Developer beta

Market price API

Build community tools with Last Epoch unique and exalted-affix asking prices.

Quick start

Get unique and exalted-affix asking prices as JSON. No account or API key is required.

First fetch active cycles and choose an ID. Use defaultCycleId when present, or let the user choose. Then fetch the complete price snapshot for that cycle.

Use https://bazaar.leoverlay.com as the API origin. The paths below use Swordfish as an example cycle. Prices are integer gold values; timestamps are Unix milliseconds.

# Active cycle IDs, labels, lastObservedAt, and defaultCycleId
GET /v1/cycles

# Complete current snapshot; cycle is a required ID
GET /v1/pricing/uniques/overview?groups=1&cycle=Swordfish

# History for unique 6, LP 0; days supports 7, 30, or 120
GET /v1/pricing/uniques/overview/6/history?cycle=Swordfish&legendaryPotential=0&days=7

# Complete current exalted-affix snapshot
GET /v1/pricing/affixes/overview?cycle=Swordfish

# History for affix 100 on item type 2 at T7; days supports 7, 30, or 120
GET /v1/pricing/affixes/overview/2/100/history?cycle=Swordfish&tier=6&days=7

Item IDs and catalogue

Load /catalogue/{catalogueRevision}.json from this website. Use the revision in the price response, which may differ from the latest manifest.

Map uniqueId to the catalogue’s names, itemType, subType, and pricingEligible fields. ID 0 is valid. Names are labels, not keys. Keep each cycle and exact LP (0–4) separate.

With groups=1, the overview returns groups: item summaries with native variants and roll buckets in children. Each group contains exact-LP prices. Item summaries use all eligible listings, not an average of child medians. No client aggregation is needed. rollsAvailable is false when roll groups could not be loaded.

Without groups=1, the v1 endpoint retains its flat prices response. Existing clients can continue using variants=1 to include exact native variants; groups=1 takes precedence when both options are supplied.

Use kind to distinguish item, variant, and roll groups. Treat id as an opaque group key. Keep parent summaries separate from their children: their listings overlap, so do not add their counts or combine their medians. Follow the catalogue pricing LP values; items without pricing LP use only LP 0. Ordinary non-LP items can still include empty LP 1–4 slots in the feed.

Current versions: overview schema 1, catalogue schema 4, methodology 1. Validate each independently from the catalogue revision.

Optional catalogue pricing metadata supplies variant affix IDs and labels, required variant count, and pricing LP values. For Unsated Rage, Withstand the Elements, and Frostborn, empty or omitted variant IDs select the all-variant item summary; populated IDs select an exact native variant. Rolls and tiers are not part of variant identity.

History accepts variantAffixIds=1140,1141 for a Withstand pair. Pair order is immaterial; duplicate IDs are rejected. Batch price cohorts accept the same field as an array.

Icons use /catalogue/{revision}/icons/{uniqueId}.webp. Some excluded records have no icon.

FieldMeaning

snapshotAt / expiresAt

Envelope timestamps describe the item/variant snapshot and its expiry (at most two hours later, or sooner if a last-known reference reaches seven days). Each price also has an expiresAt; roll prices supply their own snapshotAt and expiry.

uniqueId / legendaryPotential / variantAffixIds

The unique family, exact pricing LP, and native variant; empty variant IDs identify the item summary.

lastKnownAt

Original snapshot timestamp, present only for last-known references.

p50Gold

Median asking price in gold.

p25Gold / p75Gold

Lower and upper bounds of the middle 50% of asking prices.

positivePriceCount

Observed positive-price listings in this snapshot, not sales.

Example JSON response
// Illustrative item group from an overview response; other groups and LPs omitted
{
  "schemaVersion": 1,
  "cycle": "Swordfish",
  "snapshotAt": 1790165100018,
  "expiresAt": 1790172300018,
  "methodologyVersion": 1,
  "catalogueRevision": "2446facc6a45417a1e7e9de0e30af052847a3a64",
  "rollsAvailable": true,
  "groups": [{
    "id": "6",
    "uniqueId": 6,
    "kind": "item",
    "variantAffixIds": [],
    "children": [],
    "prices": [{
      "uniqueId": 6,
      "expiresAt": 1790172300018,
      "legendaryPotential": 0,
      "status": "available",
      "positivePriceCount": 52,
      "p25Gold": 17377,
      "p50Gold": 23000,
      "p75Gold": 23000
    }]
  }]
}

Unique roll groups

Roll children carry roll.rule and roll.bucket. match meets every rule condition; other contains classifiable listings that do not meet every condition. Missing or conflicting condition rolls are excluded from both buckets. The parent remains the overall median.

Rules are shared across LPs, but each LP independently qualifies for roll pricing. A roll child may have prices for only some LPs. Use the parent price when that LP has no roll split; do not substitute another LP or treat a missing bucket as zero. rollsAvailable: true means the roll read succeeded, not that every item has a split.

Roll prices include compact seven-day history points as [snapshotAt, medianGold], preserving null gaps. Check each price’s own expiresAt; do not use the item snapshot’s expiry for roll prices.

GET /v1/pricing/uniques/overview/{uniqueId}/rolls returns the rule, current useSplit decision, bucket prices, and detailed history. Supply cycle, legendaryPotential, and days (7, 30, or 120); native variants also need variantAffixIds. Omit ruleVersion for the active rule, or supply a version to read that rule. When useSplit is false, use ordinary pricing. The ordinary /history endpoint returns the parent cohort’s history, not roll-bucket history.

Roll prices and history
GET /v1/pricing/uniques/overview/6/rolls?cycle=Swordfish&legendaryPotential=0&days=7

Exalted affix prices

Affix prices use the exact cohort { itemType, affixId, tier }. itemType and affixId are numeric game IDs. tier uses the stored zero-based value: 5 means T6 and 6 means T7. Keep the same affix separate across item types and tiers.

The overview returns every current affix quote for a cycle in quotes. A quote is available when the snapshot has at least 15 positive-price listings. insufficient-data and stale quotes have null percentiles and must not be treated as zero. The envelope supplies snapshotAt, expiresAt, and methodologyVersion.

POST /v1/pricing/affixes/history returns seven days of compact median history for 1 to 250 distinct cohorts, in request order. Each point is [snapshotAt, medianGold]; medianGold is null when that six-hour bucket is unavailable or has fewer than 15 listings.

GET /v1/pricing/affixes/overview/{itemType}/{affixId}/history returns detailed 7-, 30-, or 120-day history for one cohort. Supply cycle, stored tier, and days as query parameters. The response uses prices[0].points, with six-hour resolutionSeconds; each point includes its status, sample count, and percentiles.

EndpointResult

GET /v1/pricing/affixes/overview?cycle={cycle}

Complete current affix quote snapshot.

POST /v1/pricing/affixes/history

Compact seven-day median history for up to 250 cohorts.

GET /v1/pricing/affixes/overview/{itemType}/{affixId}/history?cycle={cycle}&tier={tier}&days={days}

Detailed history for one exact cohort.

Batch history request
POST /v1/pricing/affixes/history
Content-Type: application/json

{
  "cycle": "Swordfish",
  "cohorts": [
    { "itemType": 2, "affixId": 100, "tier": 6 },
    { "itemType": 2, "affixId": 101, "tier": 5 }
  ]
}

Unique price statuses

available rows contain current percentiles. last-known rows retain the latest qualifying median, range and listing count from the past seven days, within the same cycle, exact LP, exact native variant, and compatible catalogue revisions. Label these as historical and show lastKnownAt. All other statuses have null prices; treat them as unknown, never zero. Unsupported variants are excluded.

Before the first completed snapshot, snapshot metadata is null and groups is empty. After a snapshot exists, the overview includes supported families and native variants at every supported pricing LP, including rows without observations. Roll buckets are included only where the snapshot uses a split, and do not use last-known fallback.

History returns the requested identity, range, versions, resolutionSeconds, and points. Six-hour points use their own sample gate. An old point can be valid for its date without being a current quote. Keep gaps visible; last-known references do not fill history gaps or create new observations.

StatusUse

available

Use only while expiresAt is in the future.

last-known

Historical reference with at least ten listings. Use only before expiresAt, with its original lastKnownAt date.

insufficient-data

Fewer than ten positive-price listings and no qualifying reference within seven days. No usable quote.

stale

Current snapshot is more than two hours old. No usable quote.

unavailable

No current cohort price or qualifying reference within seven days. No usable quote.

Refresh and errors

Poll about hourly with jitter. Store ETag and send it unchanged in If-None-Match. A 304 reuses the existing body; it does not extend expiresAt. Stop displaying or classifying a current quote when it expires, even if a refresh fails.

Overview and roll-detail HTTP caching lasts at most 60 seconds, capped by expiry. Cycles, other history reads, and the catalogue manifest use up to five minutes. Versioned catalogue assets are immutable.

The service caches overview/history reads for up to five minutes. Allow that interval for active-cycle changes to propagate. Archived cycles are not available; do not infer cycle identity from labels, dates, or observation times.

Treat network errors, invalid JSON, unknown versions, and missing catalogue mappings as failed refreshes.

HTTP statusAction

304

Reuse the cached body without extending expiresAt.

400

Correct the request parameters.

409

Re-read active cycles and ask the user to choose again.

429 / 503

Wait for Retry-After before retrying. Keep expired quotes unavailable.

Request limits

Limits apply per IP per minute, including conditional requests. Unique and affix overviews share the overview bucket; their detailed history, affix batch history, and roll-detail reads share the history bucket. Search requests have a separate bucket. These beta limits do not guarantee capacity or uptime.

The documented read endpoints support credential-free CORS and expose ETag, Retry-After, and Cache-Control. Batch-history requests use POST with Content-Type: application/json.

EndpointRequests / minute

Overview

30

History

60

Cycles

120

Node.js example

Download the Node.js example and run it with Node 22 or newer and an active cycle ID. It loads the complete feed and matching catalogue, retains ETags in memory, and exposes only fresh, available quotes. It does not fetch every item separately.

The example flattens item summaries and exact native variants, keeping variantAffixIds in each output identity. It intentionally leaves out roll buckets and last-known references; use the group hierarchy and roll rules above if your integration needs those.

For a long-running integration, keep one reader per cycle, call refresh() on your schedule, and call getUniquePrices() when using the data. getUniquePrices() rechecks expiry each time. Failed refreshes clear usable output and report any Retry-After header; the example does not run an automatic retry loop.

node unique-prices.mjs Swordfish

// Or import it in your Node application:
import { createUniquePriceReader } from './unique-prices.mjs';
const reader = createUniquePriceReader('Swordfish');
await reader.refresh();
const prices = reader.getUniquePrices(); // [] when expired or unavailable

Coverage and limitations

These are community-observed asking prices, not sales, total stock, or individual stat-roll valuations. Coverage depends on the searches players make. Sample counts do not establish independent sellers or protect against manipulated listings.

The feed supports external price-aware tools. Importable game-filter generation, automatic in-game updates, and exact unique-ID matching have not been verified. The example outputs data, not a game filter.

Multiple uniques can share a base type. Keep unknown or unsupported items visible; do not turn an unpriced item into a hide rule. Preserve build-specific keep rules. Validate a real exported filter and its import behavior before advertising filter support.

Data reuse

You may use, copy, adapt, and redistribute our aggregate price data for free, including in commercial tools. Credit Last Epoch Overlay and link to https://www.leoverlay.com/developers wherever you display or distribute the data.

This permission covers our aggregate price data. It does not grant rights to game artwork, names, or other third-party assets, and does not imply endorsement by Eleventh Hour Games. Data and API availability are provided without guarantees; respect the published request limits.