Connectors: luxury/watch/sneaker marketplaces (10 sources, agent M); Firecrawl engine keeps rawHtml
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
50 changed files +10,431 −2
added
connectors/api/_luxury-lib/capture.ts
+59 −0
@@ -0,0 +1,59 @@ | ||
| 1 | +/** | |
| 2 | + * Live capture + smoke for the luxury-wave connectors. Runs a probe crawl through the real router, | |
| 3 | + * prints normalised samples and saves the first raw record(s) as fixtures. | |
| 4 | + * | |
| 5 | + * pnpm tsx connectors/api/_luxury-lib/capture.ts <connectorId> [limit=1] [--no-save] | |
| 6 | + */ | |
| 7 | +import { readFileSync, existsSync } from 'node:fs'; | |
| 8 | +import path from 'node:path'; | |
| 9 | +import { createCrawlContext, createRouter, ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 10 | +import { saveFixture } from '@rareindex/connectors/testing'; | |
| 11 | +import type { RareIndexConnector, ConnectorMeta } from '@rareindex/connectors'; | |
| 12 | + | |
| 13 | +function loadEnv() { | |
| 14 | + const p = path.resolve(process.cwd(), '.env'); | |
| 15 | + if (!existsSync(p)) return; | |
| 16 | + for (const line of readFileSync(p, 'utf8').split('\n')) { | |
| 17 | + const m = line.match(/^([A-Z0-9_]+)=(.*)$/); | |
| 18 | + if (m && !process.env[m[1]!]) process.env[m[1]!] = m[2]!.replace(/^"(.*)"$/, '$1'); | |
| 19 | + } | |
| 20 | +} | |
| 21 | + | |
| 22 | +async function main() { | |
| 23 | + loadEnv(); | |
| 24 | + const id = process.argv[2]; | |
| 25 | + if (!id) throw new Error('usage: capture.ts <connectorId> [limit] [--no-save]'); | |
| 26 | + const limit = Number(process.argv[3] ?? 1); | |
| 27 | + const save = !process.argv.includes('--no-save'); | |
| 28 | + const dirs = ['api', 'firecrawl', 'scrapfly']; | |
| 29 | + const dir = dirs.map((d) => path.resolve('connectors', d, id)).find((d) => existsSync(path.join(d, 'meta.json'))); | |
| 30 | + if (!dir) throw new Error(`no meta.json for ${id}`); | |
| 31 | + const metaRaw = JSON.parse(readFileSync(path.join(dir, 'meta.json'), 'utf8')); | |
| 32 | + const meta: ConnectorMeta = ConnectorMetaSchema.parse({ ...metaRaw, module: metaRaw.module ?? `${path.basename(path.dirname(dir))}/${id}` }); | |
| 33 | + const mod = (await import(path.join(dir, 'index.ts'))) as { default: (m: ConnectorMeta) => RareIndexConnector }; | |
| 34 | + const connector = mod.default(meta); | |
| 35 | + const router = createRouter({ firecrawlApiKey: process.env.FIRECRAWL_API_KEY, scrapflyApiKey: process.env.SCRAPFLY_API_KEY }); | |
| 36 | + const ctx = createCrawlContext({ router, meta, options: { mode: 'probe', limit } }); | |
| 37 | + let n = 0; | |
| 38 | + for await (const raw of connector.crawl(ctx)) { | |
| 39 | + n++; | |
| 40 | + const records = await connector.normalize({ ...raw, externalId: raw.externalId ?? null, fetchedAt: raw.fetchedAt ?? new Date() }); | |
| 41 | + const kinds = records.reduce<Record<string, number>>((acc, r) => ((acc[r.kind] = (acc[r.kind] ?? 0) + 1), acc), {}); | |
| 42 | + console.log(`\n[${id}] raw #${n} ${raw.url} engine=${raw.engine} → ${records.length} records ${JSON.stringify(kinds)}`); | |
| 43 | + for (const r of records.slice(0, 3)) { | |
| 44 | + const anyR = r as Record<string, unknown>; | |
| 45 | + console.log(' -', r.kind, '|', (anyR.rawTitle as string | undefined)?.slice(0, 90) ?? (anyR.title as string | undefined), '|', 'price' in r ? `${(r as { price: unknown }).price} ${(r as { currency?: string }).currency ?? ''}` : '', '|', 'saleDate' in r ? (r as { saleDate: Date }).saleDate.toISOString().slice(0, 10) : '', '|', 'attributes' in r ? JSON.stringify({ cat: r.attributes.categorySlug, brand: r.attributes.brand, model: r.attributes.model, ref: r.attributes.reference, ids: r.attributes.identifiers }).slice(0, 220) : ''); | |
| 46 | + } | |
| 47 | + if (save) { | |
| 48 | + const name = `${(raw.externalId ?? `raw-${n}`).replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').toLowerCase()}`; | |
| 49 | + saveFixture(id, name, { raw: { url: raw.url, externalId: raw.externalId ?? null, kind: raw.kind, engine: raw.engine, fetchedAt: raw.fetchedAt ?? new Date(), payload: raw.payload }, expect: { minCount: Math.min(records.length, 1), kinds: [...new Set(records.map((r) => r.kind))] }, note: 'Captured live by connectors/api/_luxury-lib/capture.ts' }); | |
| 50 | + console.log(` saved fixture ${name}`); | |
| 51 | + } | |
| 52 | + } | |
| 53 | + console.log(`\n[${id}] done: ${n} raw records; engine stats ${JSON.stringify(ctx.engineStats)}; anomalies ${JSON.stringify(ctx.anomalies)}`); | |
| 54 | +} | |
| 55 | + | |
| 56 | +main().catch((e) => { | |
| 57 | + console.error(e); | |
| 58 | + process.exit(1); | |
| 59 | +}); | |
added
connectors/api/_luxury-lib/index.ts
+208 −0
@@ -0,0 +1,208 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import type { CrawlContext } from '@rareindex/connectors'; | |
| 3 | +import { CurrencySchema, extractYear, type CurrencyCode } from '@rareindex/shared'; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Shared helpers for luxury / watch / sneaker marketplace connectors. Conventions match the | |
| 7 | + * existing chrono24 (watches: brand/model/reference) and novelship (sneakers: style_code) connectors | |
| 8 | + * so entity resolution merges records across sources. | |
| 9 | + */ | |
| 10 | + | |
| 11 | +// ---------- watches ---------- | |
| 12 | +export const WATCH_BRAND_CATEGORY: Record<string, string> = { | |
| 13 | + rolex: 'rolex', | |
| 14 | + 'patek philippe': 'patek_philippe', | |
| 15 | + patek: 'patek_philippe', | |
| 16 | + 'audemars piguet': 'audemars_piguet', | |
| 17 | + audemars: 'audemars_piguet', | |
| 18 | + omega: 'omega', | |
| 19 | +}; | |
| 20 | +export function watchCategory(brand: string | null | undefined): string { | |
| 21 | + const b = (brand ?? '').toLowerCase().trim(); | |
| 22 | + for (const [k, v] of Object.entries(WATCH_BRAND_CATEGORY)) if (b === k || b.startsWith(k)) return v; | |
| 23 | + return 'other_watches'; | |
| 24 | +} | |
| 25 | +export const WATCH_BRANDS = ['Rolex', 'Patek Philippe', 'Audemars Piguet', 'Omega', 'Cartier', 'Tudor', 'Breitling', 'IWC', 'Jaeger-LeCoultre', 'A. Lange & Söhne', 'Vacheron Constantin', 'Richard Mille', 'Grand Seiko', 'Seiko', 'TAG Heuer', 'Panerai', 'Hublot', 'Zenith', 'Breguet', 'Blancpain', 'F.P. Journe', 'MB&F', 'De Bethune', 'Greubel Forsey', 'Urwerk', 'Chopard', 'Piaget', 'Bulgari', 'Bvlgari', 'Franck Muller', 'Girard-Perregaux', 'Glashütte Original', 'H. Moser & Cie', 'Jaquet Droz', 'Laurent Ferrier', 'Parmigiani', 'Roger Dubuis', 'Ulysse Nardin', 'Czapek', 'Longines', 'Tiffany & Co.', 'Hermès', 'Chanel', 'Louis Vuitton', 'Bell & Ross', 'Nomos', 'Oris', 'Sinn']; | |
| 26 | + | |
| 27 | +/** Same heuristics as chrono24: 116500LN, 126610LV, 5711/1A-010, 15400ST.OO.1220ST.01, 311.30.42.30.01.005, RM 011 */ | |
| 28 | +export const WATCH_REF_RE = /\b(\d{4,6}[A-Z]{0,3}(?:\/\d[A-Z0-9]*)?(?:-\d{3})?|\d{5}[A-Z]{2}\.[A-Z]{2}\.\d{4}[A-Z]{2}\.\d{2}|\d{3}\.\d{2}\.\d{2}\.\d{2}\.\d{2}\.\d{3}|RM\s?\d{2,3}(?:-\d{2})?|W[A-Z0-9]{6,8}|[A-Z]{1,3}\d{3,5}[A-Z]{0,2}\.[A-Z0-9.]{3,})\b/; | |
| 29 | +export function watchReferenceFromText(text: string): string | null { | |
| 30 | + return [...text.matchAll(new RegExp(WATCH_REF_RE.source, 'g'))].map((m) => m[1]!.replace(/\s+/g, ' ')).find((r) => !/^(18|19|20)\d{2}$/.test(r) && !/^\d{4}$/.test(r)) ?? null; | |
| 31 | +} | |
| 32 | +export function watchMaterial(text: string): string | null { | |
| 33 | + const l = text.toLowerCase(); | |
| 34 | + if (/platinum/.test(l)) return 'platinum'; | |
| 35 | + if (/two[- ]tone|rolesor|steel (?:and|&) (?:yellow |rose |everose )?gold/.test(l)) return 'two-tone'; | |
| 36 | + if (/yellow gold|rose gold|everose|white gold|18k|18ct|gold/.test(l)) return 'gold'; | |
| 37 | + if (/titanium/.test(l)) return 'titanium'; | |
| 38 | + if (/ceramic/.test(l)) return 'ceramic'; | |
| 39 | + if (/steel|stainless|\bss\b/.test(l)) return 'steel'; | |
| 40 | + return null; | |
| 41 | +} | |
| 42 | +export function watchCompleteness(text: string): string | null { | |
| 43 | + const l = text.toLowerCase(); | |
| 44 | + if (/full set|box (?:and|&|\/|\+) papers|box\/papers|complete set|box and card|box & card/.test(l)) return 'full_set'; | |
| 45 | + if (/papers|warranty card|guarantee/.test(l)) return 'papers_only'; | |
| 46 | + if (/\bbox\b/.test(l)) return 'box_only'; | |
| 47 | + return null; | |
| 48 | +} | |
| 49 | +export function watchConditionRaw(text: string): string | null { | |
| 50 | + const l = text.toLowerCase(); | |
| 51 | + if (/unworn|brand new|\bnew\b|never worn/.test(l)) return 'Unworn'; | |
| 52 | + if (/like new|\bmint\b|excellent|pristine/.test(l)) return 'Excellent'; | |
| 53 | + if (/very good|good/.test(l)) return 'Good'; | |
| 54 | + if (/fair/.test(l)) return 'Fair'; | |
| 55 | + if (/pre-owned|used|preowned/.test(l)) return 'Pre-owned'; | |
| 56 | + return null; | |
| 57 | +} | |
| 58 | +export function caseSize(text: string): string | null { | |
| 59 | + const m = text.match(/\b(\d{2}(?:\.\d)?)\s?mm\b/i); | |
| 60 | + return m ? `${m[1]}mm` : null; | |
| 61 | +} | |
| 62 | + | |
| 63 | +// ---------- sneakers ---------- | |
| 64 | +export function sneakerCategory(brand: string | null | undefined, name = ''): string { | |
| 65 | + const b = `${brand ?? ''} ${name}`.toLowerCase(); | |
| 66 | + if (/jordan|nike/.test(b)) return 'nike_jordan'; | |
| 67 | + if (/adidas|yeezy/.test(b)) return 'adidas_yeezy'; | |
| 68 | + return 'new_balance_asics_other'; | |
| 69 | +} | |
| 70 | +/** Style codes: FV5029-003, DZ5485-612, IE7264, GX1332, M990GL6, BB550STA, 1201A019-100, CT8012-170 */ | |
| 71 | +export const STYLE_CODE_RE = /\b([A-Z]{1,2}\d{4,5}-\d{3}|\d{6}-\d{3}|[A-Z]{2}\d{4}|[A-Z]{1,3}\d{3,4}[A-Z]{1,4}\d{0,2}|\d{4}[A-Z]\d{3}-\d{3}|[A-Z]{2}\d{4}-\d{3})\b/; | |
| 72 | +export function styleCodeFromText(text: string): string | null { | |
| 73 | + const m = text.toUpperCase().match(STYLE_CODE_RE); | |
| 74 | + return m ? m[1]! : null; | |
| 75 | +} | |
| 76 | + | |
| 77 | +// ---------- handbags ---------- | |
| 78 | +const BAG_MODELS = ['Birkin', 'Kelly', 'Constance', 'Lindy', 'Evelyne', 'Picotin', 'Garden Party', 'Bolide', 'Classic Flap', 'Classic Double Flap', 'Double Flap', 'Single Flap', '2.55', 'Reissue', 'Boy', 'Coco Handle', '19 Flap', 'Deauville', 'Gabrielle', 'Speedy', 'Neverfull', 'Alma', 'Keepall', 'Pochette Métis', 'Pochette Metis', 'Capucines', 'Twist', 'OnTheGo', 'Onthego', 'Multi Pochette', 'Noé', 'Petite Malle', 'Lady Dior', 'Saddle', 'Book Tote', 'Caro', 'Diorama', 'Jackie', 'Dionysus', 'Marmont', 'Bamboo', 'Horsebit', 'Saint Louis', 'Goyardine', 'Artois', 'Anjou', 'Baguette', 'Peekaboo', 'Cassette', 'Jodie', 'Pouch', 'Puzzle', 'Hammock', 'Galleria', 'Re-Edition', 'Cleo', 'Luggage', 'Triomphe', 'Loulou', 'Kate', 'Sac de Jour', 'Le 5 à 7']; | |
| 79 | +export function bagModel(title: string): string | null { | |
| 80 | + const t = title.toLowerCase(); | |
| 81 | + for (const m of BAG_MODELS) if (t.includes(m.toLowerCase())) return m; | |
| 82 | + return null; | |
| 83 | +} | |
| 84 | +export function bagSize(title: string): string | null { | |
| 85 | + const m = title.match(/\b(\d{2})\b(?!\s?mm)/) ?? title.match(/\b(Mini|Small|Medium|Large|Jumbo|Maxi|PM|MM|GM|Nano|Micro)\b/i); | |
| 86 | + return m ? m[1]! : null; | |
| 87 | +} | |
| 88 | +export const BAG_MATERIALS = ['Togo', 'Epsom', 'Clemence', 'Clémence', 'Swift', 'Box Calf', 'Chevre', 'Chèvre', 'Ostrich', 'Crocodile', 'Alligator', 'Lizard', 'Caviar', 'Lambskin', 'Calfskin', 'Patent', 'Canvas', 'Monogram', 'Damier', 'Epi', 'Vernis', 'Empreinte', 'Suede', 'Nylon', 'Denim', 'Tweed', 'Python', 'Goatskin', 'Goyardine', 'Raffia']; | |
| 89 | +export function bagMaterial(title: string): string | null { | |
| 90 | + const t = title.toLowerCase(); | |
| 91 | + for (const m of BAG_MATERIALS) if (t.includes(m.toLowerCase())) return m; | |
| 92 | + return null; | |
| 93 | +} | |
| 94 | +export function bagHardware(title: string): string | null { | |
| 95 | + const m = title.match(/\b(Gold|Palladium|Silver|Rose Gold|Ruthenium|Brushed Gold|Light Gold|Aged Gold|Antique Gold|Gunmetal|Permabrass)\s+Hardware\b/i) ?? title.match(/\b(GHW|PHW|RGHW|SHW|BGHW)\b/); | |
| 96 | + return m ? m[1]! : null; | |
| 97 | +} | |
| 98 | +const LUXURY_BAG_BRANDS = ['hermès', 'hermes', 'chanel', 'louis vuitton', 'dior', 'christian dior', 'gucci', 'goyard', 'fendi', 'bottega veneta', 'prada', 'celine', 'céline', 'loewe', 'saint laurent', 'ysl', 'balenciaga', 'givenchy', 'valentino', 'burberry', 'miu miu', 'chloé', 'chloe', 'jacquemus', 'the row']; | |
| 99 | +export function isLuxuryBagBrand(brand: string | null | undefined): boolean { | |
| 100 | + return LUXURY_BAG_BRANDS.includes((brand ?? '').toLowerCase().trim()); | |
| 101 | +} | |
| 102 | + | |
| 103 | +// ---------- Shopify ---------- | |
| 104 | +export const ShopifyVariantSchema = z.object({ | |
| 105 | + id: z.number(), | |
| 106 | + title: z.string(), | |
| 107 | + price: z.string(), | |
| 108 | + compare_at_price: z.string().nullable().optional(), | |
| 109 | + available: z.boolean(), | |
| 110 | + sku: z.string().nullable().optional(), | |
| 111 | + option1: z.string().nullable().optional(), | |
| 112 | + updated_at: z.string().optional(), | |
| 113 | +}); | |
| 114 | +export const ShopifyProductSchema = z.object({ | |
| 115 | + id: z.number(), | |
| 116 | + title: z.string(), | |
| 117 | + handle: z.string(), | |
| 118 | + body_html: z.string().nullable().optional(), | |
| 119 | + published_at: z.string().nullable().optional(), | |
| 120 | + created_at: z.string().optional(), | |
| 121 | + updated_at: z.string().optional(), | |
| 122 | + vendor: z.string().nullable().optional(), | |
| 123 | + product_type: z.string().nullable().optional(), | |
| 124 | + tags: z.array(z.string()).optional(), | |
| 125 | + variants: z.array(ShopifyVariantSchema), | |
| 126 | + images: z.array(z.object({ src: z.string() })).optional(), | |
| 127 | + options: z.array(z.object({ name: z.string(), values: z.array(z.string()).optional() })).optional(), | |
| 128 | +}); | |
| 129 | +export type ShopifyProduct = z.infer<typeof ShopifyProductSchema>; | |
| 130 | + | |
| 131 | +/** Trim a Shopify product to what normalize() needs (payloads stay compact). */ | |
| 132 | +export function trimShopifyProduct(p: ShopifyProduct): ShopifyProduct { | |
| 133 | + return { | |
| 134 | + id: p.id, | |
| 135 | + title: p.title, | |
| 136 | + handle: p.handle, | |
| 137 | + body_html: p.body_html ? p.body_html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 1200) : null, | |
| 138 | + published_at: p.published_at ?? null, | |
| 139 | + updated_at: p.updated_at, | |
| 140 | + vendor: p.vendor ?? null, | |
| 141 | + product_type: p.product_type ?? null, | |
| 142 | + tags: (p.tags ?? []).filter((t) => !/^(updated-|amazon-|tiktok-)/.test(t)).slice(0, 40), | |
| 143 | + variants: p.variants.slice(0, 20).map((v) => ({ id: v.id, title: v.title, price: v.price, compare_at_price: v.compare_at_price ?? null, available: v.available, sku: v.sku ?? null, option1: v.option1 ?? null, updated_at: v.updated_at })), | |
| 144 | + images: (p.images ?? []).slice(0, 2).map((i) => ({ src: i.src })), | |
| 145 | + options: (p.options ?? []).map((o) => ({ name: o.name })), | |
| 146 | + }; | |
| 147 | +} | |
| 148 | + | |
| 149 | +/** Fetch one page of a public Shopify products.json feed (plain HTTPS, no auth). */ | |
| 150 | +export async function fetchShopifyPage(ctx: CrawlContext, base: string, path: string, page: number, limit = 250): Promise<{ products: ShopifyProduct[]; res: Awaited<ReturnType<CrawlContext['fetch']>> }> { | |
| 151 | + const url = `${base}${path}?limit=${limit}&page=${page}`; | |
| 152 | + const res = await ctx.fetch(url, { engines: ['api'], responseType: 'json', expect: ['title', 'price'], parse: (r) => { | |
| 153 | + const j = r.json as { products?: Array<{ title?: string; variants?: Array<{ price?: string }> }> } | null; | |
| 154 | + const f = j?.products?.[0]; | |
| 155 | + return f ? { title: f.title, price: f.variants?.[0]?.price } : { title: 'empty-page', price: 0 }; | |
| 156 | + } }); | |
| 157 | + const parsed = z.object({ products: z.array(ShopifyProductSchema) }).safeParse(res.json); | |
| 158 | + return { products: parsed.success ? parsed.data.products : [], res }; | |
| 159 | +} | |
| 160 | + | |
| 161 | +export function currencyOr(code: string | null | undefined, fallback: CurrencyCode): CurrencyCode { | |
| 162 | + const c = CurrencySchema.safeParse((code ?? '').toUpperCase()); | |
| 163 | + return c.success ? c.data : fallback; | |
| 164 | +} | |
| 165 | + | |
| 166 | +export function yearFrom(text: string | null | undefined): number | null { | |
| 167 | + return text ? extractYear(text) : null; | |
| 168 | +} | |
| 169 | + | |
| 170 | +export function moneyNumber(s: string | number | null | undefined): number | null { | |
| 171 | + if (s === null || s === undefined) return null; | |
| 172 | + const n = typeof s === 'number' ? s : Number(String(s).replace(/[^0-9.]/g, '')); | |
| 173 | + return Number.isFinite(n) && n > 0 ? n : null; | |
| 174 | +} | |
| 175 | + | |
| 176 | +// ---------- Firecrawl rawHtml (scripts kept) ---------- | |
| 177 | +import type { ExtractionResult } from '@rareindex/shared'; | |
| 178 | +/** | |
| 179 | + * The shared Firecrawl engine requests the `html` format, which strips <script> blocks — pages whose | |
| 180 | + * data lives in __NEXT_DATA__ need `rawHtml`. This helper calls Firecrawl directly for rawHtml and | |
| 181 | + * books the attempt into ctx.engineStats so cost/health accounting stays correct. Falls back to | |
| 182 | + * ctx.fetch when no key is configured. | |
| 183 | + */ | |
| 184 | +export async function fetchRawHtml(ctx: CrawlContext, url: string, opts: { waitForMs?: number; timeoutMs?: number } = {}): Promise<ExtractionResult> { | |
| 185 | + const key = process.env.FIRECRAWL_API_KEY; | |
| 186 | + if (!key) return ctx.fetch(url, { minQuality: 0.2 }); | |
| 187 | + const started = Date.now(); | |
| 188 | + const stats = (ctx.engineStats.firecrawl ??= { attempts: 0, success: 0, credits: 0, ms: 0 }); | |
| 189 | + stats.attempts++; | |
| 190 | + try { | |
| 191 | + const res = await fetch('https://api.firecrawl.dev/v2/scrape', { | |
| 192 | + method: 'POST', | |
| 193 | + headers: { authorization: `Bearer ${key}`, 'content-type': 'application/json' }, | |
| 194 | + body: JSON.stringify({ url, formats: ['rawHtml'], onlyMainContent: false, timeout: opts.timeoutMs ?? 60_000, ...(opts.waitForMs ? { waitFor: opts.waitForMs } : {}) }), | |
| 195 | + }); | |
| 196 | + const text = await res.text(); | |
| 197 | + const json = JSON.parse(text) as { success?: boolean; data?: { rawHtml?: string; metadata?: { statusCode?: number; url?: string } }; error?: string; creditsUsed?: number }; | |
| 198 | + const status = json.data?.metadata?.statusCode ?? null; | |
| 199 | + const ok = Boolean(res.ok && json.success && json.data?.rawHtml && (status === null || status < 400)); | |
| 200 | + stats.credits += json.creditsUsed ?? 1; | |
| 201 | + stats.ms += Date.now() - started; | |
| 202 | + if (ok) stats.success++; | |
| 203 | + return { success: ok, engine: 'firecrawl', url, finalUrl: json.data?.metadata?.url ?? null, httpStatus: status, html: json.data?.rawHtml ?? null, markdown: null, json: null, qualityScore: ok ? 0.8 : 0, requiresReview: false, error: ok ? null : (json.error ?? `status ${status ?? res.status}`), costCredits: json.creditsUsed ?? 1, durationMs: Date.now() - started, fetchedAt: new Date() }; | |
| 204 | + } catch (err) { | |
| 205 | + stats.ms += Date.now() - started; | |
| 206 | + return { success: false, engine: 'firecrawl', url, finalUrl: null, httpStatus: null, html: null, markdown: null, json: null, qualityScore: 0, requiresReview: false, error: err instanceof Error ? err.message : String(err), costCredits: 0, durationMs: Date.now() - started, fetchedAt: new Date() }; | |
| 207 | + } | |
| 208 | +} | |
added
connectors/api/antiquorum/index.test.ts
+37 −0
@@ -0,0 +1,37 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('antiquorum', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into sale records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('antiquorum'); | |
| 15 | + const fx = loadFixture('antiquorum', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const sale = out.find((r) => r.kind === 'sale'); | |
| 19 | + if (sale?.kind !== 'sale') throw new Error('no sale'); | |
| 20 | + expect(sale.saleType).toBe('auction'); | |
| 21 | + expect(sale.buyerPremiumIncluded).toBe(true); | |
| 22 | + expect(['CHF', 'HKD', 'EUR', 'USD']).toContain(sale.currency); | |
| 23 | + expect(sale.auctionHouse).toBe('Antiquorum'); | |
| 24 | + expect(sale.lotNumber).toMatch(/^\d+/); | |
| 25 | + expect(sale.attributes.brand).not.toMatch(/switzerland/i); | |
| 26 | + expect(sale.saleDate.getUTCFullYear()).toBeGreaterThanOrEqual(2022); | |
| 27 | + const lot = out.find((r) => r.kind === 'auction_lot'); | |
| 28 | + if (lot && lot.kind === 'auction_lot') expect(lot.auctionHouse).toBe('Antiquorum'); | |
| 29 | + }); | |
| 30 | + it('parses the catalogue home into auctions with ids and dates', async () => { | |
| 31 | + const { parseAuctionIndex } = await import('./index.js'); | |
| 32 | + const html = '<div><h3>Important Modern & Vintage Timepieces</h3><p>Hong Kong May 31, 2026</p><a href="/en/auctions/Hong_Kong_May_31_2026/lots">Lots</a> <a href="/en/auctions/387/price-list">Price list</a></div><div><h3>Only Online Auction</h3><p>Sep 18, 2025</p><a href="/en/auctions/hong_kong_september_18_2025/lots">Lots</a><a href="/en/auctions/378/price-list">x</a></div>'; | |
| 33 | + const a = parseAuctionIndex(html); | |
| 34 | + expect(a[0]).toMatchObject({ slug: 'Hong_Kong_May_31_2026', id: '387', date: 'May 31, 2026', location: 'Hong Kong' }); | |
| 35 | + expect(a[1]).toMatchObject({ slug: 'hong_kong_september_18_2025', id: '378' }); | |
| 36 | + }); | |
| 37 | +}); | |
added
connectors/api/antiquorum/index.ts
+219 −0
@@ -0,0 +1,219 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { AssetAttributesSchema, NormalizedAuctionLotSchema, NormalizedSaleSchema, parseSourceDate, type NormalizedRecord } from '@rareindex/shared'; | |
| 4 | +import { caseSize, currencyOr, moneyNumber, watchCategory, watchMaterial, watchReferenceFromText } from '../_luxury-lib/index.js'; | |
| 5 | + | |
| 6 | +/** Antiquorum — catalogue lots pages (RDFa Products + 'Sold: CCY amount', premium-inclusive). */ | |
| 7 | +const BASE = 'https://catalog.antiquorum.swiss'; | |
| 8 | +const PARSER_VERSION = '1.0.0'; | |
| 9 | + | |
| 10 | +export const AuctionSchema = z.object({ slug: z.string(), id: z.string().nullable(), title: z.string().nullable(), date: z.string().nullable(), location: z.string().nullable() }); | |
| 11 | +export const LotSchema = z.object({ | |
| 12 | + lotNumber: z.string(), | |
| 13 | + name: z.string(), | |
| 14 | + url: z.string(), | |
| 15 | + sku: z.string().nullable(), | |
| 16 | + image: z.string().nullable(), | |
| 17 | + brand: z.string().nullable(), | |
| 18 | + model: z.string().nullable(), | |
| 19 | + reference: z.string().nullable(), | |
| 20 | + year: z.string().nullable(), | |
| 21 | + material: z.string().nullable(), | |
| 22 | + diameter: z.string().nullable(), | |
| 23 | + description: z.string().nullable(), | |
| 24 | + estimateLow: z.number().nullable(), | |
| 25 | + estimateHigh: z.number().nullable(), | |
| 26 | + estimateCurrency: z.string().nullable(), | |
| 27 | + soldPrice: z.number().nullable(), | |
| 28 | + soldCurrency: z.string().nullable(), | |
| 29 | + accessories: z.string().nullable(), | |
| 30 | +}); | |
| 31 | +export const PagePayloadSchema = z.object({ kind: z.literal('lots_page'), url: z.string(), auction: AuctionSchema, page: z.number(), lots: z.array(LotSchema) }); | |
| 32 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 33 | + | |
| 34 | +const unesc = (s: string) => s.replace(/&/g, '&').replace(/'/g, "'").replace(/"/g, '"').replace(/\s+/g, ' ').trim(); | |
| 35 | + | |
| 36 | +/** Auctions listed on the catalogue home: slug (+ numeric id, date, title) per block. */ | |
| 37 | +export function parseAuctionIndex(htmlText: string): z.infer<typeof AuctionSchema>[] { | |
| 38 | + const out: z.infer<typeof AuctionSchema>[] = []; | |
| 39 | + const seen = new Set<string>(); | |
| 40 | + const anchors = [...htmlText.matchAll(/href="\/en\/auctions\/([A-Za-z0-9_]+)\/lots"/g)]; | |
| 41 | + for (let i = 0; i < anchors.length; i++) { | |
| 42 | + const slug = anchors[i]![1]!; | |
| 43 | + if (seen.has(slug)) continue; | |
| 44 | + seen.add(slug); | |
| 45 | + const start = anchors[i]!.index!; | |
| 46 | + const prevEnd = i > 0 ? anchors[i - 1]!.index! + anchors[i - 1]![0].length : 0; | |
| 47 | + const end = i + 1 < anchors.length ? anchors[i + 1]!.index! : Math.min(htmlText.length, start + 4000); | |
| 48 | + // title + date precede the lots link inside an auction card; the price-list link follows it | |
| 49 | + const before = htmlText.slice(Math.max(prevEnd, start - 1500), start); | |
| 50 | + const after = htmlText.slice(start, end); | |
| 51 | + const dateRe = /([A-Z][a-z]{2,8} \d{1,2}(?:-\d{1,2})?,? 20\d\d)/g; | |
| 52 | + const id = after.match(/\/en\/auctions\/(\d+)\/price-list/)?.[1] ?? before.match(/\/en\/auctions\/(\d+)\/price-list/)?.[1] ?? null; | |
| 53 | + const beforeDates = [...before.matchAll(dateRe)].map((m) => m[1]!); | |
| 54 | + const date = beforeDates[beforeDates.length - 1] ?? after.match(dateRe)?.[0] ?? null; | |
| 55 | + const titles = [...before.matchAll(/<h[1-5][^>]*>\s*([^<]{4,90}?)\s*<\/h[1-5]>/g)].map((m) => m[1]!); | |
| 56 | + const title = titles[titles.length - 1] ?? null; | |
| 57 | + const loc = slug.match(/hong_kong|geneva|monaco|new_york|dubai/i)?.[0]?.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) ?? null; | |
| 58 | + out.push({ slug, id, title: title ? unesc(title) : null, date, location: loc }); | |
| 59 | + } | |
| 60 | + return out; | |
| 61 | +} | |
| 62 | + | |
| 63 | +export function parseLotsPage(htmlText: string, url: string, auction: z.infer<typeof AuctionSchema>, page: number): PagePayload { | |
| 64 | + const lots: z.infer<typeof LotSchema>[] = []; | |
| 65 | + const blocks = htmlText.split(/<h4>\s*LOT\s+/).slice(1); | |
| 66 | + for (const b of blocks) { | |
| 67 | + const lotNumber = b.match(/^(\d+[A-Z]?)/)?.[1]; | |
| 68 | + if (!lotNumber) continue; | |
| 69 | + const name = b.match(/property="schema:name" content="([^"]*)"/)?.[1]; | |
| 70 | + const urlRel = b.match(/rel="schema:url" resource="([^"\s]+)/)?.[1] ?? b.match(/href="(\/en\/lots\/[^"]+)"/)?.[1]; | |
| 71 | + if (!name || !urlRel) continue; | |
| 72 | + const spec = (label: string) => b.match(new RegExp(`<strong>${label}</strong> ([^<]{1,120})`))?.[1]?.trim() ?? null; | |
| 73 | + const est = b.match(/N_lots_estimation'\s*>\s*([A-Z]{3})\s*([\d,]+)\s*-\s*([\d,]+)/); | |
| 74 | + const sold = b.match(/Sold:\s*([A-Z]{3})\s*([\d,]+)/); | |
| 75 | + lots.push({ | |
| 76 | + lotNumber, | |
| 77 | + name: unesc(name), | |
| 78 | + url: urlRel.startsWith('http') ? urlRel.trim() : `${BASE}${urlRel}`, | |
| 79 | + sku: b.match(/property="schema:sku" content="([^"]*)"/)?.[1] ?? null, | |
| 80 | + image: b.match(/rel="schema:image" resource="([^"]+)"/)?.[1] ?? null, | |
| 81 | + brand: spec('Brand'), | |
| 82 | + model: spec('Model'), | |
| 83 | + reference: spec('Reference'), | |
| 84 | + year: spec('Year'), | |
| 85 | + material: spec('Material'), | |
| 86 | + diameter: spec('Diameter'), | |
| 87 | + description: b.match(/property="schema:description" content="([^"]*)"/)?.[1]?.slice(0, 500) ?? null, | |
| 88 | + estimateLow: est ? moneyNumber(est[2]) : null, | |
| 89 | + estimateHigh: est ? moneyNumber(est[3]) : null, | |
| 90 | + estimateCurrency: est ? est[1]! : null, | |
| 91 | + soldPrice: sold ? moneyNumber(sold[2]) : null, | |
| 92 | + soldCurrency: sold ? sold[1]! : null, | |
| 93 | + accessories: spec('Accessories')?.slice(0, 200) ?? null, | |
| 94 | + }); | |
| 95 | + } | |
| 96 | + return { kind: 'lots_page', url, auction, page, lots }; | |
| 97 | +} | |
| 98 | + | |
| 99 | +export class AntiquorumConnector extends BaseConnector { | |
| 100 | + readonly version = '1.0.0'; | |
| 101 | + readonly parserVersion = PARSER_VERSION; | |
| 102 | + protected override minIntervalMs = 2000; | |
| 103 | + override readonly urlPatterns = [/^https?:\/\/catalog\.antiquorum\.swiss\/en\/lots\/[a-z0-9-]+-lot-(\d+)-(\d+)/i]; | |
| 104 | + | |
| 105 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 106 | + const perRun = Number(this.meta.config.auctionsPerRun ?? 3); | |
| 107 | + const maxPages = Number(this.meta.config.maxPagesPerAuction ?? 25); | |
| 108 | + const done = new Set<string>(((ctx.options.cursor?.doneSlugs as string[] | undefined) ?? [])); | |
| 109 | + await this.throttle(); | |
| 110 | + const home = await ctx.fetch(`${BASE}/en`, { responseType: 'text', minQuality: 0.2 }); | |
| 111 | + if (!home.success || !home.html) { | |
| 112 | + ctx.anomaly('page_fetch_failed', `home: ${home.error ?? home.httpStatus}`); | |
| 113 | + return; | |
| 114 | + } | |
| 115 | + const auctions = parseAuctionIndex(home.html); | |
| 116 | + const now = Date.now(); | |
| 117 | + // upcoming/current auctions first (they change), then past auctions not yet crawled (backfill). | |
| 118 | + const withDate = auctions.map((a) => ({ a, t: a.date ? (parseSourceDate(a.date)?.getTime() ?? 0) : 0 })); | |
| 119 | + const upcoming = withDate.filter((x) => x.t >= now - 3 * 86_400_000).map((x) => x.a); | |
| 120 | + const past = withDate.filter((x) => x.t < now - 3 * 86_400_000 && !done.has(x.a.slug)).sort((x, y) => y.t - x.t).map((x) => x.a); | |
| 121 | + const selected = ctx.options.seeds?.length ? auctions.filter((a) => ctx.options.seeds!.includes(a.slug)) : [...upcoming, ...past].slice(0, ctx.options.mode === 'backfill' ? perRun * 4 : perRun); | |
| 122 | + let count = 0; | |
| 123 | + for (const auction of selected) { | |
| 124 | + for (let page = 1; page <= maxPages; page++) { | |
| 125 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 126 | + const url = `${BASE}/en/auctions/${auction.slug}/lots${page > 1 ? `?page=${page}` : ''}`; | |
| 127 | + await this.throttle(); | |
| 128 | + const res = await ctx.fetch(url, { responseType: 'text', expect: ['title', 'price', 'date'], parse: (r) => { | |
| 129 | + const p = r.html ? parseLotsPage(r.html, url, auction, page) : null; | |
| 130 | + const first = p?.lots[0]; | |
| 131 | + return first ? { title: first.name, price: first.soldPrice ?? first.estimateLow, date: auction.date } : null; | |
| 132 | + } }); | |
| 133 | + const payload = res.success && res.html ? parseLotsPage(res.html, url, auction, page) : null; | |
| 134 | + if (!payload || payload.lots.length === 0) { | |
| 135 | + if (page === 1) ctx.anomaly(payload ? 'empty_page' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 136 | + break; | |
| 137 | + } | |
| 138 | + count++; | |
| 139 | + yield { url, externalId: `${auction.slug}:${page}`, kind: payload.lots.some((l) => l.soldPrice) ? 'sale' : 'auction_lot', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 140 | + if (!(res.html ?? '').includes(`lots?page=${page + 1}`)) break; | |
| 141 | + } | |
| 142 | + const isPast = auction.date ? (parseSourceDate(auction.date)?.getTime() ?? 0) < now - 3 * 86_400_000 : false; | |
| 143 | + if (isPast) { | |
| 144 | + done.add(auction.slug); | |
| 145 | + await ctx.setCursor({ doneSlugs: [...done].slice(-200) }); | |
| 146 | + } | |
| 147 | + } | |
| 148 | + } | |
| 149 | + | |
| 150 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 151 | + const m = url.match(this.urlPatterns[0]!); | |
| 152 | + if (!m) return []; | |
| 153 | + await this.throttle(); | |
| 154 | + const home = await ctx.fetch(`${BASE}/en`, { responseType: 'text', minQuality: 0.2 }); | |
| 155 | + const auction = home.html ? parseAuctionIndex(home.html).find((a) => a.id === m[1]) : undefined; | |
| 156 | + if (!auction) return []; | |
| 157 | + // lots pages hold 20 lots each; lot N sits on page ceil(N/20) | |
| 158 | + const page = Math.max(1, Math.ceil(Number(m[2]) / 20)); | |
| 159 | + const pageUrl = `${BASE}/en/auctions/${auction.slug}/lots?page=${page}`; | |
| 160 | + await this.throttle(); | |
| 161 | + const res = await ctx.fetch(pageUrl, { responseType: 'text', minQuality: 0.2 }); | |
| 162 | + if (!res.success || !res.html) return []; | |
| 163 | + const payload = parseLotsPage(res.html, pageUrl, auction, page); | |
| 164 | + payload.lots = payload.lots.filter((l) => l.url.split('?')[0] === url.split('?')[0]); | |
| 165 | + return payload.lots.length ? [{ url: pageUrl, externalId: `${auction.slug}:${page}`, kind: 'sale', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }] : []; | |
| 166 | + } | |
| 167 | + | |
| 168 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 169 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 170 | + const saleDate = p.auction.date ? parseSourceDate(p.auction.date.replace(/(\d{1,2})-\d{1,2},/, '$1,')) : null; | |
| 171 | + const out: NormalizedRecord[] = []; | |
| 172 | + for (const lot of p.lots) { | |
| 173 | + const brandRaw = lot.brand ?? lot.name.split(',')[0] ?? null; | |
| 174 | + const brand = brandRaw ? unesc(brandRaw).replace(/,\s*(switzerland|germany|france|japan|usa|england|u\.?s\.?a\.?)\s*$/i, '').replace(/\b([A-Z])([A-Z]+)\b/g, (_m, a: string, b: string) => a + b.toLowerCase()).trim() : null; | |
| 175 | + const isJewelry = /jewel|necklace|bracelet|ring\b|earring|brooch|diamond/i.test(lot.name) && !/watch|wristwatch|chronograph/i.test(lot.name + (lot.description ?? '')); | |
| 176 | + const categorySlug = isJewelry ? 'jewelry' : watchCategory(brand); | |
| 177 | + const reference = lot.reference ?? watchReferenceFromText(lot.name); | |
| 178 | + const yearMatch = lot.year?.match(/(19|20)\d{2}/)?.[0]; | |
| 179 | + const attributes = AssetAttributesSchema.parse({ | |
| 180 | + categorySlug, | |
| 181 | + brand, | |
| 182 | + name: `${brand ?? ''} ${lot.model ?? ''}`.trim() || lot.name, | |
| 183 | + model: lot.model, | |
| 184 | + reference, | |
| 185 | + year: yearMatch ? Number(yearMatch) : null, | |
| 186 | + material: lot.material ? (watchMaterial(lot.material) ?? lot.material.toLowerCase()) : watchMaterial(lot.name), | |
| 187 | + size: lot.diameter ? caseSize(lot.diameter) : null, | |
| 188 | + identifiers: { ...(reference ? { reference } : {}), antiquorum_lot: lot.sku ?? `${p.auction.id ?? p.auction.slug}-${lot.lotNumber}` }, | |
| 189 | + metadata: { auction: p.auction.title, auction_slug: p.auction.slug, location: p.auction.location, estimate: lot.estimateLow ? { low: lot.estimateLow, high: lot.estimateHigh, currency: lot.estimateCurrency } : null, accessories: lot.accessories }, | |
| 190 | + }); | |
| 191 | + const accessories = (lot.accessories ?? '').toLowerCase(); | |
| 192 | + const completeness = accessories ? (/box/.test(accessories) && /(certificate|papers|warranty|guarantee)/.test(accessories) ? 'full_set' : /(certificate|papers|warranty|guarantee)/.test(accessories) ? 'papers_only' : /box/.test(accessories) ? 'box_only' : null) : null; | |
| 193 | + const base = { | |
| 194 | + connectorId: this.meta.id, | |
| 195 | + sourceId: this.meta.sourceId, | |
| 196 | + sourceUrl: lot.url, | |
| 197 | + externalId: lot.sku ?? `${p.auction.slug}-${lot.lotNumber}`, | |
| 198 | + rawTitle: lot.name, | |
| 199 | + description: lot.description, | |
| 200 | + imageUrls: lot.image ? [lot.image] : [], | |
| 201 | + attributes, | |
| 202 | + condition: { condition: null, conditionRaw: null, completeness }, | |
| 203 | + observedAt: raw.fetchedAt, | |
| 204 | + parserVersion: PARSER_VERSION, | |
| 205 | + }; | |
| 206 | + if (lot.soldPrice && lot.soldCurrency && saleDate) { | |
| 207 | + out.push(NormalizedSaleSchema.parse({ kind: 'sale', ...base, confidence: 0.92, saleType: 'auction', saleDate, price: lot.soldPrice, currency: currencyOr(lot.soldCurrency, 'CHF'), buyerPremiumIncluded: true, auctionHouse: 'Antiquorum', lotNumber: lot.lotNumber, location: p.auction.location })); | |
| 208 | + } else if (!lot.soldPrice) { | |
| 209 | + const isPast = saleDate ? saleDate.getTime() < Date.now() - 86_400_000 : false; | |
| 210 | + out.push(NormalizedAuctionLotSchema.parse({ kind: 'auction_lot', ...base, confidence: 0.85, auctionHouse: 'Antiquorum', auctionName: p.auction.title, lotNumber: lot.lotNumber, endsAt: saleDate, estimateLow: lot.estimateLow, estimateHigh: lot.estimateHigh, currency: lot.estimateCurrency ? currencyOr(lot.estimateCurrency, 'CHF') : null, status: isPast ? 'ended' : 'upcoming', location: p.auction.location })); | |
| 211 | + } | |
| 212 | + } | |
| 213 | + return out; | |
| 214 | + } | |
| 215 | +} | |
| 216 | + | |
| 217 | +export default function createConnector(meta: ConnectorMeta) { | |
| 218 | + return new AntiquorumConnector(meta); | |
| 219 | +} | |
added
connectors/api/antiquorum/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "antiquorum", | |
| 3 | + "displayName": "Antiquorum (watch auction results & upcoming lots)", | |
| 4 | + "sourceId": "antiquorum", | |
| 5 | + "sourceName": "Antiquorum", | |
| 6 | + "sourceType": "auction_house", | |
| 7 | + "sourceUrl": "https://catalog.antiquorum.swiss", | |
| 8 | + "module": "api/antiquorum", | |
| 9 | + "enginePriority": ["api", "firecrawl"], | |
| 10 | + "categories": ["watches", "rolex", "patek_philippe", "audemars_piguet", "omega", "other_watches", "jewelry"], | |
| 11 | + "regions": ["CH", "HK", "MC"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["CHF", "HKD", "EUR", "USD"], | |
| 14 | + "supportsListings": false, | |
| 15 | + "supportsSold": true, | |
| 16 | + "supportsAuctions": true, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": false, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 1440, | |
| 22 | + "priority": "high", | |
| 23 | + "trustScore": 0.9, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.antiquorum.swiss/conditions-of-sale/", | |
| 26 | + "accessNotes": "Public auction catalogue (catalog.antiquorum.swiss/en → /en/auctions/<slug>/lots?page=N) fetched over plain HTTPS with the RareIndex user agent; robots.txt only disallows /wp-admin/. Each lot carries schema.org RDFa (name, sku, brand, price, currency, image) and a spec table (Brand, Model, Reference, Year, Material). Sold lots show 'Sold: <CCY> <amount>' which Antiquorum's own price lists confirm is hammer + buyer's premium (e.g. lot 387-1: hammer 230,000 → sold 287,500 HKD), so buyerPremiumIncluded=true. Sale date = the auction date on the catalogue home. Unsold/upcoming lots become auction lots with estimates. 2 s between pages.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "auctionsPerRun": 3, | |
| 31 | + "maxPagesPerAuction": 25 | |
| 32 | + } | |
| 33 | +} | |
added
connectors/api/bobs-watches/index.test.ts
+28 −0
@@ -0,0 +1,28 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('bobs-watches', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into listing records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('bobs-watches'); | |
| 15 | + const fx = loadFixture('bobs-watches', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const l = out[0]; | |
| 19 | + if (l?.kind !== 'listing') throw new Error('no listing'); | |
| 20 | + expect(l.attributes.categorySlug).toBe('rolex'); | |
| 21 | + expect(l.attributes.brand).toBe('Rolex'); | |
| 22 | + expect(l.attributes.reference).toMatch(/^\d{4,6}/); | |
| 23 | + expect(l.attributes.identifiers.reference).toBe(l.attributes.reference); | |
| 24 | + expect(l.currency).toBe('USD'); | |
| 25 | + expect(l.sourceUrl).toMatch(/bobswatches\.com\/.+\.html$/); | |
| 26 | + }); | |
| 27 | + | |
| 28 | +}); | |
added
connectors/api/bobs-watches/index.ts
+128 −0
@@ -0,0 +1,128 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { normalizeCondition } from '@rareindex/taxonomy'; | |
| 4 | +import { AssetAttributesSchema, NormalizedListingSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 5 | +import { caseSize, currencyOr, moneyNumber, watchCategory, watchCompleteness, watchConditionRaw, watchMaterial, watchReferenceFromText, yearFrom } from '../_luxury-lib/index.js'; | |
| 6 | + | |
| 7 | +/** Bob's Watches — schema.org Product JSON-LD on public model pages (pre-owned Rolex, USD). */ | |
| 8 | +const BASE = 'https://www.bobswatches.com'; | |
| 9 | +const PARSER_VERSION = '1.0.0'; | |
| 10 | + | |
| 11 | +export const ItemSchema = z.object({ name: z.string(), mpn: z.string().nullable(), sku: z.string().nullable(), url: z.string(), color: z.string().nullable(), price: z.number(), currency: z.string(), availability: z.string().nullable(), image: z.string().nullable(), condition: z.string().nullable() }); | |
| 12 | +export const PagePayloadSchema = z.object({ kind: z.literal('model_page'), url: z.string(), seed: z.string(), items: z.array(ItemSchema) }); | |
| 13 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 14 | + | |
| 15 | +export function parseModelPage(htmlText: string, url: string, seed: string): PagePayload { | |
| 16 | + const items: z.infer<typeof ItemSchema>[] = []; | |
| 17 | + for (const block of H.jsonLd(htmlText, 'Product')) { | |
| 18 | + const offers = (block.offers as Record<string, unknown> | undefined) ?? {}; | |
| 19 | + const price = moneyNumber(offers.price as string | number | undefined); | |
| 20 | + if (!price) continue; | |
| 21 | + const img = block.image; | |
| 22 | + items.push({ | |
| 23 | + name: String(block.name ?? '').replace(/\s+/g, ' ').trim(), | |
| 24 | + mpn: block.mpn ? String(block.mpn) : null, | |
| 25 | + sku: block.sku ? String(block.sku) : null, | |
| 26 | + url: String(block.url ?? ''), | |
| 27 | + color: block.color ? String(block.color) : null, | |
| 28 | + price, | |
| 29 | + currency: String(offers.priceCurrency ?? 'USD'), | |
| 30 | + availability: offers.availability ? String(offers.availability).replace(/^.*\//, '') : null, | |
| 31 | + image: Array.isArray(img) ? String(img[0] ?? '') || null : typeof img === 'string' ? img : null, | |
| 32 | + condition: block.itemCondition ? String(block.itemCondition).replace(/^.*\//, '') : null, | |
| 33 | + }); | |
| 34 | + } | |
| 35 | + return { kind: 'model_page', url, seed, items }; | |
| 36 | +} | |
| 37 | + | |
| 38 | +export class BobsWatchesConnector extends BaseConnector { | |
| 39 | + readonly version = '1.0.0'; | |
| 40 | + readonly parserVersion = PARSER_VERSION; | |
| 41 | + protected override minIntervalMs = 1500; | |
| 42 | + override readonly urlPatterns = [/^https?:\/\/(www\.)?bobswatches\.com\/[a-z0-9-]+\.html$/i]; | |
| 43 | + | |
| 44 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 45 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 46 | + let count = 0; | |
| 47 | + for (const seed of seeds) { | |
| 48 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 49 | + const url = `${BASE}/${seed}`; | |
| 50 | + await this.throttle(); | |
| 51 | + const res = await ctx.fetch(url, { responseType: 'text', expect: ['title', 'price', 'identifiers'], parse: (r) => { | |
| 52 | + const first = r.html ? parseModelPage(r.html, url, seed).items[0] : undefined; | |
| 53 | + return first ? { title: first.name, price: first.price, identifiers: first.mpn ? { mpn: first.mpn } : null } : null; | |
| 54 | + } }); | |
| 55 | + const payload = res.success && res.html ? parseModelPage(res.html, url, seed) : null; | |
| 56 | + if (!payload || payload.items.length === 0) { | |
| 57 | + ctx.anomaly(payload ? 'empty_page' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 58 | + continue; | |
| 59 | + } | |
| 60 | + count++; | |
| 61 | + yield { url, externalId: `model:${seed}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 62 | + } | |
| 63 | + } | |
| 64 | + | |
| 65 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 66 | + await this.throttle(); | |
| 67 | + const res = await ctx.fetch(url, { responseType: 'text', minQuality: 0.2 }); | |
| 68 | + if (!res.success || !res.html) return []; | |
| 69 | + const payload = parseModelPage(res.html, url, 'lookup'); | |
| 70 | + return payload.items.length ? [{ url, externalId: `product:${payload.items[0]!.sku ?? url}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }] : []; | |
| 71 | + } | |
| 72 | + | |
| 73 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 74 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 75 | + const out: NormalizedRecord[] = []; | |
| 76 | + const seen = new Set<string>(); | |
| 77 | + for (const it of p.items) { | |
| 78 | + const id = it.sku ?? it.url; | |
| 79 | + if (seen.has(id)) continue; | |
| 80 | + seen.add(id); | |
| 81 | + const brandMatch = it.name.match(/\b(Rolex|Omega|Patek Philippe|Audemars Piguet|Cartier|Tudor|Breitling|Panerai|IWC)\b/i); | |
| 82 | + const brand = brandMatch ? brandMatch[1]! : 'Rolex'; | |
| 83 | + const categorySlug = watchCategory(brand); | |
| 84 | + const ref = it.mpn ?? watchReferenceFromText(it.name); | |
| 85 | + const model = it.name.match(/Rolex\s+([A-Z][A-Za-z -]+?)(?:\s+Ref\b|\s+\d|\s+Black|\s+Blue|\s+White|\s+Green|$)/)?.[1]?.trim() ?? p.seed.replace(/^rolex-/, '').replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); | |
| 86 | + const conditionRaw = watchConditionRaw(it.name) ?? (it.condition === 'UsedCondition' ? 'Pre-owned' : it.condition === 'NewCondition' ? 'Unworn' : null); | |
| 87 | + const attributes = AssetAttributesSchema.parse({ | |
| 88 | + categorySlug, | |
| 89 | + brand, | |
| 90 | + name: `${brand} ${model}`.trim(), | |
| 91 | + model, | |
| 92 | + reference: ref, | |
| 93 | + year: yearFrom(it.name), | |
| 94 | + material: it.color ? (watchMaterial(it.color) ?? it.color.toLowerCase()) : watchMaterial(it.name), | |
| 95 | + size: caseSize(it.name), | |
| 96 | + identifiers: { ...(ref ? { reference: ref } : {}), bobs_sku: id }, | |
| 97 | + metadata: { model_page: p.url, color: it.color }, | |
| 98 | + }); | |
| 99 | + out.push( | |
| 100 | + NormalizedListingSchema.parse({ | |
| 101 | + kind: 'listing', | |
| 102 | + connectorId: this.meta.id, | |
| 103 | + sourceId: this.meta.sourceId, | |
| 104 | + sourceUrl: it.url, | |
| 105 | + externalId: id, | |
| 106 | + rawTitle: it.name, | |
| 107 | + imageUrls: it.image ? [it.image] : [], | |
| 108 | + attributes, | |
| 109 | + condition: { condition: normalizeCondition(categorySlug, conditionRaw), conditionRaw, completeness: watchCompleteness(it.name) }, | |
| 110 | + observedAt: raw.fetchedAt, | |
| 111 | + confidence: 0.85, | |
| 112 | + parserVersion: PARSER_VERSION, | |
| 113 | + listingType: 'fixed_price', | |
| 114 | + price: it.price, | |
| 115 | + currency: currencyOr(it.currency, 'USD'), | |
| 116 | + seller: "Bob's Watches", | |
| 117 | + location: 'US', | |
| 118 | + availability: it.availability === 'InStock' ? 'available' : it.availability === 'SoldOut' || it.availability === 'OutOfStock' ? 'sold' : 'unknown', | |
| 119 | + }), | |
| 120 | + ); | |
| 121 | + } | |
| 122 | + return out; | |
| 123 | + } | |
| 124 | +} | |
| 125 | + | |
| 126 | +export default function createConnector(meta: ConnectorMeta) { | |
| 127 | + return new BobsWatchesConnector(meta); | |
| 128 | +} | |
added
connectors/api/bobs-watches/meta.json
+32 −0
@@ -0,0 +1,32 @@ | ||
| 1 | +{ | |
| 2 | + "id": "bobs-watches", | |
| 3 | + "displayName": "Bob's Watches (pre-owned Rolex listings)", | |
| 4 | + "sourceId": "bobs-watches", | |
| 5 | + "sourceName": "Bob's Watches", | |
| 6 | + "sourceType": "dealer", | |
| 7 | + "sourceUrl": "https://www.bobswatches.com", | |
| 8 | + "module": "api/bobs-watches", | |
| 9 | + "enginePriority": ["api", "firecrawl"], | |
| 10 | + "categories": ["rolex", "watches", "other_watches"], | |
| 11 | + "regions": ["US"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["USD"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": false, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 720, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.8, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.bobswatches.com/terms-and-conditions", | |
| 26 | + "accessNotes": "Public model-family pages (bobswatches.com/rolex-<model>) fetched over plain HTTPS with the RareIndex user agent; robots.txt allows them (sort/query/search URLs are disallowed and never used). Each page embeds schema.org Product JSON-LD per watch (name, mpn = reference, sku = stock id, colour/material, USD price, availability). Listings only; Bob's does not publish realised prices. 1.5 s between pages.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["rolex-submariner", "rolex-daytona", "rolex-gmt-master-ii", "rolex-datejust", "rolex-day-date", "rolex-explorer", "rolex-sea-dweller", "rolex-yacht-master", "rolex-oyster-perpetual", "rolex-sky-dweller", "rolex-milgauss", "rolex-air-king", "vintage-rolex", "used-rolex-watches"] | |
| 31 | + } | |
| 32 | +} | |
added
connectors/api/crown-caliber/index.test.ts
+30 −0
@@ -0,0 +1,30 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('crown-caliber', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into listing records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('crown-caliber'); | |
| 15 | + const fx = loadFixture('crown-caliber', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const l = out[0]; | |
| 19 | + if (l?.kind !== 'listing') throw new Error('no listing'); | |
| 20 | + expect(l.attributes.categorySlug).toBe('rolex'); | |
| 21 | + expect(l.attributes.reference).toBeTruthy(); | |
| 22 | + expect(l.currency).toBe('USD'); | |
| 23 | + expect(l.sourceUrl).toMatch(/europeanwatch\.com\/watch\//); | |
| 24 | + }); | |
| 25 | + it('splits names into reference + model and reads circa years', async () => { | |
| 26 | + const { splitName } = await import('./index.js'); | |
| 27 | + expect(splitName('Rolex 16710 GMT-Master II Coke Bezel SS Black Dial Circa. 2000', 'Rolex')).toEqual({ reference: '16710', model: 'GMT-Master II Coke Bezel' }); | |
| 28 | + expect(splitName('Patek Philippe 5711/1A-010 Nautilus SS Blue Dial', 'Patek Philippe')).toMatchObject({ reference: '5711/1A-010' }); | |
| 29 | + }); | |
| 30 | +}); | |
added
connectors/api/crown-caliber/index.ts
+158 −0
@@ -0,0 +1,158 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { normalizeCondition } from '@rareindex/taxonomy'; | |
| 4 | +import { AssetAttributesSchema, NormalizedListingSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 5 | +import { WATCH_BRANDS, caseSize, currencyOr, moneyNumber, watchCategory, watchCompleteness, watchConditionRaw, watchMaterial, watchReferenceFromText } from '../_luxury-lib/index.js'; | |
| 6 | + | |
| 7 | +/** European Watch Company (ex Crown & Caliber domain) — ItemList JSON-LD on brand pages. */ | |
| 8 | +const BASE = 'https://www.europeanwatch.com'; | |
| 9 | +const PARSER_VERSION = '1.0.0'; | |
| 10 | + | |
| 11 | +export const ItemSchema = z.object({ name: z.string(), sku: z.string().nullable(), url: z.string(), price: z.number(), currency: z.string(), availability: z.string().nullable(), image: z.string().nullable(), condition: z.string().nullable() }); | |
| 12 | +export const PagePayloadSchema = z.object({ kind: z.literal('brand_page'), url: z.string(), brand: z.string(), page: z.number(), items: z.array(ItemSchema), details: z.record(z.string(), z.string()).optional() }); | |
| 13 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 14 | + | |
| 15 | +export function parseBrandPage(htmlText: string, url: string, brand: string, page: number): PagePayload { | |
| 16 | + const items: z.infer<typeof ItemSchema>[] = []; | |
| 17 | + const push = (prod: Record<string, unknown>) => { | |
| 18 | + const offers = (prod.offers as Record<string, unknown> | undefined) ?? {}; | |
| 19 | + const price = moneyNumber(offers.price as string | number | undefined); | |
| 20 | + if (!price) return; | |
| 21 | + const img = prod.image; | |
| 22 | + items.push({ | |
| 23 | + name: String(prod.name ?? '').replace(/\s+/g, ' ').trim(), | |
| 24 | + sku: prod.sku ? String(prod.sku) : null, | |
| 25 | + url: String(prod.url ?? ''), | |
| 26 | + price, | |
| 27 | + currency: String(offers.priceCurrency ?? 'USD'), | |
| 28 | + availability: offers.availability ? String(offers.availability).replace(/^.*\//, '') : null, | |
| 29 | + image: Array.isArray(img) ? String(img[0] ?? '') || null : typeof img === 'string' ? img : null, | |
| 30 | + condition: offers.itemCondition ? String(offers.itemCondition).replace(/^.*\//, '') : null, | |
| 31 | + }); | |
| 32 | + }; | |
| 33 | + for (const list of H.jsonLd(htmlText, 'ItemList')) { | |
| 34 | + for (const el of (list.itemListElement as Array<Record<string, unknown>>) ?? []) { | |
| 35 | + const prod = (el.item as Record<string, unknown> | undefined) ?? el; | |
| 36 | + if (prod && prod['@type'] === 'Product') push(prod); | |
| 37 | + } | |
| 38 | + } | |
| 39 | + for (const prod of H.jsonLd(htmlText, 'Product')) push(prod); | |
| 40 | + // product pages: spec table "Condition / Box / Papers / Year / Reference" | |
| 41 | + const details: Record<string, string> = {}; | |
| 42 | + for (const m of htmlText.matchAll(/(Condition|Box|Papers|Year|Reference|Movement|Case Size)<\/(?:p|dt|span)>\s*<(?:p|dd|span)[^>]*>([^<]{1,40})</g)) details[m[1]!.toLowerCase()] = m[2]!.trim(); | |
| 43 | + return { kind: 'brand_page', url, brand, page, items, ...(Object.keys(details).length ? { details } : {}) }; | |
| 44 | +} | |
| 45 | + | |
| 46 | +/** "Rolex 126234 Datejust 36 Jubilee SS Green Palm Dial" → reference 126234, model "Datejust 36" */ | |
| 47 | +export function splitName(name: string, brand: string): { reference: string | null; model: string | null } { | |
| 48 | + const rest = name.replace(new RegExp(`^${brand.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s+`, 'i'), ''); | |
| 49 | + const tokens = rest.split(/\s+/); | |
| 50 | + const ref = watchReferenceFromText(rest) ?? (tokens[0] && /\d/.test(tokens[0]) ? tokens[0] : null); | |
| 51 | + const afterRef = ref && tokens.length > 1 && tokens[0] === ref ? tokens.slice(1) : tokens; | |
| 52 | + const modelTokens: string[] = []; | |
| 53 | + for (const t of afterRef) { | |
| 54 | + if (/^(SS|18k|18K|Steel|Gold|Platinum|Titanium|Ceramic|Two|Rose|Yellow|White|Black|Blue|Green|Silver|Grey|Gray|Champagne|Circa|Full|Box|Papers|Dial|Bracelet|Strap|Jubilee|Oyster|Leather|\d{2}mm|\d{4})$/i.test(t)) break; | |
| 55 | + modelTokens.push(t); | |
| 56 | + if (modelTokens.length >= 4) break; | |
| 57 | + } | |
| 58 | + return { reference: ref, model: modelTokens.length ? modelTokens.join(' ') : null }; | |
| 59 | +} | |
| 60 | + | |
| 61 | +export class EuropeanWatchConnector extends BaseConnector { | |
| 62 | + readonly version = '1.0.0'; | |
| 63 | + readonly parserVersion = PARSER_VERSION; | |
| 64 | + protected override minIntervalMs = 1500; | |
| 65 | + override readonly urlPatterns = [/^https?:\/\/(www\.)?europeanwatch\.com\/watch\/[a-z0-9-]+/i, /^https?:\/\/(www\.)?crownandcaliber\.com\/products\/[a-z0-9-]+/i]; | |
| 66 | + | |
| 67 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 68 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 69 | + const pages = ctx.options.mode === 'backfill' ? 10 : Number(this.meta.config.pagesPerSeed ?? 2); | |
| 70 | + let count = 0; | |
| 71 | + for (const seed of seeds) { | |
| 72 | + let prevFirst = ''; | |
| 73 | + for (let page = 1; page <= pages; page++) { | |
| 74 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 75 | + const url = `${BASE}/brand/${seed}${page > 1 ? `?page=${page}` : ''}`; | |
| 76 | + await this.throttle(); | |
| 77 | + const res = await ctx.fetch(url, { responseType: 'text', expect: ['title', 'price'], parse: (r) => { | |
| 78 | + const first = r.html ? parseBrandPage(r.html, url, seed, page).items[0] : undefined; | |
| 79 | + return first ? { title: first.name, price: first.price } : null; | |
| 80 | + } }); | |
| 81 | + const payload = res.success && res.html ? parseBrandPage(res.html, url, seed, page) : null; | |
| 82 | + if (!payload || payload.items.length === 0) { | |
| 83 | + if (page === 1) ctx.anomaly(payload ? 'empty_page' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 84 | + break; | |
| 85 | + } | |
| 86 | + const firstSku = payload.items[0]!.sku ?? payload.items[0]!.url; | |
| 87 | + if (firstSku === prevFirst) break; // the site serves the whole brand inventory on one page | |
| 88 | + prevFirst = firstSku; | |
| 89 | + count++; | |
| 90 | + yield { url, externalId: `brand:${seed}:${page}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 91 | + } | |
| 92 | + } | |
| 93 | + } | |
| 94 | + | |
| 95 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 96 | + await this.throttle(); | |
| 97 | + const res = await ctx.fetch(url, { responseType: 'text', minQuality: 0.2 }); | |
| 98 | + if (!res.success || !res.html) return []; | |
| 99 | + const payload = parseBrandPage(res.html, url, 'lookup', 1); | |
| 100 | + return payload.items.length ? [{ url, externalId: `product:${payload.items[0]!.sku ?? url}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }] : []; | |
| 101 | + } | |
| 102 | + | |
| 103 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 104 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 105 | + const out: NormalizedRecord[] = []; | |
| 106 | + const seen = new Set<string>(); | |
| 107 | + for (const it of p.items) { | |
| 108 | + const id = it.sku ?? it.url; | |
| 109 | + if (seen.has(id)) continue; | |
| 110 | + seen.add(id); | |
| 111 | + const brand = WATCH_BRANDS.find((b) => it.name.toLowerCase().startsWith(b.toLowerCase())) ?? it.name.split(' ')[0]!; | |
| 112 | + const categorySlug = watchCategory(brand); | |
| 113 | + const { reference, model } = splitName(it.name, brand); | |
| 114 | + const circa = it.name.match(/Circa\.?\s*(\d{4})/i)?.[1] ?? p.details?.year?.match(/\d{4}/)?.[0]; | |
| 115 | + const conditionRaw = p.details?.condition ?? watchConditionRaw(it.name) ?? (it.condition === 'UsedCondition' ? 'Pre-owned' : it.condition === 'NewCondition' ? 'Unworn' : null); | |
| 116 | + const completeness = p.details ? (/yes/i.test(p.details.box ?? '') && /yes/i.test(p.details.papers ?? '') ? 'full_set' : /yes/i.test(p.details.papers ?? '') ? 'papers_only' : /yes/i.test(p.details.box ?? '') ? 'box_only' : 'watch_only') : watchCompleteness(it.name); | |
| 117 | + const attributes = AssetAttributesSchema.parse({ | |
| 118 | + categorySlug, | |
| 119 | + brand, | |
| 120 | + name: `${brand} ${model ?? ''}`.trim(), | |
| 121 | + model, | |
| 122 | + reference, | |
| 123 | + year: circa ? Number(circa) : null, | |
| 124 | + material: watchMaterial(it.name), | |
| 125 | + size: caseSize(it.name), | |
| 126 | + identifiers: { ...(reference ? { reference } : {}), europeanwatch_sku: id }, | |
| 127 | + metadata: { brand_page: p.url }, | |
| 128 | + }); | |
| 129 | + out.push( | |
| 130 | + NormalizedListingSchema.parse({ | |
| 131 | + kind: 'listing', | |
| 132 | + connectorId: this.meta.id, | |
| 133 | + sourceId: this.meta.sourceId, | |
| 134 | + sourceUrl: it.url, | |
| 135 | + externalId: id, | |
| 136 | + rawTitle: it.name, | |
| 137 | + imageUrls: it.image ? [it.image] : [], | |
| 138 | + attributes, | |
| 139 | + condition: { condition: normalizeCondition(categorySlug, conditionRaw), conditionRaw, completeness }, | |
| 140 | + observedAt: raw.fetchedAt, | |
| 141 | + confidence: 0.85, | |
| 142 | + parserVersion: PARSER_VERSION, | |
| 143 | + listingType: 'fixed_price', | |
| 144 | + price: it.price, | |
| 145 | + currency: currencyOr(it.currency, 'USD'), | |
| 146 | + seller: 'European Watch Company', | |
| 147 | + location: 'Boston, US', | |
| 148 | + availability: it.availability === 'InStock' ? 'available' : it.availability ? 'sold' : 'unknown', | |
| 149 | + }), | |
| 150 | + ); | |
| 151 | + } | |
| 152 | + return out; | |
| 153 | + } | |
| 154 | +} | |
| 155 | + | |
| 156 | +export default function createConnector(meta: ConnectorMeta) { | |
| 157 | + return new EuropeanWatchConnector(meta); | |
| 158 | +} | |
added
connectors/api/crown-caliber/meta.json
+65 −0
@@ -0,0 +1,65 @@ | ||
| 1 | +{ | |
| 2 | + "id": "crown-caliber", | |
| 3 | + "displayName": "European Watch Company (crownandcaliber.com) \u2014 pre-owned watch listings", | |
| 4 | + "sourceId": "europeanwatch", | |
| 5 | + "sourceName": "European Watch Company", | |
| 6 | + "sourceType": "dealer", | |
| 7 | + "sourceUrl": "https://www.europeanwatch.com", | |
| 8 | + "module": "api/crown-caliber", | |
| 9 | + "enginePriority": [ | |
| 10 | + "api", | |
| 11 | + "firecrawl" | |
| 12 | + ], | |
| 13 | + "categories": [ | |
| 14 | + "watches", | |
| 15 | + "rolex", | |
| 16 | + "patek_philippe", | |
| 17 | + "audemars_piguet", | |
| 18 | + "omega", | |
| 19 | + "other_watches" | |
| 20 | + ], | |
| 21 | + "regions": [ | |
| 22 | + "US" | |
| 23 | + ], | |
| 24 | + "languages": [ | |
| 25 | + "en" | |
| 26 | + ], | |
| 27 | + "currency": [ | |
| 28 | + "USD" | |
| 29 | + ], | |
| 30 | + "supportsListings": true, | |
| 31 | + "supportsSold": false, | |
| 32 | + "supportsAuctions": false, | |
| 33 | + "supportsImages": true, | |
| 34 | + "supportsCatalog": false, | |
| 35 | + "supportsPopulation": false, | |
| 36 | + "supportsLookup": true, | |
| 37 | + "refreshFrequencyMinutes": 720, | |
| 38 | + "priority": "medium", | |
| 39 | + "trustScore": 0.8, | |
| 40 | + "attributionRequired": true, | |
| 41 | + "termsUrl": "https://www.europeanwatch.com/terms", | |
| 42 | + "accessNotes": "crownandcaliber.com now redirects to europeanwatch.com (Boston dealer). Public brand pages (/brand/<slug>?page=N) embed a schema.org ItemList of Products (name, sku, USD price, availability, condition) \u2014 fetched over plain HTTPS with the RareIndex user agent; robots.txt only disallows /login. References and circa years are parsed from the product name; box/papers/condition details live on product pages and are only fetched by lookup(). 1.5 s between pages, the whole brand inventory is served on one page (~130 watches for Rolex; pagination parameters are ignored, which the crawler detects).", | |
| 43 | + "enabled": true, | |
| 44 | + "schemaVersion": "1.0", | |
| 45 | + "config": { | |
| 46 | + "seeds": [ | |
| 47 | + "rolex", | |
| 48 | + "patek-philippe", | |
| 49 | + "audemars-piguet", | |
| 50 | + "omega", | |
| 51 | + "cartier", | |
| 52 | + "tudor", | |
| 53 | + "vacheron-constantin", | |
| 54 | + "a-lange-and-sohne", | |
| 55 | + "iwc", | |
| 56 | + "jaeger-lecoultre", | |
| 57 | + "breitling", | |
| 58 | + "panerai", | |
| 59 | + "grand-seiko", | |
| 60 | + "f-p-journe", | |
| 61 | + "zenith" | |
| 62 | + ], | |
| 63 | + "pagesPerSeed": 1 | |
| 64 | + } | |
| 65 | +} | |
added
connectors/api/fashionphile/index.test.ts
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('fashionphile', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into listing records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('fashionphile'); | |
| 15 | + const fx = loadFixture('fashionphile', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const l = out.find((r) => r.kind === 'listing'); | |
| 19 | + if (l?.kind !== 'listing') throw new Error('no listing'); | |
| 20 | + expect(l.currency).toBe('USD'); | |
| 21 | + expect(l.price).toBeGreaterThan(0); | |
| 22 | + expect(l.seller).toBe('FASHIONPHILE'); | |
| 23 | + expect(['luxury_handbags', 'fashion_streetwear', 'jewelry', 'rolex', 'other_watches']).toContain(l.attributes.categorySlug); | |
| 24 | + expect(l.attributes.brand).toBeTruthy(); | |
| 25 | + expect(l.attributes.identifiers.fashionphile_sku).toBeTruthy(); | |
| 26 | + expect(new Set(out.map((r) => (r as { availability?: string }).availability))).toBeTruthy(); | |
| 27 | + }); | |
| 28 | + it('maps sold-out published products to availability=sold and classifies shoes as fashion', async () => { | |
| 29 | + const { categoryFor } = await import('./index.js'); | |
| 30 | + expect(categoryFor('Shoes', 'Hermes', 'Calfskin Womens Mage Sandals 39 Black')).toBe('fashion_streetwear'); | |
| 31 | + expect(categoryFor('', 'Hermes', 'Calfskin Womens Mage Sandals 39 Black')).toBe('fashion_streetwear'); | |
| 32 | + expect(categoryFor('Bags', 'Hermes', 'Togo Birkin 30 Gold')).toBe('luxury_handbags'); | |
| 33 | + expect(categoryFor('Watches', 'Rolex', 'Stainless Steel Datejust 36 Watch')).toBe('rolex'); | |
| 34 | + }); | |
| 35 | +}); | |
added
connectors/api/fashionphile/index.ts
+129 −0
@@ -0,0 +1,129 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { normalizeCondition } from '@rareindex/taxonomy'; | |
| 4 | +import { AssetAttributesSchema, NormalizedListingSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 5 | +import { ShopifyProductSchema, bagHardware, bagMaterial, bagModel, bagSize, caseSize, fetchShopifyPage, moneyNumber, trimShopifyProduct, watchCategory, watchMaterial, watchReferenceFromText, yearFrom } from '../_luxury-lib/index.js'; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * FASHIONPHILE — one Shopify product = one authenticated pre-owned item. Listings (asking prices in | |
| 9 | + * USD) plus sold state (`available:false` on a published product = the site shows "SOLD"). | |
| 10 | + */ | |
| 11 | +const BASE = 'https://www.fashionphile.com'; | |
| 12 | +const PARSER_VERSION = '1.0.0'; | |
| 13 | + | |
| 14 | +export const PagePayloadSchema = z.object({ kind: z.literal('shopify_page'), url: z.string(), seed: z.string(), page: z.number(), products: z.array(ShopifyProductSchema) }); | |
| 15 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 16 | + | |
| 17 | +export function categoryFor(productType: string | null | undefined, vendor: string | null | undefined, title: string): string { | |
| 18 | + const t = (productType ?? '').toLowerCase(); | |
| 19 | + const l = title.toLowerCase(); | |
| 20 | + if (/watch/.test(t) || /\bwatch\b/.test(l)) return watchCategory(vendor); | |
| 21 | + if (/jewel|ring|bracelet|necklace|earring|brooch|pendant/.test(t)) return 'jewelry'; | |
| 22 | + if (/bag|wallet|clutch|tote|backpack|pouch|luggage|small leather/.test(t)) return 'luxury_handbags'; | |
| 23 | + if (/shoe|sneaker|boot|sandal|heel|pump|loafer|apparel|clothing|scarf|belt|hat|sunglass|jacket|coat|dress/.test(t)) return 'fashion_streetwear'; | |
| 24 | + if (/\b(sandals?|pumps?|loafers?|sneakers?|boots?|heels?|mules?|flats?|espadrilles?|slides?)\b/.test(l) || /\b(muffler|scarf|shawl|stole|belt|sunglasses|hat|cap|jacket|coat|dress|sweater|cardigan|t-shirt|shirt)\b/.test(l)) return 'fashion_streetwear'; | |
| 25 | + return 'luxury_handbags'; | |
| 26 | +} | |
| 27 | + | |
| 28 | +export class FashionphileConnector extends BaseConnector { | |
| 29 | + readonly version = '1.0.0'; | |
| 30 | + readonly parserVersion = PARSER_VERSION; | |
| 31 | + protected override minIntervalMs = 1500; | |
| 32 | + override readonly urlPatterns = [/^https?:\/\/(www\.)?fashionphile\.com\/products\/([a-z0-9-]+)/i]; | |
| 33 | + | |
| 34 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 35 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 36 | + const pages = ctx.options.mode === 'backfill' ? 20 : Number(this.meta.config.pagesPerSeed ?? 2); | |
| 37 | + let count = 0; | |
| 38 | + for (const seed of seeds) { | |
| 39 | + for (let page = 1; page <= pages; page++) { | |
| 40 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 41 | + await this.throttle(); | |
| 42 | + const { products, res } = await fetchShopifyPage(ctx, BASE, `/collections/${seed}/products.json`, page); | |
| 43 | + if (!res.success) { | |
| 44 | + ctx.anomaly('page_fetch_failed', `${seed} p${page}: ${res.error ?? res.httpStatus}`); | |
| 45 | + break; | |
| 46 | + } | |
| 47 | + if (products.length === 0) break; | |
| 48 | + count++; | |
| 49 | + const payload: PagePayload = { kind: 'shopify_page', url: `${BASE}/collections/${seed}/products.json?page=${page}`, seed, page, products: products.map(trimShopifyProduct) }; | |
| 50 | + yield { url: payload.url, externalId: `collection:${seed}:${page}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 51 | + if (products.length < 250) break; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + } | |
| 55 | + | |
| 56 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 57 | + const handle = url.match(this.urlPatterns[0]!)?.[2]; | |
| 58 | + if (!handle) return []; | |
| 59 | + await this.throttle(); | |
| 60 | + const res = await ctx.fetch(`${BASE}/products/${handle}.json`, { engines: ['api'], responseType: 'json', minQuality: 0.2 }); | |
| 61 | + const parsed = z.object({ product: ShopifyProductSchema }).safeParse(res.json); | |
| 62 | + if (!res.success || !parsed.success) return []; | |
| 63 | + const payload: PagePayload = { kind: 'shopify_page', url: `${BASE}/products/${handle}`, seed: `product:${handle}`, page: 1, products: [trimShopifyProduct(parsed.data.product)] }; | |
| 64 | + return [{ url: payload.url, externalId: `product:${handle}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }]; | |
| 65 | + } | |
| 66 | + | |
| 67 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 68 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 69 | + const out: NormalizedRecord[] = []; | |
| 70 | + for (const pr of p.products) { | |
| 71 | + const v = pr.variants[0]; | |
| 72 | + if (!v) continue; | |
| 73 | + const price = moneyNumber(v.price); | |
| 74 | + if (!price) continue; | |
| 75 | + const brand = pr.vendor ?? null; | |
| 76 | + const categorySlug = categoryFor(pr.product_type, brand, pr.title); | |
| 77 | + const isWatch = categorySlug.endsWith('watches') || categorySlug === 'rolex' || categorySlug === 'patek_philippe' || categorySlug === 'audemars_piguet' || categorySlug === 'omega'; | |
| 78 | + const title = pr.title.replace(/\s+/g, ' ').trim(); | |
| 79 | + const ref = isWatch ? watchReferenceFromText(title) : null; | |
| 80 | + const model = isWatch ? null : bagModel(title); | |
| 81 | + const attributes = AssetAttributesSchema.parse({ | |
| 82 | + categorySlug, | |
| 83 | + brand, | |
| 84 | + name: brand ? `${brand} ${title}` : title, | |
| 85 | + model, | |
| 86 | + reference: ref, | |
| 87 | + year: yearFrom(pr.body_html ?? '') ?? null, | |
| 88 | + material: isWatch ? watchMaterial(title) : bagMaterial(title), | |
| 89 | + size: isWatch ? caseSize(title) : bagSize(title), | |
| 90 | + color: null, | |
| 91 | + identifiers: { fashionphile_sku: v.sku ?? String(pr.id), ...(ref ? { reference: ref } : {}) }, | |
| 92 | + metadata: { product_type: pr.product_type, hardware: bagHardware(title), tags: pr.tags?.slice(0, 12) ?? [], compare_at_price: moneyNumber(v.compare_at_price) }, | |
| 93 | + }); | |
| 94 | + const sourceUrl = `${BASE}/products/${pr.handle}`; | |
| 95 | + const availability = pr.published_at ? (v.available ? 'available' : 'sold') : 'removed'; | |
| 96 | + const listedAt = pr.published_at ? new Date(pr.published_at) : null; | |
| 97 | + out.push( | |
| 98 | + NormalizedListingSchema.parse({ | |
| 99 | + kind: 'listing', | |
| 100 | + connectorId: this.meta.id, | |
| 101 | + sourceId: this.meta.sourceId, | |
| 102 | + sourceUrl, | |
| 103 | + externalId: String(pr.id), | |
| 104 | + rawTitle: title, | |
| 105 | + description: pr.body_html ?? null, | |
| 106 | + imageUrls: (pr.images ?? []).map((i) => i.src), | |
| 107 | + attributes, | |
| 108 | + condition: { condition: normalizeCondition(categorySlug, null), conditionRaw: null, completeness: null }, | |
| 109 | + observedAt: raw.fetchedAt, | |
| 110 | + confidence: 0.85, | |
| 111 | + parserVersion: PARSER_VERSION, | |
| 112 | + listingType: 'fixed_price', | |
| 113 | + price, | |
| 114 | + currency: 'USD', | |
| 115 | + seller: 'FASHIONPHILE', | |
| 116 | + location: 'US', | |
| 117 | + quantity: 1, | |
| 118 | + listedAt: listedAt && !Number.isNaN(listedAt.getTime()) ? listedAt : null, | |
| 119 | + availability, | |
| 120 | + }), | |
| 121 | + ); | |
| 122 | + } | |
| 123 | + return out; | |
| 124 | + } | |
| 125 | +} | |
| 126 | + | |
| 127 | +export default function createConnector(meta: ConnectorMeta) { | |
| 128 | + return new FashionphileConnector(meta); | |
| 129 | +} | |
added
connectors/api/fashionphile/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "fashionphile", | |
| 3 | + "displayName": "FASHIONPHILE (pre-owned luxury listings & sold items)", | |
| 4 | + "sourceId": "fashionphile", | |
| 5 | + "sourceName": "FASHIONPHILE", | |
| 6 | + "sourceType": "marketplace", | |
| 7 | + "sourceUrl": "https://www.fashionphile.com", | |
| 8 | + "module": "api/fashionphile", | |
| 9 | + "enginePriority": ["api"], | |
| 10 | + "categories": ["luxury_handbags", "watches", "other_watches", "rolex", "jewelry", "fashion_streetwear"], | |
| 11 | + "regions": ["US"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["USD"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": false, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 720, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.8, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.fashionphile.com/terms-of-use", | |
| 26 | + "accessNotes": "Public Shopify storefront feed (www.fashionphile.com/collections/<brand>/products.json) fetched over plain HTTPS with the RareIndex user agent; robots.txt allows /collections/ (only sort/filter permutations, cart, account and checkout paths are disallowed). Each product is one authenticated pre-owned item priced in USD; `available:false` on a still-published product means the item shows SOLD on the site, which is recorded as a listing with availability=sold (the last asking price is not asserted as the transaction price). Condition ratings are only on HTML product pages and are not fetched. 1.5 s between requests, 250 products per page.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["hermes", "chanel", "louis-vuitton", "dior", "gucci", "goyard", "fendi", "bottega-veneta", "prada", "celine", "loewe", "saint-laurent", "rolex", "cartier", "van-cleef-arpels"], | |
| 31 | + "pagesPerSeed": 2 | |
| 32 | + } | |
| 33 | +} | |
added
connectors/api/laced/index.test.ts
+29 −0
@@ -0,0 +1,29 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('laced', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into catalog_item records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('laced'); | |
| 15 | + const fx = loadFixture('laced', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const cat = out.find((r) => r.kind === 'catalog_item'); | |
| 19 | + if (cat?.kind !== 'catalog_item') throw new Error('no catalog item'); | |
| 20 | + expect(cat.attributes.identifiers.style_code).toMatch(/^[A-Z]{1,2}\d{4,5}-\d{3}$/); | |
| 21 | + expect(cat.attributes.categorySlug).toBe('nike_jordan'); | |
| 22 | + const asks = out.filter((r) => r.kind === 'listing'); | |
| 23 | + expect(asks.length).toBeGreaterThan(3); | |
| 24 | + for (const a of asks) if (a.kind === 'listing') { expect(a.currency).toBe('GBP'); expect(a.attributes.size).toMatch(/^UK /); } | |
| 25 | + const low = out.find((r) => r.kind === 'price_observation'); | |
| 26 | + if (low?.kind === 'price_observation') expect(low.price).toBeLessThanOrEqual(Math.min(...asks.map((a) => (a as { price: number }).price))); | |
| 27 | + }); | |
| 28 | + | |
| 29 | +}); | |
added
connectors/api/laced/index.ts
+143 −0
@@ -0,0 +1,143 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { AssetAttributesSchema, NormalizedCatalogItemSchema, NormalizedListingSchema, NormalizedPriceObservationSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 4 | +import { currencyOr, moneyNumber, sneakerCategory, styleCodeFromText } from '../_luxury-lib/index.js'; | |
| 5 | + | |
| 6 | +/** Laced — ProductGroup JSON-LD on product pages: style code + per-size lowest asks (GBP). */ | |
| 7 | +const BASE = 'https://www.laced.com'; | |
| 8 | +const PARSER_VERSION = '1.0.0'; | |
| 9 | + | |
| 10 | +export const SizeOfferSchema = z.object({ size: z.string(), price: z.number(), currency: z.string(), available: z.boolean() }); | |
| 11 | +export const ProductPayloadSchema = z.object({ | |
| 12 | + kind: z.literal('product_page'), | |
| 13 | + url: z.string(), | |
| 14 | + slug: z.string(), | |
| 15 | + name: z.string(), | |
| 16 | + sku: z.string().nullable(), | |
| 17 | + brand: z.string().nullable(), | |
| 18 | + images: z.array(z.string()), | |
| 19 | + description: z.string().nullable(), | |
| 20 | + lowPrice: z.number().nullable(), | |
| 21 | + highPrice: z.number().nullable(), | |
| 22 | + currency: z.string(), | |
| 23 | + sizes: z.array(SizeOfferSchema), | |
| 24 | +}); | |
| 25 | +export type ProductPayload = z.infer<typeof ProductPayloadSchema>; | |
| 26 | + | |
| 27 | +export function parseProductPage(htmlText: string, url: string): ProductPayload | null { | |
| 28 | + const group = H.jsonLd(htmlText, 'ProductGroup')[0] ?? H.jsonLd(htmlText, 'Product')[0]; | |
| 29 | + if (!group) return null; | |
| 30 | + const agg = (group.offers as Record<string, unknown> | undefined) ?? {}; | |
| 31 | + const sizes: z.infer<typeof SizeOfferSchema>[] = []; | |
| 32 | + for (const v of (group.hasVariant as Array<Record<string, unknown>>) ?? []) { | |
| 33 | + const off = (v.offers as Record<string, unknown> | undefined) ?? {}; | |
| 34 | + const price = moneyNumber(off.price as string | number | undefined); | |
| 35 | + if (!price) continue; | |
| 36 | + sizes.push({ size: String(v.size ?? '').trim(), price, currency: String(off.priceCurrency ?? agg.priceCurrency ?? 'GBP'), available: !/OutOfStock|SoldOut/.test(String(off.availability ?? '')) }); | |
| 37 | + } | |
| 38 | + const brand = group.brand && typeof group.brand === 'object' ? String((group.brand as { name?: string }).name ?? '') : group.brand ? String(group.brand) : null; | |
| 39 | + const img = group.image; | |
| 40 | + const slug = url.match(/\/products\/([a-z0-9-]+)/i)?.[1] ?? url; | |
| 41 | + return { | |
| 42 | + kind: 'product_page', | |
| 43 | + url: `${BASE}/products/${slug}`, | |
| 44 | + slug, | |
| 45 | + name: String(group.name ?? '').replace(/\s+/g, ' ').trim(), | |
| 46 | + sku: group.sku ? String(group.sku) : null, | |
| 47 | + brand: brand || null, | |
| 48 | + images: Array.isArray(img) ? img.slice(0, 3).map(String) : typeof img === 'string' ? [img] : [], | |
| 49 | + description: group.description ? String(group.description).slice(0, 500) : null, | |
| 50 | + lowPrice: moneyNumber(agg.lowPrice as string | number | undefined), | |
| 51 | + highPrice: moneyNumber(agg.highPrice as string | number | undefined), | |
| 52 | + currency: String(agg.priceCurrency ?? sizes[0]?.currency ?? 'GBP'), | |
| 53 | + sizes, | |
| 54 | + }; | |
| 55 | +} | |
| 56 | + | |
| 57 | +export function parseBrandPage(htmlText: string): string[] { | |
| 58 | + return [...new Set([...htmlText.matchAll(/href="\/products\/([a-z0-9-]+)"/g)].map((m) => m[1]!))]; | |
| 59 | +} | |
| 60 | + | |
| 61 | +export class LacedConnector extends BaseConnector { | |
| 62 | + readonly version = '1.0.0'; | |
| 63 | + readonly parserVersion = PARSER_VERSION; | |
| 64 | + protected override minIntervalMs = 1500; | |
| 65 | + override readonly urlPatterns = [/^https?:\/\/(www\.)?laced\.com\/(?:[a-z]{2}\/)?products\/([a-z0-9-]+)/i]; | |
| 66 | + | |
| 67 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 68 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 69 | + const perSeed = Number(this.meta.config.productsPerSeed ?? 24); | |
| 70 | + let count = 0; | |
| 71 | + for (const seed of seeds) { | |
| 72 | + await this.throttle(); | |
| 73 | + const list = await ctx.fetch(`${BASE}/${seed}`, { responseType: 'text', minQuality: 0.2 }); | |
| 74 | + if (!list.success || !list.html) { | |
| 75 | + ctx.anomaly('page_fetch_failed', `${seed}: ${list.error ?? list.httpStatus}`); | |
| 76 | + continue; | |
| 77 | + } | |
| 78 | + const slugs = parseBrandPage(list.html).slice(0, perSeed); | |
| 79 | + if (!slugs.length) ctx.anomaly('empty_page', seed); | |
| 80 | + for (const slug of slugs) { | |
| 81 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 82 | + const url = `${BASE}/products/${slug}`; | |
| 83 | + if (!(await ctx.shouldFetch(url))) continue; | |
| 84 | + const rec = await this.fetchProduct(url, ctx); | |
| 85 | + if (rec) { | |
| 86 | + count++; | |
| 87 | + yield rec; | |
| 88 | + } | |
| 89 | + } | |
| 90 | + } | |
| 91 | + } | |
| 92 | + | |
| 93 | + private async fetchProduct(url: string, ctx: CrawlContext): Promise<RawRecordInput | null> { | |
| 94 | + await this.throttle(); | |
| 95 | + const res = await ctx.fetch(url, { responseType: 'text', expect: ['title', 'price', 'identifiers'], parse: (r) => { | |
| 96 | + const p = r.html ? parseProductPage(r.html, url) : null; | |
| 97 | + return p ? { title: p.name, price: p.lowPrice ?? p.sizes[0]?.price, identifiers: p.sku ? { sku: p.sku } : null } : null; | |
| 98 | + } }); | |
| 99 | + const payload = res.success && res.html ? parseProductPage(res.html, url) : null; | |
| 100 | + if (!payload) { | |
| 101 | + if (res.httpStatus !== 404) ctx.anomaly(res.success ? 'parse_failure' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 102 | + return null; | |
| 103 | + } | |
| 104 | + return { url: payload.url, externalId: payload.slug, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 105 | + } | |
| 106 | + | |
| 107 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 108 | + const slug = url.match(this.urlPatterns[0]!)?.[2]; | |
| 109 | + if (!slug) return []; | |
| 110 | + const rec = await this.fetchProduct(`${BASE}/products/${slug}`, ctx); | |
| 111 | + return rec ? [rec] : []; | |
| 112 | + } | |
| 113 | + | |
| 114 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 115 | + const p = ProductPayloadSchema.parse(raw.payload); | |
| 116 | + const styleCode = p.sku ?? styleCodeFromText(p.name); | |
| 117 | + const year = p.name.match(/\((\d{4})\)/)?.[1]; | |
| 118 | + const attributes = AssetAttributesSchema.parse({ | |
| 119 | + categorySlug: sneakerCategory(p.brand, p.name), | |
| 120 | + brand: p.brand, | |
| 121 | + name: p.name.replace(/\s*\(\d{4}\)\s*$/, '').trim(), | |
| 122 | + year: year ? Number(year) : null, | |
| 123 | + identifiers: { ...(styleCode ? { style_code: styleCode } : {}), laced_slug: p.slug }, | |
| 124 | + metadata: { sizes_listed: p.sizes.length }, | |
| 125 | + }); | |
| 126 | + const currency = currencyOr(p.currency, 'GBP'); | |
| 127 | + const base = { connectorId: this.meta.id, sourceId: this.meta.sourceId, sourceUrl: p.url, rawTitle: p.name, description: p.description, imageUrls: p.images, attributes, observedAt: raw.fetchedAt, parserVersion: PARSER_VERSION }; | |
| 128 | + const cond = { condition: 'new', conditionRaw: 'Brand new (deadstock marketplace)', completeness: 'with_box' }; | |
| 129 | + const out: NormalizedRecord[] = [NormalizedCatalogItemSchema.parse({ kind: 'catalog_item', ...base, externalId: `product:${p.slug}`, confidence: 0.9 })]; | |
| 130 | + const low = p.lowPrice ?? (p.sizes.length ? Math.min(...p.sizes.map((s) => s.price)) : null); | |
| 131 | + if (low) out.push(NormalizedPriceObservationSchema.parse({ kind: 'price_observation', ...base, externalId: `product:${p.slug}:low`, confidence: 0.75, condition: cond, priceKind: 'low', price: low, currency, observationDate: raw.fetchedAt, sampleSize: p.sizes.length || null })); | |
| 132 | + for (const s of p.sizes) { | |
| 133 | + if (!s.available) continue; | |
| 134 | + const uk = s.size.match(/UK\s*([\d.]+)/i)?.[1] ?? s.size.split('|')[0]?.trim() ?? s.size; | |
| 135 | + out.push(NormalizedListingSchema.parse({ kind: 'listing', ...base, attributes: { ...attributes, size: `UK ${uk}` }, externalId: `product:${p.slug}:size:${uk}`, confidence: 0.75, condition: cond, listingType: 'ask', price: s.price, currency: currencyOr(s.currency, currency), seller: 'Laced marketplace', location: 'GB', availability: 'available' })); | |
| 136 | + } | |
| 137 | + return out; | |
| 138 | + } | |
| 139 | +} | |
| 140 | + | |
| 141 | +export default function createConnector(meta: ConnectorMeta) { | |
| 142 | + return new LacedConnector(meta); | |
| 143 | +} | |
added
connectors/api/laced/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "laced", | |
| 3 | + "displayName": "Laced (UK sneaker marketplace — asks per size)", | |
| 4 | + "sourceId": "laced", | |
| 5 | + "sourceName": "Laced", | |
| 6 | + "sourceType": "marketplace", | |
| 7 | + "sourceUrl": "https://www.laced.com", | |
| 8 | + "module": "api/laced", | |
| 9 | + "enginePriority": ["api", "firecrawl"], | |
| 10 | + "categories": ["sneakers", "nike_jordan", "adidas_yeezy", "new_balance_asics_other"], | |
| 11 | + "regions": ["GB", "EU"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["GBP"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": true, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 720, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.75, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.laced.com/pages/terms-and-conditions", | |
| 26 | + "accessNotes": "Public brand pages (laced.com/<brand>) list product slugs; each product page embeds a schema.org ProductGroup with the style code (sku), brand and one Product per size carrying the current lowest ask in GBP. Plain HTTPS with the RareIndex user agent; robots.txt allows everything except account/admin. New-condition marketplace (deadstock). 1.5 s between pages; productsPerSeed caps product-page fetches per run.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["air-jordan", "nike", "adidas", "yeezy", "new-balance", "asics"], | |
| 31 | + "productsPerSeed": 24 | |
| 32 | + } | |
| 33 | +} | |
added
connectors/api/rebag/index.test.ts
+31 −0
@@ -0,0 +1,31 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('rebag', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into listing records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('rebag'); | |
| 15 | + const fx = loadFixture('rebag', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const l = out.find((r) => r.kind === 'listing' && r.attributes.categorySlug === 'luxury_handbags'); | |
| 19 | + if (l?.kind !== 'listing') throw new Error('no handbag listing'); | |
| 20 | + expect(l.currency).toBe('USD'); | |
| 21 | + expect(l.condition.conditionRaw).toBeTruthy(); | |
| 22 | + expect(l.attributes.identifiers.rebag_item).toBeTruthy(); | |
| 23 | + }); | |
| 24 | + it('parses Rebag variant titles and retail price', async () => { | |
| 25 | + const { parseVariantTitle, retailFromBody, categoryFor } = await import('./index.js'); | |
| 26 | + expect(parseVariantTitle('Great | Item # 417505/4 / Blue')).toEqual({ grade: 'Great', color: 'Blue' }); | |
| 27 | + expect(retailFromBody('<b>Estimated Retail Price:</b> $4,850<br>')).toBe(4850); | |
| 28 | + expect(categoryFor('Top Handle Bag', 'Hermes', [])).toBe('luxury_handbags'); | |
| 29 | + expect(categoryFor('Dress', 'Hermes', [])).toBe('fashion_streetwear'); | |
| 30 | + }); | |
| 31 | +}); | |
added
connectors/api/rebag/index.ts
+145 −0
@@ -0,0 +1,145 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { normalizeCondition } from '@rareindex/taxonomy'; | |
| 4 | +import { AssetAttributesSchema, NormalizedListingSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 5 | +import { ShopifyProductSchema, bagHardware, bagMaterial, bagModel, bagSize, caseSize, fetchShopifyPage, moneyNumber, trimShopifyProduct, watchCategory, watchMaterial, watchReferenceFromText } from '../_luxury-lib/index.js'; | |
| 6 | + | |
| 7 | +/** Rebag — Shopify feed of individually graded pre-owned luxury items (USD). */ | |
| 8 | +const BASE = 'https://shop.rebag.com'; | |
| 9 | +const PARSER_VERSION = '1.0.0'; | |
| 10 | + | |
| 11 | +export const PagePayloadSchema = z.object({ kind: z.literal('shopify_page'), url: z.string(), seed: z.string(), page: z.number(), products: z.array(ShopifyProductSchema) }); | |
| 12 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 13 | + | |
| 14 | +const CONDITION_MAP: Record<string, string> = { pristine: 'pristine', excellent: 'excellent', great: 'very_good', 'very good': 'very_good', good: 'good', fair: 'fair', new: 'new' }; | |
| 15 | + | |
| 16 | +export function categoryFor(productType: string | null | undefined, vendor: string | null | undefined, tags: string[]): string { | |
| 17 | + const t = (productType ?? '').toLowerCase(); | |
| 18 | + const tagStr = tags.join(' ').toLowerCase(); | |
| 19 | + if (/watch/.test(t) || /bc-filter-watches/.test(tagStr)) return watchCategory(vendor); | |
| 20 | + if (/jewel|ring|bracelet|necklace|earring|brooch|pendant|charm/.test(t)) return 'jewelry'; | |
| 21 | + if (/bag|tote|clutch|satchel|backpack|wallet|pouch|luggage|handle|hobo|crossbody|shoulder|belt bag|case|small leather/.test(t)) return 'luxury_handbags'; | |
| 22 | + if (/shoe|sneaker|boot|sandal|heel|pump|loafer|apparel|clothing|scarf|shawl|belt|hat|sunglass|dress|jacket|coat|\btop\b|pant|skirt|sweater/.test(t)) return 'fashion_streetwear'; | |
| 23 | + return 'luxury_handbags'; | |
| 24 | +} | |
| 25 | + | |
| 26 | +/** "Great | Item # 417505/4 / Blue" → { grade: 'Great', color: 'Blue' } */ | |
| 27 | +export function parseVariantTitle(title: string): { grade: string | null; color: string | null } { | |
| 28 | + const parts = title.split('|').map((s) => s.trim()); | |
| 29 | + const grade = parts[0] && !/item/i.test(parts[0]) ? parts[0] : null; | |
| 30 | + const colorPart = parts[parts.length - 1] ?? ''; | |
| 31 | + const color = colorPart.includes('/') ? (colorPart.split('/').pop()?.trim() ?? null) : null; | |
| 32 | + return { grade, color: color || null }; | |
| 33 | +} | |
| 34 | + | |
| 35 | +export function retailFromBody(body: string | null | undefined): number | null { | |
| 36 | + const m = (body ?? '').replace(/<[^>]+>/g, ' ').match(/Estimated Retail Price:\s*\$?\s*([0-9,]+(?:\.\d+)?)/i); | |
| 37 | + return m ? moneyNumber(m[1]) : null; | |
| 38 | +} | |
| 39 | + | |
| 40 | +export class RebagConnector extends BaseConnector { | |
| 41 | + readonly version = '1.0.0'; | |
| 42 | + readonly parserVersion = PARSER_VERSION; | |
| 43 | + protected override minIntervalMs = 1500; | |
| 44 | + override readonly urlPatterns = [/^https?:\/\/(shop\.|www\.)?rebag\.com\/products\/([a-z0-9-]+)/i]; | |
| 45 | + | |
| 46 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 47 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 48 | + const pages = ctx.options.mode === 'backfill' ? 20 : Number(this.meta.config.pagesPerSeed ?? 2); | |
| 49 | + let count = 0; | |
| 50 | + for (const seed of seeds) { | |
| 51 | + for (let page = 1; page <= pages; page++) { | |
| 52 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 53 | + await this.throttle(); | |
| 54 | + const { products, res } = await fetchShopifyPage(ctx, BASE, `/collections/${seed}/products.json`, page); | |
| 55 | + if (!res.success) { | |
| 56 | + ctx.anomaly('page_fetch_failed', `${seed} p${page}: ${res.error ?? res.httpStatus}`); | |
| 57 | + break; | |
| 58 | + } | |
| 59 | + if (products.length === 0) break; | |
| 60 | + count++; | |
| 61 | + const payload: PagePayload = { kind: 'shopify_page', url: `${BASE}/collections/${seed}/products.json?page=${page}`, seed, page, products: products.map(trimShopifyProduct) }; | |
| 62 | + yield { url: payload.url, externalId: `collection:${seed}:${page}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 63 | + if (products.length < 250) break; | |
| 64 | + } | |
| 65 | + } | |
| 66 | + } | |
| 67 | + | |
| 68 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 69 | + const handle = url.match(this.urlPatterns[0]!)?.[2]; | |
| 70 | + if (!handle) return []; | |
| 71 | + await this.throttle(); | |
| 72 | + const res = await ctx.fetch(`${BASE}/products/${handle}.json`, { engines: ['api'], responseType: 'json', minQuality: 0.2 }); | |
| 73 | + const parsed = z.object({ product: ShopifyProductSchema }).safeParse(res.json); | |
| 74 | + if (!res.success || !parsed.success) return []; | |
| 75 | + const payload: PagePayload = { kind: 'shopify_page', url: `${BASE}/products/${handle}`, seed: `product:${handle}`, page: 1, products: [trimShopifyProduct(parsed.data.product)] }; | |
| 76 | + return [{ url: payload.url, externalId: `product:${handle}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }]; | |
| 77 | + } | |
| 78 | + | |
| 79 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 80 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 81 | + const out: NormalizedRecord[] = []; | |
| 82 | + for (const pr of p.products) { | |
| 83 | + const v = pr.variants[0]; | |
| 84 | + if (!v) continue; | |
| 85 | + const price = moneyNumber(v.price); | |
| 86 | + if (!price) continue; | |
| 87 | + const brand = pr.vendor ?? null; | |
| 88 | + const tags = pr.tags ?? []; | |
| 89 | + const categorySlug = categoryFor(pr.product_type, brand, tags); | |
| 90 | + const isWatch = ['rolex', 'patek_philippe', 'audemars_piguet', 'omega', 'other_watches'].includes(categorySlug); | |
| 91 | + const title = pr.title.replace(/\s+/g, ' ').trim(); | |
| 92 | + const { grade, color } = parseVariantTitle(v.title); | |
| 93 | + const conditionRaw = grade; | |
| 94 | + const condition = grade ? (CONDITION_MAP[grade.toLowerCase()] ?? normalizeCondition(categorySlug, grade)) : null; | |
| 95 | + const ref = isWatch ? watchReferenceFromText(`${title} ${pr.body_html ?? ''}`) : null; | |
| 96 | + const retail = retailFromBody(pr.body_html); | |
| 97 | + const attributes = AssetAttributesSchema.parse({ | |
| 98 | + categorySlug, | |
| 99 | + brand, | |
| 100 | + name: brand ? `${brand} ${title}` : title, | |
| 101 | + model: isWatch ? null : bagModel(title), | |
| 102 | + reference: ref, | |
| 103 | + material: isWatch ? watchMaterial(title) : bagMaterial(title), | |
| 104 | + size: isWatch ? caseSize(title) : bagSize(title), | |
| 105 | + color, | |
| 106 | + originalMsrp: retail, | |
| 107 | + originalMsrpCurrency: retail ? 'USD' : null, | |
| 108 | + identifiers: { rebag_item: v.sku ?? String(pr.id), ...(ref ? { reference: ref } : {}) }, | |
| 109 | + metadata: { product_type: pr.product_type, hardware: bagHardware(title), filters: tags.filter((t) => t.startsWith('bc-filter-')).map((t) => t.slice(10)).slice(0, 15) }, | |
| 110 | + }); | |
| 111 | + const accessories = (pr.body_html ?? '').match(/Accessories:\s*([^.]{0,80})/i)?.[1]?.trim() ?? null; | |
| 112 | + const completeness = accessories ? (/no accessories/i.test(accessories) ? 'item_only' : /box/i.test(accessories) && /dust ?bag|card|receipt/i.test(accessories) ? 'full_set' : /dust ?bag/i.test(accessories) ? 'dust_bag' : /box/i.test(accessories) ? 'box' : null) : null; | |
| 113 | + out.push( | |
| 114 | + NormalizedListingSchema.parse({ | |
| 115 | + kind: 'listing', | |
| 116 | + connectorId: this.meta.id, | |
| 117 | + sourceId: this.meta.sourceId, | |
| 118 | + sourceUrl: `${BASE}/products/${pr.handle}`, | |
| 119 | + externalId: String(pr.id), | |
| 120 | + rawTitle: title, | |
| 121 | + description: pr.body_html ?? null, | |
| 122 | + imageUrls: (pr.images ?? []).map((i) => i.src), | |
| 123 | + attributes, | |
| 124 | + condition: { condition, conditionRaw, completeness }, | |
| 125 | + observedAt: raw.fetchedAt, | |
| 126 | + confidence: 0.85, | |
| 127 | + parserVersion: PARSER_VERSION, | |
| 128 | + listingType: 'fixed_price', | |
| 129 | + price, | |
| 130 | + currency: 'USD', | |
| 131 | + seller: 'Rebag', | |
| 132 | + location: 'US', | |
| 133 | + quantity: 1, | |
| 134 | + listedAt: pr.published_at ? new Date(pr.published_at) : null, | |
| 135 | + availability: v.available ? 'available' : 'sold', | |
| 136 | + }), | |
| 137 | + ); | |
| 138 | + } | |
| 139 | + return out; | |
| 140 | + } | |
| 141 | +} | |
| 142 | + | |
| 143 | +export default function createConnector(meta: ConnectorMeta) { | |
| 144 | + return new RebagConnector(meta); | |
| 145 | +} | |
added
connectors/api/rebag/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "rebag", | |
| 3 | + "displayName": "Rebag (pre-owned luxury bags, watches & jewelry listings)", | |
| 4 | + "sourceId": "rebag", | |
| 5 | + "sourceName": "Rebag", | |
| 6 | + "sourceType": "marketplace", | |
| 7 | + "sourceUrl": "https://shop.rebag.com", | |
| 8 | + "module": "api/rebag", | |
| 9 | + "enginePriority": ["api"], | |
| 10 | + "categories": ["luxury_handbags", "watches", "rolex", "omega", "other_watches", "jewelry", "fashion_streetwear"], | |
| 11 | + "regions": ["US"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["USD"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": false, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 720, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.8, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.rebag.com/terms-of-service/", | |
| 26 | + "accessNotes": "Public Shopify storefront feed (shop.rebag.com/collections/<handle>/products.json) over plain HTTPS with the RareIndex user agent; rebag.com robots.txt disallows nothing relevant (only /digital_certificate/). Each product is one authenticated pre-owned item in USD; Rebag's condition grade (Pristine/Excellent/Great/Good/Fair) is the first token of the variant title and the estimated retail price comes from the description. 1.5 s between requests.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["hermes", "chanel", "louis-vuitton", "dior", "gucci", "goyard", "fendi", "bottega-veneta", "prada", "celine", "loewe", "saint-laurent", "all-watches", "rolex", "omega", "cartier", "van-cleef-arpels"], | |
| 31 | + "pagesPerSeed": 2 | |
| 32 | + } | |
| 33 | +} | |
added
connectors/api/subdial/index.test.ts
+27 −0
@@ -0,0 +1,27 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('subdial', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into listing records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('subdial'); | |
| 15 | + const fx = loadFixture('subdial', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const l = out[0]; | |
| 19 | + if (l?.kind !== 'listing') throw new Error('no listing'); | |
| 20 | + expect(l.currency).toBe('GBP'); | |
| 21 | + expect(l.attributes.reference).toBeTruthy(); | |
| 22 | + expect(l.attributes.identifiers.subdial_id).toMatch(/^SD\d+/); | |
| 23 | + if (l.attributes.model) expect(l.attributes.model).not.toMatch(/box|papers|\b(19|20)\d{2}\b/i); | |
| 24 | + expect(l.seller).toBe('Subdial'); | |
| 25 | + }); | |
| 26 | + | |
| 27 | +}); | |
added
connectors/api/subdial/index.ts
+160 −0
@@ -0,0 +1,160 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { normalizeCondition } from '@rareindex/taxonomy'; | |
| 4 | +import { AssetAttributesSchema, NormalizedListingSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 5 | +import { caseSize, currencyOr, moneyNumber, watchCategory, watchMaterial, watchReferenceFromText } from '../_luxury-lib/index.js'; | |
| 6 | + | |
| 7 | +/** Subdial — sitemap-driven listing pages with schema.org Product + spec table (GBP). */ | |
| 8 | +const BASE = 'https://subdial.com'; | |
| 9 | +const PARSER_VERSION = '1.0.0'; | |
| 10 | + | |
| 11 | +export const ListingPayloadSchema = z.object({ | |
| 12 | + kind: z.literal('listing_page'), | |
| 13 | + url: z.string(), | |
| 14 | + name: z.string(), | |
| 15 | + brand: z.string().nullable(), | |
| 16 | + mpn: z.string().nullable(), | |
| 17 | + sku: z.string().nullable(), | |
| 18 | + price: z.number(), | |
| 19 | + currency: z.string(), | |
| 20 | + availability: z.string().nullable(), | |
| 21 | + images: z.array(z.string()), | |
| 22 | + description: z.string().nullable(), | |
| 23 | + specs: z.record(z.string(), z.string()), | |
| 24 | +}); | |
| 25 | +export type ListingPayload = z.infer<typeof ListingPayloadSchema>; | |
| 26 | + | |
| 27 | +export function parseListingPage(htmlText: string, url: string): ListingPayload | null { | |
| 28 | + const prod = H.jsonLd(htmlText, 'Product')[0]; | |
| 29 | + if (!prod) return null; | |
| 30 | + const offers = (prod.offers as Record<string, unknown> | undefined) ?? {}; | |
| 31 | + const price = moneyNumber(offers.price as string | number | undefined); | |
| 32 | + if (!price) return null; | |
| 33 | + const specs: Record<string, string> = {}; | |
| 34 | + for (const m of htmlText.matchAll(/(Reference|Year|Box|Papers|Condition|Movement|Case size|Case material|Dial|Bracelet|Diameter)[^<]{0,3}<\/[^>]+>\s*<[^>]+>([^<]{1,60})</g)) { | |
| 35 | + const k = m[1]!.toLowerCase(); | |
| 36 | + const v = m[2]!.replace(/\s+/g, ' ').trim(); | |
| 37 | + if (v && !(k in specs)) specs[k] = v; | |
| 38 | + } | |
| 39 | + const brand = prod.brand && typeof prod.brand === 'object' ? String((prod.brand as { name?: string }).name ?? '') : prod.brand ? String(prod.brand) : null; | |
| 40 | + const img = prod.image; | |
| 41 | + return { | |
| 42 | + kind: 'listing_page', | |
| 43 | + url, | |
| 44 | + name: String(prod.name ?? '').replace(/\s+/g, ' ').trim(), | |
| 45 | + brand: brand || null, | |
| 46 | + mpn: prod.mpn ? String(prod.mpn) : null, | |
| 47 | + sku: prod.sku ? String(prod.sku) : null, | |
| 48 | + price, | |
| 49 | + currency: String(offers.priceCurrency ?? 'GBP'), | |
| 50 | + availability: offers.availability ? String(offers.availability).replace(/^.*\//, '') : null, | |
| 51 | + images: Array.isArray(img) ? img.slice(0, 3).map(String) : typeof img === 'string' ? [img] : [], | |
| 52 | + description: prod.description ? String(prod.description).slice(0, 600) : null, | |
| 53 | + specs, | |
| 54 | + }; | |
| 55 | +} | |
| 56 | + | |
| 57 | +export class SubdialConnector extends BaseConnector { | |
| 58 | + readonly version = '1.0.0'; | |
| 59 | + readonly parserVersion = PARSER_VERSION; | |
| 60 | + protected override minIntervalMs = 1500; | |
| 61 | + override readonly urlPatterns = [/^https?:\/\/(www\.)?subdial\.com\/listing\/[a-z0-9-]+/i]; | |
| 62 | + | |
| 63 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 64 | + const max = ctx.options.limit ?? Number(this.meta.config.maxListingsPerRun ?? 250); | |
| 65 | + const seeds = ctx.options.seeds?.length ? ctx.options.seeds : await this.listingUrls(ctx); | |
| 66 | + let count = 0; | |
| 67 | + for (const url of seeds) { | |
| 68 | + if (ctx.signal?.aborted || count >= max) return; | |
| 69 | + if (!(await ctx.shouldFetch(url))) continue; | |
| 70 | + const rec = await this.fetchListing(url, ctx); | |
| 71 | + if (rec) { | |
| 72 | + count++; | |
| 73 | + yield rec; | |
| 74 | + } | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | + private async listingUrls(ctx: CrawlContext): Promise<string[]> { | |
| 79 | + const res = await ctx.fetch(`${BASE}/sitemap-listing.xml`, { engines: ['api'], responseType: 'text', minQuality: 0 }); | |
| 80 | + if (!res.success || !res.html) { | |
| 81 | + ctx.anomaly('page_fetch_failed', `sitemap: ${res.error ?? res.httpStatus}`); | |
| 82 | + return []; | |
| 83 | + } | |
| 84 | + const urls = [...res.html.matchAll(/<loc>\s*(https?:\/\/[^<\s]+\/listing\/[^<\s]+)\s*<\/loc>/g)].map((m) => m[1]!); | |
| 85 | + // newest listings tend to have the highest SD numbers; crawl those first | |
| 86 | + return urls.sort((a, b) => (b.match(/sd(\d+)$/i)?.[1] ?? '0').localeCompare(a.match(/sd(\d+)$/i)?.[1] ?? '0', undefined, { numeric: true })); | |
| 87 | + } | |
| 88 | + | |
| 89 | + private async fetchListing(url: string, ctx: CrawlContext): Promise<RawRecordInput | null> { | |
| 90 | + await this.throttle(); | |
| 91 | + const res = await ctx.fetch(url, { responseType: 'text', expect: ['title', 'price', 'identifiers'], parse: (r) => { | |
| 92 | + const p = r.html ? parseListingPage(r.html, url) : null; | |
| 93 | + return p ? { title: p.name, price: p.price, identifiers: p.mpn ? { mpn: p.mpn } : null } : null; | |
| 94 | + } }); | |
| 95 | + if (res.httpStatus === 404 || res.httpStatus === 410) return null; | |
| 96 | + const payload = res.success && res.html ? parseListingPage(res.html, url) : null; | |
| 97 | + if (!payload) { | |
| 98 | + ctx.anomaly(res.success ? 'parse_failure' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 99 | + return null; | |
| 100 | + } | |
| 101 | + return { url, externalId: payload.sku ?? url, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 102 | + } | |
| 103 | + | |
| 104 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 105 | + const rec = await this.fetchListing(url.split('?')[0]!, ctx); | |
| 106 | + return rec ? [rec] : []; | |
| 107 | + } | |
| 108 | + | |
| 109 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 110 | + const p = ListingPayloadSchema.parse(raw.payload); | |
| 111 | + const brand = p.brand ?? p.name.split(' ')[0]!; | |
| 112 | + const categorySlug = watchCategory(brand); | |
| 113 | + const reference = p.mpn ?? p.specs.reference ?? watchReferenceFromText(p.name); | |
| 114 | + const model = p.name.replace(new RegExp(`^${brand.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s+`, 'i'), '').replace(/\b(original\s+)?(box\s*(&|and)\s*papers|box only|papers only|full set)\b/gi, '').replace(reference ? reference.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : /$^/, '').replace(/\b(19|20)\d{2}\b/g, '').replace(/\bRef\.?\b/gi, '').replace(/\s+/g, ' ').trim() || null; | |
| 115 | + const yearStr = p.specs.year ?? p.description?.match(/·\s*(\d{4})\s*·/)?.[1]; | |
| 116 | + const conditionRaw = p.specs.condition ?? null; | |
| 117 | + const box = /yes|included|original/i.test(p.specs.box ?? ''); | |
| 118 | + const papers = /yes|included|original|\d{4}/i.test(p.specs.papers ?? ''); | |
| 119 | + const completeness = box && papers ? 'full_set' : papers ? 'papers_only' : box ? 'box_only' : p.specs.box || p.specs.papers ? 'watch_only' : null; | |
| 120 | + const attributes = AssetAttributesSchema.parse({ | |
| 121 | + categorySlug, | |
| 122 | + brand, | |
| 123 | + name: `${brand} ${model ?? ''}`.trim(), | |
| 124 | + model, | |
| 125 | + reference, | |
| 126 | + year: yearStr ? Number(yearStr) : null, | |
| 127 | + material: watchMaterial(`${p.name} ${p.specs['case material'] ?? ''}`), | |
| 128 | + size: caseSize(`${p.name} ${p.specs.diameter ?? p.specs['case size'] ?? ''}`), | |
| 129 | + identifiers: { ...(reference ? { reference } : {}), subdial_id: p.sku ?? p.url }, | |
| 130 | + metadata: { specs: p.specs }, | |
| 131 | + }); | |
| 132 | + return [ | |
| 133 | + NormalizedListingSchema.parse({ | |
| 134 | + kind: 'listing', | |
| 135 | + connectorId: this.meta.id, | |
| 136 | + sourceId: this.meta.sourceId, | |
| 137 | + sourceUrl: p.url, | |
| 138 | + externalId: p.sku ?? p.url, | |
| 139 | + rawTitle: `${p.name}${reference ? ` Ref. ${reference}` : ''}${yearStr ? ` (${yearStr})` : ''}`, | |
| 140 | + description: p.description, | |
| 141 | + imageUrls: p.images, | |
| 142 | + attributes, | |
| 143 | + condition: { condition: normalizeCondition(categorySlug, conditionRaw), conditionRaw, completeness }, | |
| 144 | + observedAt: raw.fetchedAt, | |
| 145 | + confidence: 0.9, | |
| 146 | + parserVersion: PARSER_VERSION, | |
| 147 | + listingType: 'fixed_price', | |
| 148 | + price: p.price, | |
| 149 | + currency: currencyOr(p.currency, 'GBP'), | |
| 150 | + seller: 'Subdial', | |
| 151 | + location: 'London, GB', | |
| 152 | + availability: p.availability === 'InStock' ? 'available' : p.availability ? 'sold' : 'unknown', | |
| 153 | + }), | |
| 154 | + ]; | |
| 155 | + } | |
| 156 | +} | |
| 157 | + | |
| 158 | +export default function createConnector(meta: ConnectorMeta) { | |
| 159 | + return new SubdialConnector(meta); | |
| 160 | +} | |
added
connectors/api/subdial/meta.json
+32 −0
@@ -0,0 +1,32 @@ | ||
| 1 | +{ | |
| 2 | + "id": "subdial", | |
| 3 | + "displayName": "Subdial (UK pre-owned watch listings)", | |
| 4 | + "sourceId": "subdial", | |
| 5 | + "sourceName": "Subdial", | |
| 6 | + "sourceType": "marketplace", | |
| 7 | + "sourceUrl": "https://subdial.com", | |
| 8 | + "module": "api/subdial", | |
| 9 | + "enginePriority": ["api", "firecrawl"], | |
| 10 | + "categories": ["watches", "rolex", "patek_philippe", "audemars_piguet", "omega", "other_watches"], | |
| 11 | + "regions": ["GB"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["GBP"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": false, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 720, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.8, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://subdial.com/terms", | |
| 26 | + "accessNotes": "Listing URLs come from the public sitemap (subdial.com/sitemap-listing.xml); each listing page embeds a schema.org Product (brand, mpn = reference, sku = Subdial id, GBP price, availability) plus a specification table (reference, year, condition, box, papers). robots.txt allows / for generic agents (it blocks a list of named AI crawlers, which we are not). Plain HTTPS with the RareIndex user agent, 1.5 s between pages, capped per run; the crawl budget skips URLs already fetched recently.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "maxListingsPerRun": 250 | |
| 31 | + } | |
| 32 | +} | |
added
connectors/firecrawl/flight-club/index.test.ts
+26 −0
@@ -0,0 +1,26 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('flight-club', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into catalog_item records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('flight-club'); | |
| 15 | + const fx = loadFixture('flight-club', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const cat = out.find((r) => r.kind === 'catalog_item'); | |
| 19 | + if (cat?.kind !== 'catalog_item') throw new Error('no catalog item'); | |
| 20 | + expect(cat.attributes.identifiers.style_code).toBeTruthy(); | |
| 21 | + expect(cat.attributes.identifiers.flightclub_id).toMatch(/^\d+$/); | |
| 22 | + const ask = out.find((r) => r.kind === 'listing'); | |
| 23 | + if (ask?.kind === 'listing') { expect(ask.currency).toBe('USD'); expect(ask.listingType).toBe('ask'); } | |
| 24 | + }); | |
| 25 | + | |
| 26 | +}); | |
added
connectors/firecrawl/flight-club/index.ts
+96 −0
@@ -0,0 +1,96 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { AssetAttributesSchema, NormalizedCatalogItemSchema, NormalizedListingSchema, NormalizedPriceObservationSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 4 | +import { fetchRawHtml, sneakerCategory, styleCodeFromText } from '../../api/_luxury-lib/index.js'; | |
| 5 | + | |
| 6 | +/** Flight Club — search grid from __NEXT_DATA__ (lowest ask + retail, USD). */ | |
| 7 | +const BASE = 'https://www.flightclub.com'; | |
| 8 | +const PARSER_VERSION = '1.0.0'; | |
| 9 | + | |
| 10 | +export const ItemSchema = z.object({ id: z.string(), name: z.string(), brand: z.string().nullable(), image: z.string().nullable(), price: z.number().nullable(), retail: z.number().nullable(), slug: z.string() }); | |
| 11 | +export const PagePayloadSchema = z.object({ kind: z.literal('search_page'), url: z.string(), seed: z.string(), page: z.number(), currency: z.string(), total: z.number().nullable(), items: z.array(ItemSchema) }); | |
| 12 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 13 | + | |
| 14 | +export function parseSearchPage(htmlText: string, url: string, seed: string, page: number): PagePayload | null { | |
| 15 | + const nd = H.nextData(htmlText) as { props?: { pageProps?: { consumerSearchResult?: { data?: Array<Record<string, unknown>>; total?: number }; currency?: string; availableCurrencies?: unknown[] } } } | null; | |
| 16 | + const r = nd?.props?.pageProps?.consumerSearchResult; | |
| 17 | + if (!r?.data) return null; | |
| 18 | + const items: z.infer<typeof ItemSchema>[] = []; | |
| 19 | + for (const d of r.data) { | |
| 20 | + const id = d.id !== undefined ? String(d.id) : null; | |
| 21 | + const slug = typeof d.slug === 'string' ? d.slug : null; | |
| 22 | + if (!id || !slug) continue; | |
| 23 | + const priceObj = d.price as { localizedValue?: number } | undefined; | |
| 24 | + const retailObj = d.retailPrice as { localizedValue?: number } | undefined; | |
| 25 | + items.push({ id, name: String(d.name ?? '').trim(), brand: d.brandName ? String(d.brandName) : null, image: d.pictureUrl ? String(d.pictureUrl) : null, price: typeof priceObj?.localizedValue === 'number' && priceObj.localizedValue > 0 ? priceObj.localizedValue : null, retail: typeof retailObj?.localizedValue === 'number' && retailObj.localizedValue > 0 ? retailObj.localizedValue : null, slug }); | |
| 26 | + } | |
| 27 | + return { kind: 'search_page', url, seed, page, currency: String(nd?.props?.pageProps?.currency ?? 'USD'), total: typeof r.total === 'number' ? r.total : null, items }; | |
| 28 | +} | |
| 29 | + | |
| 30 | +export class FlightClubConnector extends BaseConnector { | |
| 31 | + readonly version = '1.0.0'; | |
| 32 | + readonly parserVersion = PARSER_VERSION; | |
| 33 | + protected override minIntervalMs = 2000; | |
| 34 | + | |
| 35 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 36 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 37 | + const pages = ctx.options.mode === 'backfill' ? 10 : Number(this.meta.config.pagesPerSeed ?? 2); | |
| 38 | + let count = 0; | |
| 39 | + for (const seed of seeds) { | |
| 40 | + for (let page = 1; page <= pages; page++) { | |
| 41 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 42 | + const url = `${BASE}/${seed}${page > 1 ? `?page=${page}` : ''}`; | |
| 43 | + await this.throttle(); | |
| 44 | + // __NEXT_DATA__ lives in a <script>; the shared engine's `html` format strips scripts, so ask Firecrawl for rawHtml. | |
| 45 | + let res = await fetchRawHtml(ctx, url); | |
| 46 | + let payload = res.success && res.html ? parseSearchPage(res.html, url, seed, page) : null; | |
| 47 | + if (!payload) { | |
| 48 | + res = await ctx.fetch(url, { engines: ['scrapfly'], expect: ['title', 'price', 'identifiers'], parse: (r) => { | |
| 49 | + const first = r.html ? parseSearchPage(r.html, url, seed, page)?.items[0] : undefined; | |
| 50 | + return first ? { title: first.name, price: first.price, identifiers: { slug: first.slug } } : null; | |
| 51 | + } }); | |
| 52 | + payload = res.success && res.html ? parseSearchPage(res.html, url, seed, page) : null; | |
| 53 | + } | |
| 54 | + if (!payload || payload.items.length === 0) { | |
| 55 | + if (page === 1) ctx.anomaly(payload ? 'empty_page' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 56 | + break; | |
| 57 | + } | |
| 58 | + count++; | |
| 59 | + yield { url, externalId: `search:${seed}:${page}`, kind: 'catalog_item', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 60 | + } | |
| 61 | + } | |
| 62 | + } | |
| 63 | + | |
| 64 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 65 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 66 | + const out: NormalizedRecord[] = []; | |
| 67 | + const currency = p.currency === 'USD' ? 'USD' : 'USD'; | |
| 68 | + for (const it of p.items) { | |
| 69 | + const styleCode = styleCodeFromText(it.slug.split('-').slice(-2).join('-')) ?? styleCodeFromText(it.slug); | |
| 70 | + const year = it.name.match(/\b(20\d{2}|19\d{2})\b\s*$/)?.[1]; | |
| 71 | + const attributes = AssetAttributesSchema.parse({ | |
| 72 | + categorySlug: sneakerCategory(it.brand, it.name), | |
| 73 | + brand: it.brand, | |
| 74 | + name: it.name.replace(/\s+\d{4}$/, '').trim(), | |
| 75 | + year: year ? Number(year) : null, | |
| 76 | + originalMsrp: it.retail, | |
| 77 | + originalMsrpCurrency: it.retail ? currency : null, | |
| 78 | + identifiers: { ...(styleCode ? { style_code: styleCode } : {}), flightclub_id: it.id }, | |
| 79 | + metadata: { slug: it.slug }, | |
| 80 | + }); | |
| 81 | + const sourceUrl = `${BASE}/${it.slug}`; | |
| 82 | + const base = { connectorId: this.meta.id, sourceId: this.meta.sourceId, sourceUrl, rawTitle: it.name, imageUrls: it.image ? [it.image] : [], attributes, observedAt: raw.fetchedAt, parserVersion: PARSER_VERSION }; | |
| 83 | + const cond = { condition: 'new', conditionRaw: 'New (Flight Club consignment)', completeness: 'with_box' }; | |
| 84 | + out.push(NormalizedCatalogItemSchema.parse({ kind: 'catalog_item', ...base, externalId: `product:${it.id}`, confidence: 0.85 })); | |
| 85 | + if (it.price) { | |
| 86 | + out.push(NormalizedPriceObservationSchema.parse({ kind: 'price_observation', ...base, externalId: `product:${it.id}:low`, confidence: 0.7, condition: cond, priceKind: 'low', price: it.price, currency, observationDate: raw.fetchedAt })); | |
| 87 | + out.push(NormalizedListingSchema.parse({ kind: 'listing', ...base, externalId: `product:${it.id}:lowest_ask`, confidence: 0.7, condition: cond, listingType: 'ask', price: it.price, currency, seller: 'Flight Club', location: 'US', availability: 'available' })); | |
| 88 | + } | |
| 89 | + } | |
| 90 | + return out; | |
| 91 | + } | |
| 92 | +} | |
| 93 | + | |
| 94 | +export default function createConnector(meta: ConnectorMeta) { | |
| 95 | + return new FlightClubConnector(meta); | |
| 96 | +} | |
added
connectors/firecrawl/flight-club/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "flight-club", | |
| 3 | + "displayName": "Flight Club (sneaker lowest asks & retail)", | |
| 4 | + "sourceId": "flight-club", | |
| 5 | + "sourceName": "Flight Club", | |
| 6 | + "sourceType": "marketplace", | |
| 7 | + "sourceUrl": "https://www.flightclub.com", | |
| 8 | + "module": "firecrawl/flight-club", | |
| 9 | + "enginePriority": ["firecrawl", "scrapfly"], | |
| 10 | + "categories": ["sneakers", "nike_jordan", "adidas_yeezy", "new_balance_asics_other"], | |
| 11 | + "regions": ["US"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["USD"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": true, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": false, | |
| 21 | + "refreshFrequencyMinutes": 1440, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.75, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.flightclub.com/terms", | |
| 26 | + "accessNotes": "Plain HTTPS requests receive 403 (robots.txt itself is not served to non-browser agents); Firecrawl's standard fetch returns the public category pages (flightclub.com/air-jordans?page=N …) whose __NEXT_DATA__ holds the search grid: product id, name, brand, image, lowest price, retail price and a slug ending with the style code. 1 Firecrawl credit per page of 30 products; 2 s between pages. Listings/asks only — Flight Club does not publish sale history publicly.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["air-jordans", "nike", "adidas/adidas-yeezy", "adidas", "new-balance", "asics"], | |
| 31 | + "pagesPerSeed": 2 | |
| 32 | + } | |
| 33 | +} | |
added
connectors/firecrawl/hypeboost/index.test.ts
+26 −0
@@ -0,0 +1,26 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('hypeboost', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into catalog_item records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('hypeboost'); | |
| 15 | + const fx = loadFixture('hypeboost', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const ask = out.find((r) => r.kind === 'listing'); | |
| 19 | + if (ask?.kind !== 'listing') throw new Error('no listing'); | |
| 20 | + expect(ask.currency).toBe('EUR'); | |
| 21 | + expect(ask.price).toBeGreaterThan(20); | |
| 22 | + expect(ask.attributes.identifiers.hypeboost_id).toMatch(/^\d+$/); | |
| 23 | + expect(ask.sourceUrl).toMatch(/hypeboost\.com\/en\/product\//); | |
| 24 | + }); | |
| 25 | + | |
| 26 | +}); | |
added
connectors/firecrawl/hypeboost/index.ts
+106 −0
@@ -0,0 +1,106 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { AssetAttributesSchema, NormalizedCatalogItemSchema, NormalizedListingSchema, parsePrice, type NormalizedRecord } from '@rareindex/shared'; | |
| 4 | +import { sneakerCategory, styleCodeFromText } from '../../api/_luxury-lib/index.js'; | |
| 5 | + | |
| 6 | +/** Hypeboost — category grid (EUR lowest price) + product-page lookup (style code from JSON-LD). */ | |
| 7 | +const BASE = 'https://hypeboost.com'; | |
| 8 | +const PARSER_VERSION = '1.0.0'; | |
| 9 | + | |
| 10 | +export const ItemSchema = z.object({ id: z.string(), name: z.string(), brand: z.string().nullable(), category: z.string().nullable(), url: z.string(), image: z.string().nullable(), price: z.number(), currency: z.string(), sku: z.string().nullable() }); | |
| 11 | +export const PagePayloadSchema = z.object({ kind: z.literal('grid_page'), url: z.string(), seed: z.string(), page: z.number(), total: z.number().nullable(), items: z.array(ItemSchema) }); | |
| 12 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 13 | + | |
| 14 | +export function parseGridPage(htmlText: string, url: string, seed: string, page: number): PagePayload { | |
| 15 | + const $ = H.load(htmlText); | |
| 16 | + const items: z.infer<typeof ItemSchema>[] = []; | |
| 17 | + $('.grid_item').each((_, el) => { | |
| 18 | + const a = $(el).find('a[href*="/product/"]').first(); | |
| 19 | + const href = a.attr('href'); | |
| 20 | + const name = $(el).find('h3').first().text().replace(/\s+/g, ' ').trim(); | |
| 21 | + const priceText = $(el).find('.new_price').first().text(); | |
| 22 | + const parsed = parsePrice(priceText, 'EUR'); | |
| 23 | + const meta = $(el).find('[data-item-id]').first(); | |
| 24 | + if (!href || !name || !parsed) return; | |
| 25 | + items.push({ id: meta.attr('data-item-id') ?? href, name, brand: meta.attr('data-item_brand') ?? null, category: meta.attr('data-item_category') ?? null, url: href.startsWith('http') ? href : `${BASE}${href}`, image: $(el).find('img').first().attr('data-src') ?? $(el).find('img').first().attr('src') ?? null, price: parsed.amount, currency: parsed.currency ?? 'EUR', sku: null }); | |
| 26 | + }); | |
| 27 | + const total = htmlText.match(/of\s+([\d,.]+)\s+results/)?.[1]; | |
| 28 | + return { kind: 'grid_page', url, seed, page, total: total ? Number(total.replace(/[,.]/g, '')) : null, items }; | |
| 29 | +} | |
| 30 | + | |
| 31 | +export function parseProductPage(htmlText: string, url: string): PagePayload | null { | |
| 32 | + const prod = H.jsonLd(htmlText, 'Product')[0]; | |
| 33 | + if (!prod) return null; | |
| 34 | + const offers = (prod.offers as Record<string, unknown> | undefined) ?? {}; | |
| 35 | + const price = Number(offers.price ?? (offers as { lowPrice?: unknown }).lowPrice); | |
| 36 | + if (!Number.isFinite(price) || price <= 0) return null; | |
| 37 | + const img = prod.image; | |
| 38 | + const brand = prod.brand && typeof prod.brand === 'object' ? String((prod.brand as { name?: string }).name ?? '') : null; | |
| 39 | + return { kind: 'grid_page', url, seed: 'lookup', page: 1, total: null, items: [{ id: url.replace(/^.*\/product\//, ''), name: String(prod.name ?? ''), brand: brand || null, category: null, url, image: Array.isArray(img) ? String(img[0] ?? '') || null : typeof img === 'string' ? img : null, price, currency: String(offers.priceCurrency ?? 'EUR'), sku: prod.sku ? String(prod.sku) : null }] }; | |
| 40 | +} | |
| 41 | + | |
| 42 | +export class HypeboostConnector extends BaseConnector { | |
| 43 | + readonly version = '1.0.0'; | |
| 44 | + readonly parserVersion = PARSER_VERSION; | |
| 45 | + protected override minIntervalMs = 2000; | |
| 46 | + override readonly urlPatterns = [/^https?:\/\/(www\.)?hypeboost\.com\/[a-z]{2}\/product\/[a-z0-9-]+/i]; | |
| 47 | + | |
| 48 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 49 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 50 | + const pages = ctx.options.mode === 'backfill' ? 10 : Number(this.meta.config.pagesPerSeed ?? 2); | |
| 51 | + let count = 0; | |
| 52 | + for (const seed of seeds) { | |
| 53 | + let prevIds = ''; | |
| 54 | + for (let page = 1; page <= pages; page++) { | |
| 55 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 56 | + const url = `${BASE}/en/category/sneakers/${seed}${page > 1 ? `?page=${page}` : ''}`; | |
| 57 | + await this.throttle(); | |
| 58 | + const res = await ctx.fetch(url, { expect: ['title', 'price'], parse: (r) => { | |
| 59 | + const first = r.html ? parseGridPage(r.html, url, seed, page).items[0] : undefined; | |
| 60 | + return first ? { title: first.name, price: first.price } : null; | |
| 61 | + } }); | |
| 62 | + const payload = res.success && res.html ? parseGridPage(res.html, url, seed, page) : null; | |
| 63 | + if (!payload || payload.items.length === 0) { | |
| 64 | + if (page === 1) ctx.anomaly(payload ? 'empty_page' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 65 | + break; | |
| 66 | + } | |
| 67 | + const ids = payload.items.map((i) => i.id).join(','); | |
| 68 | + if (ids === prevIds) break; // pagination not honoured → same grid again | |
| 69 | + prevIds = ids; | |
| 70 | + count++; | |
| 71 | + yield { url, externalId: `grid:${seed}:${page}`, kind: 'catalog_item', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 72 | + } | |
| 73 | + } | |
| 74 | + } | |
| 75 | + | |
| 76 | + async lookup(url: string, ctx: CrawlContext): Promise<RawRecordInput[]> { | |
| 77 | + await this.throttle(); | |
| 78 | + const res = await ctx.fetch(url, { minQuality: 0.2 }); | |
| 79 | + const payload = res.success && res.html ? parseProductPage(res.html, url) : null; | |
| 80 | + return payload ? [{ url, externalId: `product:${payload.items[0]!.id}`, kind: 'catalog_item', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }] : []; | |
| 81 | + } | |
| 82 | + | |
| 83 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 84 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 85 | + const out: NormalizedRecord[] = []; | |
| 86 | + for (const it of p.items) { | |
| 87 | + const styleCode = it.sku ?? styleCodeFromText(it.name); | |
| 88 | + const attributes = AssetAttributesSchema.parse({ | |
| 89 | + categorySlug: sneakerCategory(it.brand, it.name), | |
| 90 | + brand: it.brand, | |
| 91 | + series: it.category, | |
| 92 | + name: it.name, | |
| 93 | + identifiers: { ...(styleCode ? { style_code: styleCode } : {}), hypeboost_id: it.id }, | |
| 94 | + }); | |
| 95 | + const base = { connectorId: this.meta.id, sourceId: this.meta.sourceId, sourceUrl: it.url, rawTitle: it.name, imageUrls: it.image ? [it.image] : [], attributes, observedAt: raw.fetchedAt, parserVersion: PARSER_VERSION }; | |
| 96 | + const cond = { condition: 'new', conditionRaw: 'Brand new (marketplace standard)', completeness: 'with_box' }; | |
| 97 | + out.push(NormalizedCatalogItemSchema.parse({ kind: 'catalog_item', ...base, externalId: `product:${it.id}`, confidence: styleCode ? 0.8 : 0.6 })); | |
| 98 | + out.push(NormalizedListingSchema.parse({ kind: 'listing', ...base, externalId: `product:${it.id}:lowest_ask`, confidence: 0.7, condition: cond, listingType: 'ask', price: it.price, currency: it.currency === 'EUR' ? 'EUR' : 'EUR', seller: 'Hypeboost', location: 'NL', availability: 'available' })); | |
| 99 | + } | |
| 100 | + return out; | |
| 101 | + } | |
| 102 | +} | |
| 103 | + | |
| 104 | +export default function createConnector(meta: ConnectorMeta) { | |
| 105 | + return new HypeboostConnector(meta); | |
| 106 | +} | |
added
connectors/firecrawl/hypeboost/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "hypeboost", | |
| 3 | + "displayName": "Hypeboost (EU sneaker marketplace — lowest asks)", | |
| 4 | + "sourceId": "hypeboost", | |
| 5 | + "sourceName": "Hypeboost", | |
| 6 | + "sourceType": "marketplace", | |
| 7 | + "sourceUrl": "https://hypeboost.com", | |
| 8 | + "module": "firecrawl/hypeboost", | |
| 9 | + "enginePriority": ["firecrawl", "scrapfly"], | |
| 10 | + "categories": ["sneakers", "nike_jordan", "adidas_yeezy", "new_balance_asics_other"], | |
| 11 | + "regions": ["NL", "EU"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["EUR"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": true, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": true, | |
| 21 | + "refreshFrequencyMinutes": 1440, | |
| 22 | + "priority": "low", | |
| 23 | + "trustScore": 0.7, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://hypeboost.com/en/terms-and-conditions", | |
| 26 | + "accessNotes": "Plain HTTPS gets 403 (robots.txt not served to non-browser agents); Firecrawl's standard fetch returns the public category grid (hypeboost.com/en/category/sneakers/<brand>[?page=N]) with product name, EUR lowest price, brand/category data attributes and product id (36 per page, 1 credit). Product pages (lookup) expose the style code in schema.org Product sku. 2 s between pages.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["air-jordan", "nike", "adidas", "yeezy", "new-balance", "asics"], | |
| 31 | + "pagesPerSeed": 2 | |
| 32 | + } | |
| 33 | +} | |
added
connectors/firecrawl/watchfinder/index.test.ts
+30 −0
@@ -0,0 +1,30 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { ConnectorMetaSchema } from '@rareindex/connectors'; | |
| 3 | +import metaJson from './meta.json' with { type: 'json' }; | |
| 4 | +import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; | |
| 5 | +import createConnector from './index.js'; | |
| 6 | + | |
| 7 | +// meta.json is read directly so the suite does not depend on connectors/registry.json being regenerated. | |
| 8 | +const connector = createConnector(ConnectorMetaSchema.parse(metaJson)); | |
| 9 | + | |
| 10 | +describe('watchfinder', () => { | |
| 11 | + runFixtureSuite(connector, it, expect); | |
| 12 | + | |
| 13 | + it('normalises the first fixture into listing records with the expected fields', async () => { | |
| 14 | + const [name] = listFixtures('watchfinder'); | |
| 15 | + const fx = loadFixture('watchfinder', name!); | |
| 16 | + const out = await connector.normalize(fx.raw); | |
| 17 | + expect(out.length).toBeGreaterThan(0); | |
| 18 | + const l = out[0]; | |
| 19 | + if (l?.kind !== 'listing') throw new Error('no listing'); | |
| 20 | + expect(l.attributes.categorySlug).toBe('rolex'); | |
| 21 | + expect(l.attributes.reference).toMatch(/^\d{5,6}/); | |
| 22 | + expect(l.currency).toBe('GBP'); | |
| 23 | + expect(l.attributes.identifiers.watchfinder_sku).toMatch(/^\d+$/); | |
| 24 | + const withYear = out.filter((r) => r.kind === 'listing' && r.attributes.year); | |
| 25 | + expect(withYear.length).toBeGreaterThan(out.length / 2); | |
| 26 | + const withSet = out.filter((r) => r.kind === 'listing' && r.condition.completeness); | |
| 27 | + expect(withSet.length).toBeGreaterThan(0); | |
| 28 | + }); | |
| 29 | + | |
| 30 | +}); | |
added
connectors/firecrawl/watchfinder/index.ts
+132 −0
@@ -0,0 +1,132 @@ | ||
| 1 | +import { z } from 'zod'; | |
| 2 | +import { BaseConnector, html as H, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; | |
| 3 | +import { AssetAttributesSchema, NormalizedListingSchema, type NormalizedRecord } from '@rareindex/shared'; | |
| 4 | +import { caseSize, watchCategory, watchMaterial } from '../../api/_luxury-lib/index.js'; | |
| 5 | + | |
| 6 | +/** Watchfinder & Co. — product cards on model pages (GBP asks, box/papers, year). */ | |
| 7 | +const BASE = 'https://www.watchfinder.co.uk'; | |
| 8 | +const PARSER_VERSION = '1.0.0'; | |
| 9 | + | |
| 10 | +export const CardSchema = z.object({ sku: z.string(), brand: z.string(), series: z.string().nullable(), model: z.string().nullable(), url: z.string(), image: z.string().nullable(), name: z.string(), box: z.boolean().nullable(), papers: z.boolean().nullable(), year: z.number().nullable(), price: z.number(), currency: z.string() }); | |
| 11 | +export const PagePayloadSchema = z.object({ kind: z.literal('model_page'), url: z.string(), seed: z.string(), page: z.number(), total: z.number().nullable(), cards: z.array(CardSchema) }); | |
| 12 | +export type PagePayload = z.infer<typeof PagePayloadSchema>; | |
| 13 | + | |
| 14 | +export function parseModelPage(htmlText: string, url: string, seed: string, page: number): PagePayload { | |
| 15 | + const $ = H.load(htmlText); | |
| 16 | + const cards: z.infer<typeof CardSchema>[] = []; | |
| 17 | + $('a.product-card').each((_, el) => { | |
| 18 | + const $el = $(el); | |
| 19 | + const sku = $el.attr('data-product-sku'); | |
| 20 | + const brand = $el.attr('data-product-brand'); | |
| 21 | + const href = $el.attr('href'); | |
| 22 | + const priceAttr = $el.find('[data-price-amount]').first().attr('data-price-amount'); | |
| 23 | + const price = Number(priceAttr); | |
| 24 | + if (!sku || !brand || !href || !Number.isFinite(price) || price <= 0) return; | |
| 25 | + const priceText = $el.find('.price').first().text(); | |
| 26 | + const currency = /£/.test(priceText) ? 'GBP' : /€/.test(priceText) ? 'EUR' : /\$/.test(priceText) ? 'USD' : 'GBP'; | |
| 27 | + const spec = (label: string) => { | |
| 28 | + const item = $el.find('.product-card__specs__box-papers__item').filter((__, e) => $(e).text().trim().startsWith(label)).first(); | |
| 29 | + if (!item.length) return null; | |
| 30 | + return item.find('.icon-yes').length > 0 ? true : item.find('.icon-no').length > 0 ? false : null; | |
| 31 | + }; | |
| 32 | + const yearText = $el.find('.product-card__specs__year-location__item__value').first().text().trim(); | |
| 33 | + cards.push({ | |
| 34 | + sku, | |
| 35 | + brand, | |
| 36 | + series: $el.attr('data-product-series') ?? null, | |
| 37 | + model: $el.attr('data-product-model') ?? null, | |
| 38 | + url: href.startsWith('http') ? href : `${BASE}${href}`, | |
| 39 | + image: $el.attr('data-product-image') ?? null, | |
| 40 | + name: $el.find('meta[itemprop="name"]').attr('content') ?? `${brand} ${$el.attr('data-product-series') ?? ''} ${$el.attr('data-product-model') ?? ''}`.trim(), | |
| 41 | + box: spec('Box'), | |
| 42 | + papers: spec('Papers'), | |
| 43 | + year: /^\d{4}$/.test(yearText) ? Number(yearText) : null, | |
| 44 | + price, | |
| 45 | + currency, | |
| 46 | + }); | |
| 47 | + }); | |
| 48 | + const total = $('meta[itemprop="numberOfItems"]').attr('content'); | |
| 49 | + return { kind: 'model_page', url, seed, page, total: total ? Number(total) : null, cards }; | |
| 50 | +} | |
| 51 | + | |
| 52 | +export class WatchfinderConnector extends BaseConnector { | |
| 53 | + readonly version = '1.0.0'; | |
| 54 | + readonly parserVersion = PARSER_VERSION; | |
| 55 | + protected override minIntervalMs = 2000; | |
| 56 | + | |
| 57 | + async *crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput> { | |
| 58 | + const seeds = (ctx.options.seeds?.length ? ctx.options.seeds : (this.meta.config.seeds as string[] | undefined)) ?? []; | |
| 59 | + const pages = ctx.options.mode === 'backfill' ? 8 : Number(this.meta.config.pagesPerSeed ?? 2); | |
| 60 | + let count = 0; | |
| 61 | + for (const seed of seeds) { | |
| 62 | + for (let page = 1; page <= pages; page++) { | |
| 63 | + if (ctx.signal?.aborted || this.reached(ctx, count)) return; | |
| 64 | + const url = `${BASE}/watches/${seed}${page > 1 ? `?p=${page}` : ''}`; | |
| 65 | + await this.throttle(); | |
| 66 | + const res = await ctx.fetch(url, { expect: ['title', 'price', 'identifiers'], parse: (r) => { | |
| 67 | + const first = r.html ? parseModelPage(r.html, url, seed, page).cards[0] : undefined; | |
| 68 | + return first ? { title: first.name, price: first.price, identifiers: { sku: first.sku } } : null; | |
| 69 | + } }); | |
| 70 | + const payload = res.success && res.html ? parseModelPage(res.html, url, seed, page) : null; | |
| 71 | + if (!payload || payload.cards.length === 0) { | |
| 72 | + if (page === 1) ctx.anomaly(payload ? 'empty_page' : 'page_fetch_failed', `${url}: ${res.error ?? res.httpStatus}`); | |
| 73 | + break; | |
| 74 | + } | |
| 75 | + count++; | |
| 76 | + yield { url, externalId: `model:${seed}:${page}`, kind: 'listing', engine: res.engine, httpStatus: res.httpStatus, payload, fetchedAt: res.fetchedAt }; | |
| 77 | + if (payload.total !== null && page * 24 >= payload.total) break; | |
| 78 | + } | |
| 79 | + } | |
| 80 | + } | |
| 81 | + | |
| 82 | + async normalize(raw: RawRecordLike): Promise<NormalizedRecord[]> { | |
| 83 | + const p = PagePayloadSchema.parse(raw.payload); | |
| 84 | + const out: NormalizedRecord[] = []; | |
| 85 | + const seen = new Set<string>(); | |
| 86 | + for (const c of p.cards) { | |
| 87 | + if (seen.has(c.sku)) continue; | |
| 88 | + seen.add(c.sku); | |
| 89 | + const categorySlug = watchCategory(c.brand); | |
| 90 | + const completeness = c.box && c.papers ? 'full_set' : c.papers ? 'papers_only' : c.box ? 'box_only' : c.box === false && c.papers === false ? 'watch_only' : null; | |
| 91 | + const attributes = AssetAttributesSchema.parse({ | |
| 92 | + categorySlug, | |
| 93 | + brand: c.brand, | |
| 94 | + name: `${c.brand} ${c.series ?? ''}`.trim(), | |
| 95 | + model: c.series, | |
| 96 | + reference: c.model, | |
| 97 | + year: c.year, | |
| 98 | + material: watchMaterial(c.name), | |
| 99 | + size: caseSize(c.name), | |
| 100 | + identifiers: { ...(c.model ? { reference: c.model } : {}), watchfinder_sku: c.sku }, | |
| 101 | + metadata: { model_page: p.url }, | |
| 102 | + }); | |
| 103 | + out.push( | |
| 104 | + NormalizedListingSchema.parse({ | |
| 105 | + kind: 'listing', | |
| 106 | + connectorId: this.meta.id, | |
| 107 | + sourceId: this.meta.sourceId, | |
| 108 | + sourceUrl: c.url, | |
| 109 | + externalId: c.sku, | |
| 110 | + rawTitle: c.name, | |
| 111 | + imageUrls: c.image ? [c.image.replace(/&/g, '&')] : [], | |
| 112 | + attributes, | |
| 113 | + condition: { condition: null, conditionRaw: 'Pre-owned (Watchfinder inspected)', completeness }, | |
| 114 | + observedAt: raw.fetchedAt, | |
| 115 | + confidence: 0.85, | |
| 116 | + parserVersion: PARSER_VERSION, | |
| 117 | + listingType: 'fixed_price', | |
| 118 | + price: c.price, | |
| 119 | + currency: c.currency === 'EUR' ? 'EUR' : c.currency === 'USD' ? 'USD' : 'GBP', | |
| 120 | + seller: 'Watchfinder & Co.', | |
| 121 | + location: 'GB', | |
| 122 | + availability: 'available', | |
| 123 | + }), | |
| 124 | + ); | |
| 125 | + } | |
| 126 | + return out; | |
| 127 | + } | |
| 128 | +} | |
| 129 | + | |
| 130 | +export default function createConnector(meta: ConnectorMeta) { | |
| 131 | + return new WatchfinderConnector(meta); | |
| 132 | +} | |
added
connectors/firecrawl/watchfinder/meta.json
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +{ | |
| 2 | + "id": "watchfinder", | |
| 3 | + "displayName": "Watchfinder & Co. (UK pre-owned watch listings)", | |
| 4 | + "sourceId": "watchfinder", | |
| 5 | + "sourceName": "Watchfinder & Co.", | |
| 6 | + "sourceType": "dealer", | |
| 7 | + "sourceUrl": "https://www.watchfinder.co.uk", | |
| 8 | + "module": "firecrawl/watchfinder", | |
| 9 | + "enginePriority": ["firecrawl", "scrapfly"], | |
| 10 | + "categories": ["watches", "rolex", "patek_philippe", "audemars_piguet", "omega", "other_watches"], | |
| 11 | + "regions": ["GB"], | |
| 12 | + "languages": ["en"], | |
| 13 | + "currency": ["GBP"], | |
| 14 | + "supportsListings": true, | |
| 15 | + "supportsSold": false, | |
| 16 | + "supportsAuctions": false, | |
| 17 | + "supportsImages": true, | |
| 18 | + "supportsCatalog": false, | |
| 19 | + "supportsPopulation": false, | |
| 20 | + "supportsLookup": false, | |
| 21 | + "refreshFrequencyMinutes": 720, | |
| 22 | + "priority": "medium", | |
| 23 | + "trustScore": 0.85, | |
| 24 | + "attributionRequired": true, | |
| 25 | + "termsUrl": "https://www.watchfinder.co.uk/terms-and-conditions", | |
| 26 | + "accessNotes": "Plain HTTPS returns 403 to non-browser agents (robots.txt is not served either); Firecrawl's standard fetch returns the public model pages (watchfinder.co.uk/watches/<brand>/<model>?p=N). Each product card carries data attributes (sku, brand, series, model reference, image), box/papers icons, year and the GBP price (data-price-amount). 1 Firecrawl credit per page (~24 watches); 2 s between pages. Listings only.", | |
| 27 | + "enabled": true, | |
| 28 | + "schemaVersion": "1.0", | |
| 29 | + "config": { | |
| 30 | + "seeds": ["rolex/daytona", "rolex/submariner", "rolex/gmt-master-ii", "rolex/datejust", "rolex/day-date", "rolex/explorer", "patek-philippe/nautilus", "patek-philippe/aquanaut", "audemars-piguet/royal-oak", "audemars-piguet/royal-oak-offshore", "omega/speedmaster", "omega/seamaster", "cartier/santos", "tudor/black-bay", "iwc/portugieser", "jaeger-lecoultre/reverso"], | |
| 31 | + "pagesPerSeed": 2 | |
| 32 | + } | |
| 33 | +} | |
added
data/fixtures/antiquorum/monaco-june-2026-1.json
+432 −0
@@ -0,0 +1,432 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://catalog.antiquorum.swiss/en/auctions/monaco_june_2026/lots", | |
| 4 | + "externalId": "monaco_june_2026:1", | |
| 5 | + "kind": "sale", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:50.630Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "lots_page", | |
| 10 | + "url": "https://catalog.antiquorum.swiss/en/auctions/monaco_june_2026/lots", | |
| 11 | + "auction": { | |
| 12 | + "slug": "monaco_june_2026", | |
| 13 | + "id": "388", | |
| 14 | + "title": "Important Modern & Vintage Timepieces", | |
| 15 | + "date": "Jun 28, 2026", | |
| 16 | + "location": "Monaco" | |
| 17 | + }, | |
| 18 | + "page": 1, | |
| 19 | + "lots": [ | |
| 20 | + { | |
| 21 | + "lotNumber": "1", | |
| 22 | + "name": "JAEGER LECOULTRE, SWITZERLAND, REF. 1671, LUCCHETTO, STAINLESS STEEL", | |
| 23 | + "url": "https://catalog.antiquorum.swiss/en/lots/jaeger-lecoultre-ref-1671-lucchetto-lot-388-1", | |
| 24 | + "sku": "388144006", | |
| 25 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/1/medium_1.jpg", | |
| 26 | + "brand": "Jaeger Lecoultre, Switzerland", | |
| 27 | + "model": "Lucchetto", | |
| 28 | + "reference": "1671", | |
| 29 | + "year": "circa 1970", | |
| 30 | + "material": null, | |
| 31 | + "diameter": null, | |
| 32 | + "description": "A very fine and rare, stainless steel, manual wind wristwatch with black dial,", | |
| 33 | + "estimateLow": 1000, | |
| 34 | + "estimateHigh": 2000, | |
| 35 | + "estimateCurrency": "EUR", | |
| 36 | + "soldPrice": 1049, | |
| 37 | + "soldCurrency": "EUR", | |
| 38 | + "accessories": null | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + "lotNumber": "2", | |
| 42 | + "name": "TUDOR, SWITZERLAND, REF. 43300, ARCHEO CHRONOGRAPH, WHITE DIAL, STAINLESS STEEL", | |
| 43 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-ref-43300-archeo-chronograph-lot-388-2", | |
| 44 | + "sku": "388144007", | |
| 45 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/2/medium_2.jpg", | |
| 46 | + "brand": "Tudor, Switzerland", | |
| 47 | + "model": "Archeo chronograph", | |
| 48 | + "reference": "43300", | |
| 49 | + "year": "Circa 2000", | |
| 50 | + "material": null, | |
| 51 | + "diameter": "33 X 44 mm.", | |
| 52 | + "description": "A fine, stainless steel, quartz chronograph wristwatch with date.", | |
| 53 | + "estimateLow": 700, | |
| 54 | + "estimateHigh": 1300, | |
| 55 | + "estimateCurrency": "EUR", | |
| 56 | + "soldPrice": null, | |
| 57 | + "soldCurrency": null, | |
| 58 | + "accessories": "Original box and certificate" | |
| 59 | + }, | |
| 60 | + { | |
| 61 | + "lotNumber": "3", | |
| 62 | + "name": "TUDOR, SWITZERLAND, ARCHEO CHRONOGRAPH, BLACK DIAL, STAINLESS STEEL", | |
| 63 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-archeo-chronograph-lot-388-3", | |
| 64 | + "sku": "388144008", | |
| 65 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/3/medium_3.jpg", | |
| 66 | + "brand": "Tudor, Switzerland", | |
| 67 | + "model": "ARCHEO CHRONOGRAPH", | |
| 68 | + "reference": null, | |
| 69 | + "year": "Circa 2000", | |
| 70 | + "material": null, | |
| 71 | + "diameter": "33 X 44 mm.", | |
| 72 | + "description": "A fine, stainless steel, quartz quartz chronograph wristwatch with date.", | |
| 73 | + "estimateLow": 700, | |
| 74 | + "estimateHigh": 1400, | |
| 75 | + "estimateCurrency": "EUR", | |
| 76 | + "soldPrice": 787, | |
| 77 | + "soldCurrency": "EUR", | |
| 78 | + "accessories": "Original box and certificate" | |
| 79 | + }, | |
| 80 | + { | |
| 81 | + "lotNumber": "4", | |
| 82 | + "name": "TUDOR, SWITZERLAND, ARCHEO CHRONOGRAPH, SILVER DIAL, STAINLESS STEEL", | |
| 83 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-archeo-chronograph-lot-388-4", | |
| 84 | + "sku": "388144009", | |
| 85 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/4/medium_4.jpg", | |
| 86 | + "brand": "Tudor, Switzerland", | |
| 87 | + "model": "ARCHEO CHRONOGRAPH", | |
| 88 | + "reference": null, | |
| 89 | + "year": "Circa 2000", | |
| 90 | + "material": null, | |
| 91 | + "diameter": "33 X 44 mm.", | |
| 92 | + "description": "A fine, stainless steel, quartz chronograph wristwatch with date.", | |
| 93 | + "estimateLow": 700, | |
| 94 | + "estimateHigh": 1400, | |
| 95 | + "estimateCurrency": "EUR", | |
| 96 | + "soldPrice": null, | |
| 97 | + "soldCurrency": null, | |
| 98 | + "accessories": "Original box and certificate" | |
| 99 | + }, | |
| 100 | + { | |
| 101 | + "lotNumber": "5", | |
| 102 | + "name": "LONGINES, SWITZERLAND, REF. 6984-3, CARRÉ, 18K PINK GOLD", | |
| 103 | + "url": "https://catalog.antiquorum.swiss/en/lots/longines-ref-6984-3-carre-lot-388-5", | |
| 104 | + "sku": "388144010", | |
| 105 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/5/medium_5.jpg", | |
| 106 | + "brand": "Longines, Switzerland", | |
| 107 | + "model": "Carré", | |
| 108 | + "reference": "6984-3", | |
| 109 | + "year": "circa 1960's", | |
| 110 | + "material": null, | |
| 111 | + "diameter": null, | |
| 112 | + "description": "A very fine and rare, 18k pink gold, manual wind Wristwatch, silver dial, gold index and hands", | |
| 113 | + "estimateLow": 400, | |
| 114 | + "estimateHigh": 800, | |
| 115 | + "estimateCurrency": "EUR", | |
| 116 | + "soldPrice": 892, | |
| 117 | + "soldCurrency": "EUR", | |
| 118 | + "accessories": null | |
| 119 | + }, | |
| 120 | + { | |
| 121 | + "lotNumber": "6", | |
| 122 | + "name": "LONGINES, SWITZERLAND, REF. 7115, 18K PINK GOLD", | |
| 123 | + "url": "https://catalog.antiquorum.swiss/en/lots/longines-ref-7115-lot-388-6", | |
| 124 | + "sku": "388144011", | |
| 125 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/6/medium_6.jpg", | |
| 126 | + "brand": "Longines, Switzerland", | |
| 127 | + "model": null, | |
| 128 | + "reference": "7115", | |
| 129 | + "year": "circa 1960", | |
| 130 | + "material": null, | |
| 131 | + "diameter": "34mm", | |
| 132 | + "description": "A very fine and rare, 18k pink gold, manual wind Wristwatch, silver dial, gold index and hands", | |
| 133 | + "estimateLow": 1000, | |
| 134 | + "estimateHigh": 2000, | |
| 135 | + "estimateCurrency": "EUR", | |
| 136 | + "soldPrice": 1312, | |
| 137 | + "soldCurrency": "EUR", | |
| 138 | + "accessories": "original fitted box" | |
| 139 | + }, | |
| 140 | + { | |
| 141 | + "lotNumber": "7", | |
| 142 | + "name": "OMEGA, SWITZERLAND, REF. 5110404, DEVILLE TANK, 18K YELLOW GOLD", | |
| 143 | + "url": "https://catalog.antiquorum.swiss/en/lots/omega-ref-5110404-deville-tank-lot-388-7", | |
| 144 | + "sku": "388144012", | |
| 145 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/7/medium_7.jpg", | |
| 146 | + "brand": "Omega, Switzerland", | |
| 147 | + "model": "DeVille Tank", | |
| 148 | + "reference": "5110404", | |
| 149 | + "year": "circa 1970's", | |
| 150 | + "material": null, | |
| 151 | + "diameter": null, | |
| 152 | + "description": "A very fine and rare, 18k yellow gold, manual wind Wristwatch, gold colour dial, black romanic numbers and hands", | |
| 153 | + "estimateLow": 500, | |
| 154 | + "estimateHigh": 1000, | |
| 155 | + "estimateCurrency": "EUR", | |
| 156 | + "soldPrice": 1115, | |
| 157 | + "soldCurrency": "EUR", | |
| 158 | + "accessories": "original fitted box" | |
| 159 | + }, | |
| 160 | + { | |
| 161 | + "lotNumber": "8", | |
| 162 | + "name": "OMEGA, SWITZERLAND, REF. 131-90010, ÉQUINOXE, 18K YELLOW GOLD", | |
| 163 | + "url": "https://catalog.antiquorum.swiss/en/lots/omega-ref-131-90010-equinoxe-lot-388-8", | |
| 164 | + "sku": "388144013", | |
| 165 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/8/medium_8.jpg", | |
| 166 | + "brand": "Omega, Switzerland", | |
| 167 | + "model": "Équinoxe", | |
| 168 | + "reference": "131-90010", | |
| 169 | + "year": "circa 1970's", | |
| 170 | + "material": null, | |
| 171 | + "diameter": null, | |
| 172 | + "description": "A very fine and rare, 18k yellow gold, manual wind Wristwatch, silver dial, gold index and hands", | |
| 173 | + "estimateLow": 800, | |
| 174 | + "estimateHigh": 1600, | |
| 175 | + "estimateCurrency": "EUR", | |
| 176 | + "soldPrice": 1574, | |
| 177 | + "soldCurrency": "EUR", | |
| 178 | + "accessories": "original fitted box, tag" | |
| 179 | + }, | |
| 180 | + { | |
| 181 | + "lotNumber": "9", | |
| 182 | + "name": "OMEGA, SWITZERLAND, REF. 2417, 18K YELLOW GOLD", | |
| 183 | + "url": "https://catalog.antiquorum.swiss/en/lots/omega-ref-2417-lot-388-9", | |
| 184 | + "sku": "388144014", | |
| 185 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/9/medium_9.jpg", | |
| 186 | + "brand": "Omega, Switzerland", | |
| 187 | + "model": null, | |
| 188 | + "reference": "2417", | |
| 189 | + "year": "circa 1960's", | |
| 190 | + "material": null, | |
| 191 | + "diameter": "36mm", | |
| 192 | + "description": "A very fine and rare, 18k yellow gold, manual wind Wristwatch, silver dial, gold index and hands", | |
| 193 | + "estimateLow": 800, | |
| 194 | + "estimateHigh": 1600, | |
| 195 | + "estimateCurrency": "EUR", | |
| 196 | + "soldPrice": 1312, | |
| 197 | + "soldCurrency": "EUR", | |
| 198 | + "accessories": null | |
| 199 | + }, | |
| 200 | + { | |
| 201 | + "lotNumber": "10", | |
| 202 | + "name": "BAUME & MERCIER, SWITZERLAND, REF. 35100, CLASSIMA, 18K YELLOW GOLD", | |
| 203 | + "url": "https://catalog.antiquorum.swiss/en/lots/baume-mercier-ref-35100-classima-lot-388-10", | |
| 204 | + "sku": "388144015", | |
| 205 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/10/medium_10.jpg", | |
| 206 | + "brand": "Baume & Mercier, Switzerland", | |
| 207 | + "model": "Classima", | |
| 208 | + "reference": "35100", | |
| 209 | + "year": "circa 1990's", | |
| 210 | + "material": null, | |
| 211 | + "diameter": "34mm", | |
| 212 | + "description": "A very fine and rare, 18k yellow gold, manual wind Wristwatch, white dial, black Roman numbers and hands", | |
| 213 | + "estimateLow": 800, | |
| 214 | + "estimateHigh": 1500, | |
| 215 | + "estimateCurrency": "EUR", | |
| 216 | + "soldPrice": 1207, | |
| 217 | + "soldCurrency": "EUR", | |
| 218 | + "accessories": null | |
| 219 | + }, | |
| 220 | + { | |
| 221 | + "lotNumber": "11", | |
| 222 | + "name": "MOVADO, SWITZERLAND, REF. 11729, RONDE, STAINLESS STEEL", | |
| 223 | + "url": "https://catalog.antiquorum.swiss/en/lots/movado-ref-11729-ronde-lot-388-11", | |
| 224 | + "sku": "388144016", | |
| 225 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/11/medium_11.jpg", | |
| 226 | + "brand": "Movado, Switzerland", | |
| 227 | + "model": "ronde", | |
| 228 | + "reference": "11729", | |
| 229 | + "year": "circa 1938", | |
| 230 | + "material": null, | |
| 231 | + "diameter": "29mm", | |
| 232 | + "description": "A very fine and rare, stainless steel, manual wind Wristwatch, two tone dial, Breguet numbers.", | |
| 233 | + "estimateLow": 300, | |
| 234 | + "estimateHigh": 800, | |
| 235 | + "estimateCurrency": "EUR", | |
| 236 | + "soldPrice": 787, | |
| 237 | + "soldCurrency": "EUR", | |
| 238 | + "accessories": null | |
| 239 | + }, | |
| 240 | + { | |
| 241 | + "lotNumber": "12", | |
| 242 | + "name": "AUDEMARS PIGUET, SWITZERLAND, ROYAL OAK CHRONOGRAPH DIAL", | |
| 243 | + "url": "https://catalog.antiquorum.swiss/en/lots/audemars-piguet-royal-oak-chronograph-dial-lot-388-12", | |
| 244 | + "sku": "388144017", | |
| 245 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/12/medium_12.jpg", | |
| 246 | + "brand": "Audemars Piguet, Switzerland", | |
| 247 | + "model": "Royal Oak chronograph dial", | |
| 248 | + "reference": null, | |
| 249 | + "year": null, | |
| 250 | + "material": null, | |
| 251 | + "diameter": null, | |
| 252 | + "description": "A fine and attractive audemars piguet silver dial with hands for a audemars piguet royal oak chronograph ", | |
| 253 | + "estimateLow": 300, | |
| 254 | + "estimateHigh": 500, | |
| 255 | + "estimateCurrency": "EUR", | |
| 256 | + "soldPrice": 328, | |
| 257 | + "soldCurrency": "EUR", | |
| 258 | + "accessories": null | |
| 259 | + }, | |
| 260 | + { | |
| 261 | + "lotNumber": "13", | |
| 262 | + "name": "PATEK PHILIPPE, SWITZERLAND, COMMEMORATIVE MEDAL, METAL", | |
| 263 | + "url": "https://catalog.antiquorum.swiss/en/lots/patek-philippe-commemorative-medal-lot-388-13", | |
| 264 | + "sku": "388144018", | |
| 265 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/13/medium_13.jpg", | |
| 266 | + "brand": "Patek Philippe, Switzerland", | |
| 267 | + "model": "commemorative medal", | |
| 268 | + "reference": null, | |
| 269 | + "year": "circa 1900", | |
| 270 | + "material": null, | |
| 271 | + "diameter": "36", | |
| 272 | + "description": "A very fine and collectable metal medal by Georges Hantz", | |
| 273 | + "estimateLow": 500, | |
| 274 | + "estimateHigh": 1000, | |
| 275 | + "estimateCurrency": "EUR", | |
| 276 | + "soldPrice": 656, | |
| 277 | + "soldCurrency": "EUR", | |
| 278 | + "accessories": "fitted box" | |
| 279 | + }, | |
| 280 | + { | |
| 281 | + "lotNumber": "14", | |
| 282 | + "name": "MAURICE LACROIX, SWITZERLAND, REF. MP6439, MASTER ASTRONOMIC STAINLESS STEEL AND GOLD", | |
| 283 | + "url": "https://catalog.antiquorum.swiss/en/lots/maurice-lacroix-ref-mp6439-master-astronomic-stainless-steel-and-gold-lot-388-14", | |
| 284 | + "sku": "388144019", | |
| 285 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/14/medium_14.jpg", | |
| 286 | + "brand": "Maurice Lacroix, Switzerland", | |
| 287 | + "model": "Master Astronomic stainless steel and gold", | |
| 288 | + "reference": "MP6439", | |
| 289 | + "year": "2004", | |
| 290 | + "material": null, | |
| 291 | + "diameter": null, | |
| 292 | + "description": "A fine tonneau shaped self-winding astronomic wristwatch with yellow gold bezel. silver dial. day, date, month and moon phases. with original papers ", | |
| 293 | + "estimateLow": 800, | |
| 294 | + "estimateHigh": 1200, | |
| 295 | + "estimateCurrency": "EUR", | |
| 296 | + "soldPrice": 1180, | |
| 297 | + "soldCurrency": "EUR", | |
| 298 | + "accessories": "Original papers and invoice" | |
| 299 | + }, | |
| 300 | + { | |
| 301 | + "lotNumber": "15", | |
| 302 | + "name": "BELL & ROSS, SWITZERLAND, REF. BR 03-310159, AVIATION TYPE, CERAMIC", | |
| 303 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-br-03-310159-aviation-type-lot-388-15", | |
| 304 | + "sku": "388144020", | |
| 305 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/15/medium_15.jpg", | |
| 306 | + "brand": "Bell & Ross, Switzerland", | |
| 307 | + "model": "Aviation type", | |
| 308 | + "reference": "BR 03-310159", | |
| 309 | + "year": "Circa 2020", | |
| 310 | + "material": null, | |
| 311 | + "diameter": "42 X 42 mm.", | |
| 312 | + "description": "A fine and large, ceramic, self winding water resistant wristwatch, center second with date.", | |
| 313 | + "estimateLow": 700, | |
| 314 | + "estimateHigh": 1400, | |
| 315 | + "estimateCurrency": "EUR", | |
| 316 | + "soldPrice": 1312, | |
| 317 | + "soldCurrency": "EUR", | |
| 318 | + "accessories": "Original box and certificate" | |
| 319 | + }, | |
| 320 | + { | |
| 321 | + "lotNumber": "16", | |
| 322 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRV2-92-S-04447, VINTAGE DIVER, STAINLESS STEEL", | |
| 323 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-brv2-92-s-04447-vintage-diver-lot-388-16", | |
| 324 | + "sku": "388144021", | |
| 325 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/16/medium_16.jpg", | |
| 326 | + "brand": "Bell & Ross, Switzerland", | |
| 327 | + "model": "vintage diver", | |
| 328 | + "reference": "BRV2-92-S-04447", | |
| 329 | + "year": "circa 2020", | |
| 330 | + "material": null, | |
| 331 | + "diameter": "41 mm.", | |
| 332 | + "description": "A fine, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 333 | + "estimateLow": 900, | |
| 334 | + "estimateHigh": 1400, | |
| 335 | + "estimateCurrency": "EUR", | |
| 336 | + "soldPrice": 1180, | |
| 337 | + "soldCurrency": "EUR", | |
| 338 | + "accessories": "Original box and certificate" | |
| 339 | + }, | |
| 340 | + { | |
| 341 | + "lotNumber": "17", | |
| 342 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRV2-92-S-03748, VINTAGE DIVER, STAINLESS STEEL", | |
| 343 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-brv2-92-s-03748-vintage-diver-lot-388-17", | |
| 344 | + "sku": "388144022", | |
| 345 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/17/medium_17.jpg", | |
| 346 | + "brand": "Bell & Ross, Switzerland", | |
| 347 | + "model": "vintage diver", | |
| 348 | + "reference": "BRV2-92-S-03748", | |
| 349 | + "year": "circa 2020", | |
| 350 | + "material": null, | |
| 351 | + "diameter": "41 mm.", | |
| 352 | + "description": "A fine and large, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 353 | + "estimateLow": 900, | |
| 354 | + "estimateHigh": 1400, | |
| 355 | + "estimateCurrency": "EUR", | |
| 356 | + "soldPrice": 1180, | |
| 357 | + "soldCurrency": "EUR", | |
| 358 | + "accessories": "Original box and certificate" | |
| 359 | + }, | |
| 360 | + { | |
| 361 | + "lotNumber": "18", | |
| 362 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRV2-92-S-04675, VINTAGE COLLECTION, STAINLESS STEEL", | |
| 363 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-brv2-92-s-04675-vintage-collection-lot-388-18", | |
| 364 | + "sku": "388144023", | |
| 365 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/18/medium_18.jpg", | |
| 366 | + "brand": "Bell & Ross, Switzerland", | |
| 367 | + "model": "Vintage Collection", | |
| 368 | + "reference": "BRV2-92-S-04675", | |
| 369 | + "year": "Circa 2020", | |
| 370 | + "material": null, | |
| 371 | + "diameter": "41 mm.", | |
| 372 | + "description": "A fine, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 373 | + "estimateLow": 1000, | |
| 374 | + "estimateHigh": 2000, | |
| 375 | + "estimateCurrency": "EUR", | |
| 376 | + "soldPrice": null, | |
| 377 | + "soldCurrency": null, | |
| 378 | + "accessories": "Original box and certificate" | |
| 379 | + }, | |
| 380 | + { | |
| 381 | + "lotNumber": "19", | |
| 382 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRO3-3002202, AVIATION TYPE, STAINLESS STEEL", | |
| 383 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-bro3-3002202-aviation-type-lot-388-19", | |
| 384 | + "sku": "388144024", | |
| 385 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/19/medium_19.jpg", | |
| 386 | + "brand": "Bell & Ross, Switzerland", | |
| 387 | + "model": "Aviation type", | |
| 388 | + "reference": "BRO3-3002202", | |
| 389 | + "year": "Circa 2020", | |
| 390 | + "material": null, | |
| 391 | + "diameter": "42 X 42 mm.", | |
| 392 | + "description": "A fine and large, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 393 | + "estimateLow": 1200, | |
| 394 | + "estimateHigh": 2200, | |
| 395 | + "estimateCurrency": "EUR", | |
| 396 | + "soldPrice": 1574, | |
| 397 | + "soldCurrency": "EUR", | |
| 398 | + "accessories": "Original box and certificate" | |
| 399 | + }, | |
| 400 | + { | |
| 401 | + "lotNumber": "20", | |
| 402 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRO3-92-DIVER-08063, DIVER, STAINLESS STEEL", | |
| 403 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-bro3-92-diver-08063-diver-lot-388-20", | |
| 404 | + "sku": "388144025", | |
| 405 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/20/medium_20.jpg", | |
| 406 | + "brand": "Bell & Ross, Switzerland", | |
| 407 | + "model": "diver", | |
| 408 | + "reference": "BRO3-92-DIVER-08063", | |
| 409 | + "year": "Circa 2020", | |
| 410 | + "material": null, | |
| 411 | + "diameter": "42 X 42 mm.", | |
| 412 | + "description": "A fine and large, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 413 | + "estimateLow": 1200, | |
| 414 | + "estimateHigh": 2200, | |
| 415 | + "estimateCurrency": "EUR", | |
| 416 | + "soldPrice": null, | |
| 417 | + "soldCurrency": null, | |
| 418 | + "accessories": "Original box and certificate" | |
| 419 | + } | |
| 420 | + ] | |
| 421 | + } | |
| 422 | + }, | |
| 423 | + "expect": { | |
| 424 | + "minCount": 1, | |
| 425 | + "kinds": [ | |
| 426 | + "sale", | |
| 427 | + "auction_lot" | |
| 428 | + ] | |
| 429 | + }, | |
| 430 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 431 | + "capturedAt": "2026-09-07T06:28:50.644Z" | |
| 432 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/antiquorum/monaco-june-2026-2.json
+432 −0
@@ -0,0 +1,432 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://catalog.antiquorum.swiss/en/auctions/monaco_june_2026/lots?page=2", | |
| 4 | + "externalId": "monaco_june_2026:2", | |
| 5 | + "kind": "sale", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:52.940Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "lots_page", | |
| 10 | + "url": "https://catalog.antiquorum.swiss/en/auctions/monaco_june_2026/lots?page=2", | |
| 11 | + "auction": { | |
| 12 | + "slug": "monaco_june_2026", | |
| 13 | + "id": "388", | |
| 14 | + "title": "Important Modern & Vintage Timepieces", | |
| 15 | + "date": "Jun 28, 2026", | |
| 16 | + "location": "Monaco" | |
| 17 | + }, | |
| 18 | + "page": 2, | |
| 19 | + "lots": [ | |
| 20 | + { | |
| 21 | + "lotNumber": "21", | |
| 22 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRO3-92-DIVER-00739, DIVER, STAINLESS STEEL", | |
| 23 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-bro3-92-diver-00739-diver-lot-388-21", | |
| 24 | + "sku": "388144026", | |
| 25 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/21/medium_21.jpg", | |
| 26 | + "brand": "Bell & Ross, Switzerland", | |
| 27 | + "model": "diver", | |
| 28 | + "reference": "BRO3-92-DIVER-00739", | |
| 29 | + "year": "circa 2020", | |
| 30 | + "material": null, | |
| 31 | + "diameter": "42 X 42 mm.", | |
| 32 | + "description": "A fine and large, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 33 | + "estimateLow": 1200, | |
| 34 | + "estimateHigh": 2200, | |
| 35 | + "estimateCurrency": "EUR", | |
| 36 | + "soldPrice": 1574, | |
| 37 | + "soldCurrency": "EUR", | |
| 38 | + "accessories": "Original box and certificate" | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + "lotNumber": "22", | |
| 42 | + "name": "ROLEX, SWITZERLAND, HOTEL PARTICULIER SILVER", | |
| 43 | + "url": "https://catalog.antiquorum.swiss/en/lots/rolex-hotel-particulier-silver-lot-388-22", | |
| 44 | + "sku": "388144027", | |
| 45 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/22/medium_22.jpg", | |
| 46 | + "brand": "Rolex, Switzerland", | |
| 47 | + "model": "Hotel Particulier silver", | |
| 48 | + "reference": null, | |
| 49 | + "year": null, | |
| 50 | + "material": null, | |
| 51 | + "diameter": null, | |
| 52 | + "description": "A fine silver, decorative rectangular-shaped cigarettes box with high relief cast front panel representing a “hôtel particulier ", | |
| 53 | + "estimateLow": 2000, | |
| 54 | + "estimateHigh": 3000, | |
| 55 | + "estimateCurrency": "EUR", | |
| 56 | + "soldPrice": 4198, | |
| 57 | + "soldCurrency": "EUR", | |
| 58 | + "accessories": "Box" | |
| 59 | + }, | |
| 60 | + { | |
| 61 | + "lotNumber": "23", | |
| 62 | + "name": "ROLEX, SWITZERLAND, REF. 4498, PRECISION, STAINLESS STEEL", | |
| 63 | + "url": "https://catalog.antiquorum.swiss/en/lots/rolex-ref-4498-precision-lot-388-23", | |
| 64 | + "sku": "388144028", | |
| 65 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/23/medium_23.jpg", | |
| 66 | + "brand": "Rolex, Switzerland", | |
| 67 | + "model": "Precision", | |
| 68 | + "reference": "4498", | |
| 69 | + "year": "circa 1960", | |
| 70 | + "material": null, | |
| 71 | + "diameter": "35mm", | |
| 72 | + "description": "A very fine and rare, stainless steel, manual wind Wristwatch, silver dial, gold index and hands", | |
| 73 | + "estimateLow": 3000, | |
| 74 | + "estimateHigh": 4000, | |
| 75 | + "estimateCurrency": "EUR", | |
| 76 | + "soldPrice": null, | |
| 77 | + "soldCurrency": null, | |
| 78 | + "accessories": "original fitted box" | |
| 79 | + }, | |
| 80 | + { | |
| 81 | + "lotNumber": "24", | |
| 82 | + "name": "ROLEX, SWITZERLAND, REF. 6694, PRECISION, STAINLESS STEEL", | |
| 83 | + "url": "https://catalog.antiquorum.swiss/en/lots/rolex-ref-6694-precision-lot-388-24", | |
| 84 | + "sku": "388144029", | |
| 85 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/24/medium_24.jpg", | |
| 86 | + "brand": "Rolex, Switzerland", | |
| 87 | + "model": "Precision", | |
| 88 | + "reference": "6694", | |
| 89 | + "year": "1979", | |
| 90 | + "material": null, | |
| 91 | + "diameter": "34 mm", | |
| 92 | + "description": "A very fine and rare, stainless steel wristwatch with black dial", | |
| 93 | + "estimateLow": 1500, | |
| 94 | + "estimateHigh": 2000, | |
| 95 | + "estimateCurrency": "EUR", | |
| 96 | + "soldPrice": 3017, | |
| 97 | + "soldCurrency": "EUR", | |
| 98 | + "accessories": null | |
| 99 | + }, | |
| 100 | + { | |
| 101 | + "lotNumber": "25", | |
| 102 | + "name": "BELL & ROSS, SWITZERLAND, REF. BR 03-93-S-02678, AVIATION TYPE GMT, STAINLESS STEEL", | |
| 103 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-br-03-93-s-02678-aviation-type-gmt-lot-388-25", | |
| 104 | + "sku": "388144030", | |
| 105 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/25/medium_25.jpg", | |
| 106 | + "brand": "Bell & Ross, Switzerland", | |
| 107 | + "model": "Aviation type gmt", | |
| 108 | + "reference": "BR 03-93-S-02678", | |
| 109 | + "year": "CIRCA 2020", | |
| 110 | + "material": null, | |
| 111 | + "diameter": "42 X 42 mm.", | |
| 112 | + "description": "A fine and large, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 113 | + "estimateLow": 1200, | |
| 114 | + "estimateHigh": 2200, | |
| 115 | + "estimateCurrency": "EUR", | |
| 116 | + "soldPrice": null, | |
| 117 | + "soldCurrency": null, | |
| 118 | + "accessories": null | |
| 119 | + }, | |
| 120 | + { | |
| 121 | + "lotNumber": "26", | |
| 122 | + "name": "BELL & ROSS, SWITZERLAND, REF. BR 03-93-S-03565, AVIATION TYPE GMT, STAINLESS STEEL", | |
| 123 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-br-03-93-s-03565-aviation-type-gmt-lot-388-26", | |
| 124 | + "sku": "388144031", | |
| 125 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/26/medium_26.jpg", | |
| 126 | + "brand": "Bell & Ross, Switzerland", | |
| 127 | + "model": "Aviation type gmt", | |
| 128 | + "reference": "BR 03-93-S-03565", | |
| 129 | + "year": "Circa 2020", | |
| 130 | + "material": null, | |
| 131 | + "diameter": "42 X 42 mm.", | |
| 132 | + "description": "A fine and large, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 133 | + "estimateLow": 1200, | |
| 134 | + "estimateHigh": 2200, | |
| 135 | + "estimateCurrency": "EUR", | |
| 136 | + "soldPrice": 2099, | |
| 137 | + "soldCurrency": "EUR", | |
| 138 | + "accessories": "Original box and certificate" | |
| 139 | + }, | |
| 140 | + { | |
| 141 | + "lotNumber": "27", | |
| 142 | + "name": "BELL & ROSS, SWITZERLAND, REF. BR V2-94 BLACK, CHRONOGRAPH, STAINLESS STEEL", | |
| 143 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-br-v2-94-black-lot-388-27", | |
| 144 | + "sku": "388144032", | |
| 145 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/27/medium_27.jpg", | |
| 146 | + "brand": "Bell & Ross, Switzerland", | |
| 147 | + "model": null, | |
| 148 | + "reference": "BR V2-94 black", | |
| 149 | + "year": "circa 2020", | |
| 150 | + "material": null, | |
| 151 | + "diameter": "41 mm.", | |
| 152 | + "description": "A fine, stainless steel, self winding water resistant chronograph wristwatch with date.", | |
| 153 | + "estimateLow": 1200, | |
| 154 | + "estimateHigh": 2200, | |
| 155 | + "estimateCurrency": "EUR", | |
| 156 | + "soldPrice": 2099, | |
| 157 | + "soldCurrency": "EUR", | |
| 158 | + "accessories": "Original box and certificate" | |
| 159 | + }, | |
| 160 | + { | |
| 161 | + "lotNumber": "28", | |
| 162 | + "name": "BELL & ROSS, SWITZERLAND, STAINLESS STEEL", | |
| 163 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-lot-388-28", | |
| 164 | + "sku": "388144033", | |
| 165 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/28/medium_28.jpg", | |
| 166 | + "brand": "Bell & Ross, Switzerland", | |
| 167 | + "model": null, | |
| 168 | + "reference": null, | |
| 169 | + "year": "Circa 2020", | |
| 170 | + "material": null, | |
| 171 | + "diameter": "40 mm.", | |
| 172 | + "description": "A fine, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 173 | + "estimateLow": 1500, | |
| 174 | + "estimateHigh": 2500, | |
| 175 | + "estimateCurrency": "EUR", | |
| 176 | + "soldPrice": 2492, | |
| 177 | + "soldCurrency": "EUR", | |
| 178 | + "accessories": "Original box and certificate" | |
| 179 | + }, | |
| 180 | + { | |
| 181 | + "lotNumber": "29", | |
| 182 | + "name": "BELL & ROSS, SWITZERLAND, REF. BRO3-92-D-B, DIVER BRONZE, LIMITED EDITION OF 999 PIECES, BRONZE", | |
| 183 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-bro3-92-d-b-diver-bronze-lot-388-29", | |
| 184 | + "sku": "388144034", | |
| 185 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/29/medium_29.jpg", | |
| 186 | + "brand": "Bell & Ross, Switzerland", | |
| 187 | + "model": "diver bronze", | |
| 188 | + "reference": "BRO3-92-D-B", | |
| 189 | + "year": "Circa 2020", | |
| 190 | + "material": null, | |
| 191 | + "diameter": "42 X 42 mm.", | |
| 192 | + "description": "A fine, rare and large, bronze, self winding water resistant wristwatch, center second with date.", | |
| 193 | + "estimateLow": 1500, | |
| 194 | + "estimateHigh": 2500, | |
| 195 | + "estimateCurrency": "EUR", | |
| 196 | + "soldPrice": 2230, | |
| 197 | + "soldCurrency": "EUR", | |
| 198 | + "accessories": "Original box and certificate" | |
| 199 | + }, | |
| 200 | + { | |
| 201 | + "lotNumber": "30", | |
| 202 | + "name": "BELL & ROSS, SWITZERLAND, REF. BR05G-SI-ST/SST, GMT, STAINLESS STEEL", | |
| 203 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-ref-br05g-si-st-sst-gmt-lot-388-30", | |
| 204 | + "sku": "388144035", | |
| 205 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/30/medium_30.jpg", | |
| 206 | + "brand": "Bell & Ross, Switzerland", | |
| 207 | + "model": "GMT", | |
| 208 | + "reference": "BR05G-SI-ST/SST", | |
| 209 | + "year": "Circa 2020", | |
| 210 | + "material": null, | |
| 211 | + "diameter": "41 mm.", | |
| 212 | + "description": "A fine and elegant, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 213 | + "estimateLow": 1500, | |
| 214 | + "estimateHigh": 2500, | |
| 215 | + "estimateCurrency": "EUR", | |
| 216 | + "soldPrice": null, | |
| 217 | + "soldCurrency": null, | |
| 218 | + "accessories": "Original box and certificate" | |
| 219 | + }, | |
| 220 | + { | |
| 221 | + "lotNumber": "31", | |
| 222 | + "name": "BELL & ROSS, SWITZERLAND, GMT, STAINLESS STEEL", | |
| 223 | + "url": "https://catalog.antiquorum.swiss/en/lots/bell-ross-gmt-lot-388-31", | |
| 224 | + "sku": "388144036", | |
| 225 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/31/medium_31.jpg", | |
| 226 | + "brand": "Bell & Ross, Switzerland", | |
| 227 | + "model": "GMT", | |
| 228 | + "reference": null, | |
| 229 | + "year": "Circa 2020", | |
| 230 | + "material": null, | |
| 231 | + "diameter": "41 mm.", | |
| 232 | + "description": "A fine and elegant, stainless steel water resistant wristwatch, center second with date.", | |
| 233 | + "estimateLow": 1500, | |
| 234 | + "estimateHigh": 2500, | |
| 235 | + "estimateCurrency": "EUR", | |
| 236 | + "soldPrice": null, | |
| 237 | + "soldCurrency": null, | |
| 238 | + "accessories": "Original box and certificate" | |
| 239 | + }, | |
| 240 | + { | |
| 241 | + "lotNumber": "32", | |
| 242 | + "name": "TUDOR, SWITZERLAND, REF. 21010, CLASSIC, STAINLESS STEEL", | |
| 243 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-ref-21010-classic-lot-388-32", | |
| 244 | + "sku": "388144037", | |
| 245 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/32/medium_32.jpg", | |
| 246 | + "brand": "Tudor, Switzerland", | |
| 247 | + "model": "classic", | |
| 248 | + "reference": "21010", | |
| 249 | + "year": "Circa 2010", | |
| 250 | + "material": null, | |
| 251 | + "diameter": "38 mm.", | |
| 252 | + "description": "A fine, stainless steel, self winding water resistant wristwatch, center second with date.", | |
| 253 | + "estimateLow": 800, | |
| 254 | + "estimateHigh": 1800, | |
| 255 | + "estimateCurrency": "EUR", | |
| 256 | + "soldPrice": 1049, | |
| 257 | + "soldCurrency": "EUR", | |
| 258 | + "accessories": null | |
| 259 | + }, | |
| 260 | + { | |
| 261 | + "lotNumber": "33", | |
| 262 | + "name": "TUDOR, SWITZERLAND, REF. 55003, GLAMOUR DATE, CHAMPAGNE DIAL, STAINLESS STEEL AND GOLD", | |
| 263 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-ref-55003-glamour-date-lot-388-33", | |
| 264 | + "sku": "388144038", | |
| 265 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/33/medium_33.jpg", | |
| 266 | + "brand": "Tudor, Switzerland", | |
| 267 | + "model": "Glamour date", | |
| 268 | + "reference": "55003", | |
| 269 | + "year": "Circa 2020", | |
| 270 | + "material": null, | |
| 271 | + "diameter": "36 mm.", | |
| 272 | + "description": "A very fine, steel and gold, self winding water resistant wristwatch, center second with date.", | |
| 273 | + "estimateLow": 1000, | |
| 274 | + "estimateHigh": 2000, | |
| 275 | + "estimateCurrency": "EUR", | |
| 276 | + "soldPrice": 1180, | |
| 277 | + "soldCurrency": "EUR", | |
| 278 | + "accessories": "Original box and certificate" | |
| 279 | + }, | |
| 280 | + { | |
| 281 | + "lotNumber": "34", | |
| 282 | + "name": "TUDOR, SWITZERLAND, REF. 57000, GLAMOUR DOUBLE DATE, STAINLESS STEEL", | |
| 283 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-ref-57000-glamour-double-date-lot-388-34", | |
| 284 | + "sku": "388144039", | |
| 285 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/34/medium_34.jpg", | |
| 286 | + "brand": "Tudor, Switzerland", | |
| 287 | + "model": "GLAMOUR DOUBLE DATE", | |
| 288 | + "reference": "57000", | |
| 289 | + "year": "Circa 2010", | |
| 290 | + "material": null, | |
| 291 | + "diameter": "42 mm.", | |
| 292 | + "description": "A fine, stainless steel, self winding water resistant wristwatch with double date.", | |
| 293 | + "estimateLow": 1000, | |
| 294 | + "estimateHigh": 2000, | |
| 295 | + "estimateCurrency": "EUR", | |
| 296 | + "soldPrice": 1180, | |
| 297 | + "soldCurrency": "EUR", | |
| 298 | + "accessories": "Original box and certificate" | |
| 299 | + }, | |
| 300 | + { | |
| 301 | + "lotNumber": "35", | |
| 302 | + "name": "TUDOR, SWITZERLAND, REF. 25000, HYDRONAUT 1200, STAINLESS STEEL", | |
| 303 | + "url": "https://catalog.antiquorum.swiss/en/lots/tudor-ref-25000-hydronaut-1200-lot-388-35", | |
| 304 | + "sku": "388144040", | |
| 305 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/35/medium_35.jpg", | |
| 306 | + "brand": "Tudor, Switzerland", | |
| 307 | + "model": "hydronaut 1200", | |
| 308 | + "reference": "25000", | |
| 309 | + "year": "Circa 2010", | |
| 310 | + "material": null, | |
| 311 | + "diameter": "45 mm.", | |
| 312 | + "description": "A fine, large, heavy, stainless steel, self winding diver's wristwatch with date and helium escape valve.", | |
| 313 | + "estimateLow": 1200, | |
| 314 | + "estimateHigh": 3200, | |
| 315 | + "estimateCurrency": "EUR", | |
| 316 | + "soldPrice": null, | |
| 317 | + "soldCurrency": null, | |
| 318 | + "accessories": "Original box and certificate" | |
| 319 | + }, | |
| 320 | + { | |
| 321 | + "lotNumber": "36", | |
| 322 | + "name": "EBERHARD & CO., SWITZERLAND, REF. 61007CA2, GINGI, STAINLESS STEEL", | |
| 323 | + "url": "https://catalog.antiquorum.swiss/en/lots/eberhard-co-ref-61007ca2-gingi-lot-388-36", | |
| 324 | + "sku": "388144041", | |
| 325 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/36/medium_36.jpg", | |
| 326 | + "brand": "Eberhard & Co., Switzerland", | |
| 327 | + "model": "Gingi", | |
| 328 | + "reference": "61007CA2", | |
| 329 | + "year": "Circa 2020", | |
| 330 | + "material": null, | |
| 331 | + "diameter": "22 X 33 mm.", | |
| 332 | + "description": "A fine, stainless steel, quartz lady wristwatch.", | |
| 333 | + "estimateLow": 300, | |
| 334 | + "estimateHigh": 1000, | |
| 335 | + "estimateCurrency": "EUR", | |
| 336 | + "soldPrice": 393, | |
| 337 | + "soldCurrency": "EUR", | |
| 338 | + "accessories": null | |
| 339 | + }, | |
| 340 | + { | |
| 341 | + "lotNumber": "37", | |
| 342 | + "name": "EBERHARD & CO., SWITZERLAND, REF. 21120.21 CP, TRAVERSETOLO VITRÉ, STAINLESS STEEL", | |
| 343 | + "url": "https://catalog.antiquorum.swiss/en/lots/eberhard-co-ref-21120-21-cp-traversetolo-vitre-lot-388-37", | |
| 344 | + "sku": "388144042", | |
| 345 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/37/medium_37.jpg", | |
| 346 | + "brand": "Eberhard & Co., Switzerland", | |
| 347 | + "model": "TRAVERSETOLO VITRÉ", | |
| 348 | + "reference": "21120.21 CP", | |
| 349 | + "year": "circa 2020", | |
| 350 | + "material": null, | |
| 351 | + "diameter": "43 mm.", | |
| 352 | + "description": "A fine, large, stainless steel, manual wind water resistant wristwatch.", | |
| 353 | + "estimateLow": 700, | |
| 354 | + "estimateHigh": 1700, | |
| 355 | + "estimateCurrency": "EUR", | |
| 356 | + "soldPrice": 852, | |
| 357 | + "soldCurrency": "EUR", | |
| 358 | + "accessories": null | |
| 359 | + }, | |
| 360 | + { | |
| 361 | + "lotNumber": "38", | |
| 362 | + "name": "EBERHARD & CO., SWITZERLAND, REF. 41034.VS CAD, SCAFOGRAF 300, STAINLESS STEEL", | |
| 363 | + "url": "https://catalog.antiquorum.swiss/en/lots/eberhard-co-ref-41034-vs-cad-scafograf-300-lot-388-38", | |
| 364 | + "sku": "388144043", | |
| 365 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/38/medium_38.jpg", | |
| 366 | + "brand": "Eberhard & Co., Switzerland", | |
| 367 | + "model": "SCAFOGRAF 300", | |
| 368 | + "reference": "41034.VS CAD", | |
| 369 | + "year": "circa 2020", | |
| 370 | + "material": null, | |
| 371 | + "diameter": "43 mm.", | |
| 372 | + "description": "A fine, stainless steel, self winding water resistant wristwatch with date.", | |
| 373 | + "estimateLow": 800, | |
| 374 | + "estimateHigh": 1800, | |
| 375 | + "estimateCurrency": "EUR", | |
| 376 | + "soldPrice": 1312, | |
| 377 | + "soldCurrency": "EUR", | |
| 378 | + "accessories": null | |
| 379 | + }, | |
| 380 | + { | |
| 381 | + "lotNumber": "39", | |
| 382 | + "name": "VULCAIN, SWITZERLAND, CRICKET, 14K YELLOW GOLD", | |
| 383 | + "url": "https://catalog.antiquorum.swiss/en/lots/vulcain-cricket-lot-388-39", | |
| 384 | + "sku": "388144044", | |
| 385 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/39/medium_39.jpg", | |
| 386 | + "brand": "Vulcain, Switzerland", | |
| 387 | + "model": "cricket", | |
| 388 | + "reference": null, | |
| 389 | + "year": "circa 1950's", | |
| 390 | + "material": null, | |
| 391 | + "diameter": "34mm", | |
| 392 | + "description": "A very fine and rare, 14k yellow gold, manual wind Wristwatch, silver two tone dial, gold index and hands", | |
| 393 | + "estimateLow": 800, | |
| 394 | + "estimateHigh": 1500, | |
| 395 | + "estimateCurrency": "EUR", | |
| 396 | + "soldPrice": 2755, | |
| 397 | + "soldCurrency": "EUR", | |
| 398 | + "accessories": "Original box , booklet" | |
| 399 | + }, | |
| 400 | + { | |
| 401 | + "lotNumber": "40", | |
| 402 | + "name": "LECOULTRE, SWITZERLAND, MEMOVOX, 10K GOLD FILLED", | |
| 403 | + "url": "https://catalog.antiquorum.swiss/en/lots/lecoultre-memovox-lot-388-40", | |
| 404 | + "sku": "388144045", | |
| 405 | + "image": "https://antiquorum-swiss-assets.s3.us-west-2.amazonaws.com/images/388/lots/40/medium_40.jpg", | |
| 406 | + "brand": "LeCoultre, Switzerland", | |
| 407 | + "model": "memovox", | |
| 408 | + "reference": null, | |
| 409 | + "year": "circa 1960's", | |
| 410 | + "material": null, | |
| 411 | + "diameter": "34mm", | |
| 412 | + "description": "A very fine and rare, 10k gold filled wristwatch with two tone dial, alarm", | |
| 413 | + "estimateLow": 1800, | |
| 414 | + "estimateHigh": 3500, | |
| 415 | + "estimateCurrency": "EUR", | |
| 416 | + "soldPrice": null, | |
| 417 | + "soldCurrency": null, | |
| 418 | + "accessories": "box, outer box, price tag with $71.50 FTI" | |
| 419 | + } | |
| 420 | + ] | |
| 421 | + } | |
| 422 | + }, | |
| 423 | + "expect": { | |
| 424 | + "minCount": 1, | |
| 425 | + "kinds": [ | |
| 426 | + "sale", | |
| 427 | + "auction_lot" | |
| 428 | + ] | |
| 429 | + }, | |
| 430 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 431 | + "capturedAt": "2026-09-07T06:28:52.943Z" | |
| 432 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/bobs-watches/model-rolex-daytona.json
+564 −0
@@ -0,0 +1,564 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.bobswatches.com/rolex-daytona", | |
| 4 | + "externalId": "model:rolex-daytona", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:37.649Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "model_page", | |
| 10 | + "url": "https://www.bobswatches.com/rolex-daytona", | |
| 11 | + "seed": "rolex-daytona", | |
| 12 | + "items": [ | |
| 13 | + { | |
| 14 | + "name": "Pre-Owned Rolex Daytona 126525LN 18K Everose Gold “Le Mans”", | |
| 15 | + "mpn": "126525", | |
| 16 | + "sku": "193618 C", | |
| 17 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-126525ln-18k-everose-gold-le-mans.html", | |
| 18 | + "color": "Rose Gold", | |
| 19 | + "price": 225995, | |
| 20 | + "currency": "USD", | |
| 21 | + "availability": "InStock", | |
| 22 | + "image": "[object Object]", | |
| 23 | + "condition": "UsedCondition" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "name": "Used Rolex Daytona Ref 126503 Black Dial", | |
| 27 | + "mpn": "126503", | |
| 28 | + "sku": "193590 PL", | |
| 29 | + "url": "https://www.bobswatches.com/used-rolex-daytona-ref-126503-black-dial.html", | |
| 30 | + "color": "Steel and Gold", | |
| 31 | + "price": 26795, | |
| 32 | + "currency": "USD", | |
| 33 | + "availability": "InStock", | |
| 34 | + "image": "[object Object]", | |
| 35 | + "condition": "UsedCondition" | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "name": "Pre-Owned White Dial Rolex Cosmograph Daytona Ref 126503 Steel & Yellow Gold", | |
| 39 | + "mpn": "126503", | |
| 40 | + "sku": "192982", | |
| 41 | + "url": "https://www.bobswatches.com/pre-owned-white-dial-rolex-cosmograph-daytona-ref-126503-steel-and-yellow-gold.html", | |
| 42 | + "color": "Steel and Gold", | |
| 43 | + "price": 26795, | |
| 44 | + "currency": "USD", | |
| 45 | + "availability": "InStock", | |
| 46 | + "image": "[object Object]", | |
| 47 | + "condition": "UsedCondition" | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "name": "Used Leather Strap Rolex Daytona Ref 16519 Black Dial", | |
| 51 | + "mpn": "116519", | |
| 52 | + "sku": "191905", | |
| 53 | + "url": "https://www.bobswatches.com/used-leather-strap-rolex-daytona-ref-16519-black-dial.html", | |
| 54 | + "color": "White Gold", | |
| 55 | + "price": 29995, | |
| 56 | + "currency": "USD", | |
| 57 | + "availability": "InStock", | |
| 58 | + "image": "[object Object]", | |
| 59 | + "condition": "UsedCondition" | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + "name": "Used Stainless Steel Rolex Daytona Black Dial Ref 116520", | |
| 63 | + "mpn": "116520", | |
| 64 | + "sku": "191732", | |
| 65 | + "url": "https://www.bobswatches.com/used-stainless-steel-rolex-daytona-black-dial-ref-116520.html", | |
| 66 | + "color": "Stainless Steel", | |
| 67 | + "price": 22995, | |
| 68 | + "currency": "USD", | |
| 69 | + "availability": "InStock", | |
| 70 | + "image": "[object Object]", | |
| 71 | + "condition": "UsedCondition" | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + "name": "Used Two-Tone Rolex Daytona Black Dial Ref 116503", | |
| 75 | + "mpn": "116503", | |
| 76 | + "sku": "192886", | |
| 77 | + "url": "https://www.bobswatches.com/used-two-tone-rolex-daytona-black-dial-ref-116503.html", | |
| 78 | + "color": "Steel and Gold", | |
| 79 | + "price": 21995, | |
| 80 | + "currency": "USD", | |
| 81 | + "availability": "InStock", | |
| 82 | + "image": "[object Object]", | |
| 83 | + "condition": "UsedCondition" | |
| 84 | + }, | |
| 85 | + { | |
| 86 | + "name": "Used Oyster Two Tone Rolex Cosmograph Daytona Ref 126503", | |
| 87 | + "mpn": "126503", | |
| 88 | + "sku": "192720", | |
| 89 | + "url": "https://www.bobswatches.com/used-oyster-two-tone-rolex-cosmograph-daytona-ref-126503.html", | |
| 90 | + "color": "Steel and Gold", | |
| 91 | + "price": 26595, | |
| 92 | + "currency": "USD", | |
| 93 | + "availability": "InStock", | |
| 94 | + "image": "[object Object]", | |
| 95 | + "condition": "UsedCondition" | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "name": "Used White Gold Rolex Daytona Ref 116509 Blue Dial", | |
| 99 | + "mpn": "116509", | |
| 100 | + "sku": "192822", | |
| 101 | + "url": "https://www.bobswatches.com/used-white-gold-rolex-daytona-ref-116509-blue-dial.html", | |
| 102 | + "color": "White Gold", | |
| 103 | + "price": 49995, | |
| 104 | + "currency": "USD", | |
| 105 | + "availability": "InStock", | |
| 106 | + "image": "[object Object]", | |
| 107 | + "condition": "UsedCondition" | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + "name": "Pre-Owned Rolex Daytona Ref 126509 Silver Dial", | |
| 111 | + "mpn": "126509", | |
| 112 | + "sku": "193527 PL", | |
| 113 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-126509-silver-dial.html", | |
| 114 | + "color": "White Gold", | |
| 115 | + "price": 57495, | |
| 116 | + "currency": "USD", | |
| 117 | + "availability": "InStock", | |
| 118 | + "image": "[object Object]", | |
| 119 | + "condition": "UsedCondition" | |
| 120 | + }, | |
| 121 | + { | |
| 122 | + "name": "Pre-Owned Rolex White Dial Daytona Cosmograph Ref 116500LN Stainless Steel", | |
| 123 | + "mpn": "116500LN", | |
| 124 | + "sku": "193351", | |
| 125 | + "url": "https://www.bobswatches.com/pre-owned-rolex-white-dial-daytona-cosmograph-ref-116500ln-stainless-steel.html", | |
| 126 | + "color": "Stainless Steel", | |
| 127 | + "price": 33895, | |
| 128 | + "currency": "USD", | |
| 129 | + "availability": "InStock", | |
| 130 | + "image": "[object Object]", | |
| 131 | + "condition": "UsedCondition" | |
| 132 | + }, | |
| 133 | + { | |
| 134 | + "name": "Pre-Owned Rolex Daytona White Dial Ref 126500LN Panda", | |
| 135 | + "mpn": "126500LN", | |
| 136 | + "sku": "193371 PL", | |
| 137 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-white-dial-ref-126500ln-panda.html", | |
| 138 | + "color": "Stainless Steel", | |
| 139 | + "price": 38995, | |
| 140 | + "currency": "USD", | |
| 141 | + "availability": "InStock", | |
| 142 | + "image": "[object Object]", | |
| 143 | + "condition": "UsedCondition" | |
| 144 | + }, | |
| 145 | + { | |
| 146 | + "name": "Pre-Owned Rolex Daytona Ref 126505 Chocolate Chronograph Dial", | |
| 147 | + "mpn": "126505", | |
| 148 | + "sku": "192778 C", | |
| 149 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-126505-chocolate-chronograph-dial.html", | |
| 150 | + "color": "Rose Gold", | |
| 151 | + "price": 61995, | |
| 152 | + "currency": "USD", | |
| 153 | + "availability": "InStock", | |
| 154 | + "image": "[object Object]", | |
| 155 | + "condition": "UsedCondition" | |
| 156 | + }, | |
| 157 | + { | |
| 158 | + "name": "Pre-Owned Rolex Daytona Ref 126508 Champagne Dial", | |
| 159 | + "mpn": "126508", | |
| 160 | + "sku": "193480 PL", | |
| 161 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-126508-champagne-dial.html", | |
| 162 | + "color": "Yellow Gold", | |
| 163 | + "price": 56495, | |
| 164 | + "currency": "USD", | |
| 165 | + "availability": "InStock", | |
| 166 | + "image": "[object Object]", | |
| 167 | + "condition": "UsedCondition" | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "name": "Pre-Owned Rolex Daytona Ref 116528 Black Diamond Dial", | |
| 171 | + "mpn": "116528", | |
| 172 | + "sku": "192132", | |
| 173 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-116528-black-diamond-dial.html", | |
| 174 | + "color": "Yellow Gold", | |
| 175 | + "price": 46995, | |
| 176 | + "currency": "USD", | |
| 177 | + "availability": "InStock", | |
| 178 | + "image": "[object Object]", | |
| 179 | + "condition": "UsedCondition" | |
| 180 | + }, | |
| 181 | + { | |
| 182 | + "name": "Pre-Owned White Dial Rolex Daytona Cosmograph Ref 116500 Panda", | |
| 183 | + "mpn": "116500LN", | |
| 184 | + "sku": "190973", | |
| 185 | + "url": "https://www.bobswatches.com/pre-owned-white-dial-rolex-daytona-cosmograph-ref-116500-panda.html", | |
| 186 | + "color": "Stainless Steel", | |
| 187 | + "price": 33895, | |
| 188 | + "currency": "USD", | |
| 189 | + "availability": "InStock", | |
| 190 | + "image": "[object Object]", | |
| 191 | + "condition": "UsedCondition" | |
| 192 | + }, | |
| 193 | + { | |
| 194 | + "name": "Used Rolex Daytona Black Chronograph Dial Ref 116520", | |
| 195 | + "mpn": "116520", | |
| 196 | + "sku": "192985", | |
| 197 | + "url": "https://www.bobswatches.com/used-rolex-daytona-black-chronograph-dial-ref-116520.html", | |
| 198 | + "color": "Stainless Steel", | |
| 199 | + "price": 22495, | |
| 200 | + "currency": "USD", | |
| 201 | + "availability": "InStock", | |
| 202 | + "image": "[object Object]", | |
| 203 | + "condition": "UsedCondition" | |
| 204 | + }, | |
| 205 | + { | |
| 206 | + "name": "Used Rolex Daytona Ref 116503 Black Chromalight Dial", | |
| 207 | + "mpn": "116503", | |
| 208 | + "sku": "192915", | |
| 209 | + "url": "https://www.bobswatches.com/used-rolex-daytona-ref-116503-black-chromalight-dial.html", | |
| 210 | + "color": "Steel and Gold", | |
| 211 | + "price": 21595, | |
| 212 | + "currency": "USD", | |
| 213 | + "availability": "InStock", | |
| 214 | + "image": "[object Object]", | |
| 215 | + "condition": "UsedCondition" | |
| 216 | + }, | |
| 217 | + { | |
| 218 | + "name": "Used Champagne Dial Rolex Daytona Ref 126503", | |
| 219 | + "mpn": "126503", | |
| 220 | + "sku": "192328", | |
| 221 | + "url": "https://www.bobswatches.com/used-champagne-dial-rolex-daytona-ref-126503.html", | |
| 222 | + "color": "Steel and Gold", | |
| 223 | + "price": 26495, | |
| 224 | + "currency": "USD", | |
| 225 | + "availability": "InStock", | |
| 226 | + "image": "[object Object]", | |
| 227 | + "condition": "UsedCondition" | |
| 228 | + }, | |
| 229 | + { | |
| 230 | + "name": "Used Stainless Steel Rolex Daytona Cosmograph Ref 116500 White Dial", | |
| 231 | + "mpn": "116500LN", | |
| 232 | + "sku": "192481", | |
| 233 | + "url": "https://www.bobswatches.com/used-stainless-steel-rolex-daytona-cosmograph-ref-116500-white-dial.html", | |
| 234 | + "color": "Stainless Steel", | |
| 235 | + "price": 33695, | |
| 236 | + "currency": "USD", | |
| 237 | + "availability": "InStock", | |
| 238 | + "image": "[object Object]", | |
| 239 | + "condition": "UsedCondition" | |
| 240 | + }, | |
| 241 | + { | |
| 242 | + "name": "Pre-Owned Steel Rolex Daytona Ref 116520 White Chronograph Dial", | |
| 243 | + "mpn": "116520", | |
| 244 | + "sku": "192662", | |
| 245 | + "url": "https://www.bobswatches.com/pre-owned-steel-rolex-daytona-ref-116520-white-chronograph-dial.html", | |
| 246 | + "color": "Stainless Steel", | |
| 247 | + "price": 23495, | |
| 248 | + "currency": "USD", | |
| 249 | + "availability": "InStock", | |
| 250 | + "image": "[object Object]", | |
| 251 | + "condition": "UsedCondition" | |
| 252 | + }, | |
| 253 | + { | |
| 254 | + "name": "Pre-Owned Stainless Steel Rolex Daytona Ref 116520 Black Dial", | |
| 255 | + "mpn": "116520", | |
| 256 | + "sku": "192084", | |
| 257 | + "url": "https://www.bobswatches.com/pre-owned-stainless-steel-rolex-daytona-ref-116520-black-dial.html", | |
| 258 | + "color": "Stainless Steel", | |
| 259 | + "price": 23495, | |
| 260 | + "currency": "USD", | |
| 261 | + "availability": "InStock", | |
| 262 | + "image": "[object Object]", | |
| 263 | + "condition": "UsedCondition" | |
| 264 | + }, | |
| 265 | + { | |
| 266 | + "name": "Pre-Owned Rolex Daytona Cosmograph Ref 116500LN White Dial", | |
| 267 | + "mpn": "116500LN", | |
| 268 | + "sku": "192362", | |
| 269 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-cosmograph-ref-116500ln-white-dial.html", | |
| 270 | + "color": "Stainless Steel", | |
| 271 | + "price": 33495, | |
| 272 | + "currency": "USD", | |
| 273 | + "availability": "InStock", | |
| 274 | + "image": "[object Object]", | |
| 275 | + "condition": "UsedCondition" | |
| 276 | + }, | |
| 277 | + { | |
| 278 | + "name": "Used Black Dial Rolex Daytona Ref 126503 Steel & 18k Yellow Gold", | |
| 279 | + "mpn": "126503", | |
| 280 | + "sku": "192981", | |
| 281 | + "url": "https://www.bobswatches.com/used-black-dial-rolex-daytona-ref-126503-steel-and-18k-yellow-gold.html", | |
| 282 | + "color": "Steel and Gold", | |
| 283 | + "price": 26495, | |
| 284 | + "currency": "USD", | |
| 285 | + "availability": "InStock", | |
| 286 | + "image": "[object Object]", | |
| 287 | + "condition": "UsedCondition" | |
| 288 | + }, | |
| 289 | + { | |
| 290 | + "name": "Pre-Owned Rolex Daytona Ref 126500LN", | |
| 291 | + "mpn": "126500LN", | |
| 292 | + "sku": "192658", | |
| 293 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-126500ln.html", | |
| 294 | + "color": "Stainless Steel", | |
| 295 | + "price": 32495, | |
| 296 | + "currency": "USD", | |
| 297 | + "availability": "InStock", | |
| 298 | + "image": "[object Object]", | |
| 299 | + "condition": "UsedCondition" | |
| 300 | + }, | |
| 301 | + { | |
| 302 | + "name": "Pre-Owned Rolex Daytona Ref 116519 Steel Dial", | |
| 303 | + "mpn": "116519", | |
| 304 | + "sku": "192887", | |
| 305 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-116519-steel-dial.html", | |
| 306 | + "color": "White Gold", | |
| 307 | + "price": 46595, | |
| 308 | + "currency": "USD", | |
| 309 | + "availability": "InStock", | |
| 310 | + "image": "[object Object]", | |
| 311 | + "condition": "UsedCondition" | |
| 312 | + }, | |
| 313 | + { | |
| 314 | + "name": "Pre-Owned Champagne Dial Rolex Daytona Ref 116523", | |
| 315 | + "mpn": "116523", | |
| 316 | + "sku": "192827", | |
| 317 | + "url": "https://www.bobswatches.com/pre-owned-champagne-dial-rolex-daytona-ref-116523.html", | |
| 318 | + "color": "Steel and Gold", | |
| 319 | + "price": 21495, | |
| 320 | + "currency": "USD", | |
| 321 | + "availability": "InStock", | |
| 322 | + "image": "[object Object]", | |
| 323 | + "condition": "UsedCondition" | |
| 324 | + }, | |
| 325 | + { | |
| 326 | + "name": "Used Rolex Daytona Ref 116523 Champagne Dial", | |
| 327 | + "mpn": "116523", | |
| 328 | + "sku": "192514", | |
| 329 | + "url": "https://www.bobswatches.com/used-rolex-daytona-ref-116523-champagne-dial.html", | |
| 330 | + "color": "Steel and Gold", | |
| 331 | + "price": 20495, | |
| 332 | + "currency": "USD", | |
| 333 | + "availability": "InStock", | |
| 334 | + "image": "[object Object]", | |
| 335 | + "condition": "UsedCondition" | |
| 336 | + }, | |
| 337 | + { | |
| 338 | + "name": "Used Rolex Daytona ref 126509 Black Chronograph Dial", | |
| 339 | + "mpn": "126509", | |
| 340 | + "sku": "189889", | |
| 341 | + "url": "https://www.bobswatches.com/used-rolex-daytona-ref-126509-black-chronograph-dial.html", | |
| 342 | + "color": "White Gold", | |
| 343 | + "price": 52795, | |
| 344 | + "currency": "USD", | |
| 345 | + "availability": "InStock", | |
| 346 | + "image": "[object Object]", | |
| 347 | + "condition": "UsedCondition" | |
| 348 | + }, | |
| 349 | + { | |
| 350 | + "name": "Vintage Rolex Chronograph 3525 \"Monoblocco\"", | |
| 351 | + "mpn": "3525", | |
| 352 | + "sku": "188097 C", | |
| 353 | + "url": "https://www.bobswatches.com/vintage-rolex-chronograph-3525-monoblocco.html", | |
| 354 | + "color": "Stainless Steel", | |
| 355 | + "price": 59995, | |
| 356 | + "currency": "USD", | |
| 357 | + "availability": "InStock", | |
| 358 | + "image": "[object Object]", | |
| 359 | + "condition": "UsedCondition" | |
| 360 | + }, | |
| 361 | + { | |
| 362 | + "name": "Used Two Tone Rolex Daytona Ref 16523 Champagne Dial", | |
| 363 | + "mpn": "16523", | |
| 364 | + "sku": "191174", | |
| 365 | + "url": "https://www.bobswatches.com/used-two-tone-rolex-daytona-ref-16523-champagne-dial.html", | |
| 366 | + "color": "Steel and Gold", | |
| 367 | + "price": 18595, | |
| 368 | + "currency": "USD", | |
| 369 | + "availability": "InStock", | |
| 370 | + "image": "[object Object]", | |
| 371 | + "condition": "UsedCondition" | |
| 372 | + }, | |
| 373 | + { | |
| 374 | + "name": "Used Steel Oyster Rolex Daytona 126500 Black Dial", | |
| 375 | + "mpn": "126500LN", | |
| 376 | + "sku": "192377", | |
| 377 | + "url": "https://www.bobswatches.com/used-steel-oyster-rolex-daytona-126500-black-dial.html", | |
| 378 | + "color": "Stainless Steel", | |
| 379 | + "price": 32495, | |
| 380 | + "currency": "USD", | |
| 381 | + "availability": "InStock", | |
| 382 | + "image": "[object Object]", | |
| 383 | + "condition": "UsedCondition" | |
| 384 | + }, | |
| 385 | + { | |
| 386 | + "name": "Used 18k Everose Gold Rolex Daytona Ref 116505 Black Dial", | |
| 387 | + "mpn": "116505", | |
| 388 | + "sku": "190521", | |
| 389 | + "url": "https://www.bobswatches.com/used-18k-everose-gold-rolex-daytona-ref-116505-black-dial.html", | |
| 390 | + "color": "Rose Gold", | |
| 391 | + "price": 47995, | |
| 392 | + "currency": "USD", | |
| 393 | + "availability": "InStock", | |
| 394 | + "image": "[object Object]", | |
| 395 | + "condition": "UsedCondition" | |
| 396 | + }, | |
| 397 | + { | |
| 398 | + "name": "Pre-Owned Rolex Daytona 116508 18k Yellow Gold", | |
| 399 | + "mpn": "116508", | |
| 400 | + "sku": "191989", | |
| 401 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-116508-18k-yellow-gold.html", | |
| 402 | + "color": "Yellow Gold", | |
| 403 | + "price": 51995, | |
| 404 | + "currency": "USD", | |
| 405 | + "availability": "InStock", | |
| 406 | + "image": "[object Object]", | |
| 407 | + "condition": "UsedCondition" | |
| 408 | + }, | |
| 409 | + { | |
| 410 | + "name": "Pre-Owned Rolex Daytona 116509 18k White Gold", | |
| 411 | + "mpn": "116509", | |
| 412 | + "sku": "188534", | |
| 413 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-116509-18k-white-gold.html", | |
| 414 | + "color": "White Gold", | |
| 415 | + "price": 35495, | |
| 416 | + "currency": "USD", | |
| 417 | + "availability": "InStock", | |
| 418 | + "image": "[object Object]", | |
| 419 | + "condition": "UsedCondition" | |
| 420 | + }, | |
| 421 | + { | |
| 422 | + "name": "Used White Dial Rolex Cosmograph Daytona Ref 126503", | |
| 423 | + "mpn": "126503", | |
| 424 | + "sku": "192118", | |
| 425 | + "url": "https://www.bobswatches.com/used-white-dial-rolex-cosmograph-daytona-ref-126503.html", | |
| 426 | + "color": "Steel and Gold", | |
| 427 | + "price": 26495, | |
| 428 | + "currency": "USD", | |
| 429 | + "availability": "InStock", | |
| 430 | + "image": "[object Object]", | |
| 431 | + "condition": "UsedCondition" | |
| 432 | + }, | |
| 433 | + { | |
| 434 | + "name": "Used Rolex Daytona Ref 16528 Black Diamond Dial", | |
| 435 | + "mpn": "16528", | |
| 436 | + "sku": "189967", | |
| 437 | + "url": "https://www.bobswatches.com/used-rolex-daytona-ref-16528-black-diamond-dial.html", | |
| 438 | + "color": "Yellow Gold", | |
| 439 | + "price": 45995, | |
| 440 | + "currency": "USD", | |
| 441 | + "availability": "InStock", | |
| 442 | + "image": "[object Object]", | |
| 443 | + "condition": "UsedCondition" | |
| 444 | + }, | |
| 445 | + { | |
| 446 | + "name": "Pre-Owned Yellow Gold Rolex Daytona Ref 116518 Champagne Dial", | |
| 447 | + "mpn": "116518", | |
| 448 | + "sku": "190915", | |
| 449 | + "url": "https://www.bobswatches.com/pre-owned-yellow-gold-rolex-daytona-ref-116518-champagne-dial.html", | |
| 450 | + "color": "Yellow Gold", | |
| 451 | + "price": 43995, | |
| 452 | + "currency": "USD", | |
| 453 | + "availability": "InStock", | |
| 454 | + "image": "[object Object]", | |
| 455 | + "condition": "UsedCondition" | |
| 456 | + }, | |
| 457 | + { | |
| 458 | + "name": "Pre-Owned Rolex Daytona Ref 126500LN Black Chromalight Dial", | |
| 459 | + "mpn": "126500LN", | |
| 460 | + "sku": "192618", | |
| 461 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-126500ln-black-chromalight-dial.html", | |
| 462 | + "color": "Stainless Steel", | |
| 463 | + "price": 32395, | |
| 464 | + "currency": "USD", | |
| 465 | + "availability": "InStock", | |
| 466 | + "image": "[object Object]", | |
| 467 | + "condition": "UsedCondition" | |
| 468 | + }, | |
| 469 | + { | |
| 470 | + "name": "Pre-Owned Rolex Daytona Ref 116523 Black Index Dial", | |
| 471 | + "mpn": "116523", | |
| 472 | + "sku": "191153", | |
| 473 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-116523-black-index-dial.html", | |
| 474 | + "color": "Steel and Gold", | |
| 475 | + "price": 20495, | |
| 476 | + "currency": "USD", | |
| 477 | + "availability": "InStock", | |
| 478 | + "image": "[object Object]", | |
| 479 | + "condition": "UsedCondition" | |
| 480 | + }, | |
| 481 | + { | |
| 482 | + "name": "Pre-Owned 18k Everose Gold Rolex Daytona Ref 116505 Black Dial", | |
| 483 | + "mpn": "116505", | |
| 484 | + "sku": "190827", | |
| 485 | + "url": "https://www.bobswatches.com/pre-owned-18k-everose-gold-rolex-daytona-ref-116505-black-dial.html", | |
| 486 | + "color": "Rose Gold", | |
| 487 | + "price": 45995, | |
| 488 | + "currency": "USD", | |
| 489 | + "availability": "InStock", | |
| 490 | + "image": "[object Object]", | |
| 491 | + "condition": "UsedCondition" | |
| 492 | + }, | |
| 493 | + { | |
| 494 | + "name": "Pre-Owned Rolex Daytona Ref 116500 Luminous Black Dial", | |
| 495 | + "mpn": "116500", | |
| 496 | + "sku": "191692", | |
| 497 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-116500-luminous-black-dial.html", | |
| 498 | + "color": "Stainless Steel", | |
| 499 | + "price": 28795, | |
| 500 | + "currency": "USD", | |
| 501 | + "availability": "InStock", | |
| 502 | + "image": "[object Object]", | |
| 503 | + "condition": "UsedCondition" | |
| 504 | + }, | |
| 505 | + { | |
| 506 | + "name": "Used Rolex Daytona 116528 Mother of Pearl Roman Dial", | |
| 507 | + "mpn": "116528", | |
| 508 | + "sku": "186619", | |
| 509 | + "url": "https://www.bobswatches.com/used-rolex-daytona-116528-mother-of-pearl-roman-dial.html", | |
| 510 | + "color": "Yellow Gold", | |
| 511 | + "price": 45995, | |
| 512 | + "currency": "USD", | |
| 513 | + "availability": "InStock", | |
| 514 | + "image": "[object Object]", | |
| 515 | + "condition": "UsedCondition" | |
| 516 | + }, | |
| 517 | + { | |
| 518 | + "name": "Pre-owned Rolex Cosmograph Daytona 126508 Black Dial", | |
| 519 | + "mpn": "126508", | |
| 520 | + "sku": "188399", | |
| 521 | + "url": "https://www.bobswatches.com/pre-owned-rolex-cosmograph-daytona-126508-black-dial.html", | |
| 522 | + "color": "Yellow Gold", | |
| 523 | + "price": 56495, | |
| 524 | + "currency": "USD", | |
| 525 | + "availability": "InStock", | |
| 526 | + "image": "[object Object]", | |
| 527 | + "condition": "UsedCondition" | |
| 528 | + }, | |
| 529 | + { | |
| 530 | + "name": "Used Rolex Daytona Ref 16523 Steel & Gold Champagne Dial", | |
| 531 | + "mpn": "16523", | |
| 532 | + "sku": "189141", | |
| 533 | + "url": "https://www.bobswatches.com/used-rolex-daytona-ref-16523-steel-and-gold-champagne-dial.html", | |
| 534 | + "color": "Steel and Gold", | |
| 535 | + "price": 18595, | |
| 536 | + "currency": "USD", | |
| 537 | + "availability": "InStock", | |
| 538 | + "image": "[object Object]", | |
| 539 | + "condition": "UsedCondition" | |
| 540 | + }, | |
| 541 | + { | |
| 542 | + "name": "Pre-Owned Rolex Daytona ref 16523 Champagne Dial Zenith Movement", | |
| 543 | + "mpn": "16523", | |
| 544 | + "sku": "189090", | |
| 545 | + "url": "https://www.bobswatches.com/pre-owned-rolex-daytona-ref-16523-champagne-dial-zenith-movement.html", | |
| 546 | + "color": "Steel and Gold", | |
| 547 | + "price": 19995, | |
| 548 | + "currency": "USD", | |
| 549 | + "availability": "InStock", | |
| 550 | + "image": "[object Object]", | |
| 551 | + "condition": "UsedCondition" | |
| 552 | + } | |
| 553 | + ] | |
| 554 | + } | |
| 555 | + }, | |
| 556 | + "expect": { | |
| 557 | + "minCount": 1, | |
| 558 | + "kinds": [ | |
| 559 | + "listing" | |
| 560 | + ] | |
| 561 | + }, | |
| 562 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 563 | + "capturedAt": "2026-09-07T06:28:37.695Z" | |
| 564 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/bobs-watches/model-rolex-submariner.json
+564 −0
@@ -0,0 +1,564 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.bobswatches.com/rolex-submariner", | |
| 4 | + "externalId": "model:rolex-submariner", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:36.315Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "model_page", | |
| 10 | + "url": "https://www.bobswatches.com/rolex-submariner", | |
| 11 | + "seed": "rolex-submariner", | |
| 12 | + "items": [ | |
| 13 | + { | |
| 14 | + "name": "Used 18k Yellow Gold Rolex Submariner Ref 126618 Black Dial", | |
| 15 | + "mpn": "126618", | |
| 16 | + "sku": "193277", | |
| 17 | + "url": "https://www.bobswatches.com/used-18k-yellow-gold-rolex-submariner-ref-126618-black-dial.html", | |
| 18 | + "color": "Yellow Gold", | |
| 19 | + "price": 41995, | |
| 20 | + "currency": "USD", | |
| 21 | + "availability": "InStock", | |
| 22 | + "image": "[object Object]", | |
| 23 | + "condition": "UsedCondition" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "name": "Pre-Owned Steel Rolex Submariner Ref 124060 Black Cerachrom Bezel", | |
| 27 | + "mpn": "124060", | |
| 28 | + "sku": "192468", | |
| 29 | + "url": "https://www.bobswatches.com/pre-owned-steel-rolex-submariner-ref-124060-black-cerachrom-bezel.html", | |
| 30 | + "color": "Stainless Steel", | |
| 31 | + "price": 13995, | |
| 32 | + "currency": "USD", | |
| 33 | + "availability": "InStock", | |
| 34 | + "image": "[object Object]", | |
| 35 | + "condition": "UsedCondition" | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "name": "Used Two Tone Rolex Submariner Ref 126613 Black Dial", | |
| 39 | + "mpn": "126613LN", | |
| 40 | + "sku": "193285", | |
| 41 | + "url": "https://www.bobswatches.com/used-two-tone-rolex-submariner-ref-126613-black-dial.html", | |
| 42 | + "color": "Steel and Gold", | |
| 43 | + "price": 18295, | |
| 44 | + "currency": "USD", | |
| 45 | + "availability": "InStock", | |
| 46 | + "image": "[object Object]", | |
| 47 | + "condition": "UsedCondition" | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "name": "Used Stainless Steel Rolex Submariner Ref 16610 Black Dial", | |
| 51 | + "mpn": "16610", | |
| 52 | + "sku": "192151", | |
| 53 | + "url": "https://www.bobswatches.com/used-stainless-steel-rolex-submariner-ref-16610-black-dial.html", | |
| 54 | + "color": "Stainless Steel", | |
| 55 | + "price": 10595, | |
| 56 | + "currency": "USD", | |
| 57 | + "availability": "InStock", | |
| 58 | + "image": "[object Object]", | |
| 59 | + "condition": "UsedCondition" | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + "name": "Pre-Owned Rolex Submariner Black Dial Ref 114060 Stainless Steel", | |
| 63 | + "mpn": "114060", | |
| 64 | + "sku": "192704", | |
| 65 | + "url": "https://www.bobswatches.com/pre-owned-rolex-submariner-black-dial-ref-114060-stainless-steel.html", | |
| 66 | + "color": "Stainless Steel", | |
| 67 | + "price": 12495, | |
| 68 | + "currency": "USD", | |
| 69 | + "availability": "InStock", | |
| 70 | + "image": "[object Object]", | |
| 71 | + "condition": "UsedCondition" | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + "name": "Used Rolex Submariner Blue Dial Ref 16618", | |
| 75 | + "mpn": "16618", | |
| 76 | + "sku": "192060", | |
| 77 | + "url": "https://www.bobswatches.com/used-rolex-submariner-blue-dial-ref-16618.html", | |
| 78 | + "color": "Yellow Gold", | |
| 79 | + "price": 28495, | |
| 80 | + "currency": "USD", | |
| 81 | + "availability": "InStock", | |
| 82 | + "image": "[object Object]", | |
| 83 | + "condition": "UsedCondition" | |
| 84 | + }, | |
| 85 | + { | |
| 86 | + "name": "Used Rolex Submariner Date Starbucks Ref 126610LV Stainless Steel", | |
| 87 | + "mpn": "126610LV", | |
| 88 | + "sku": "193218", | |
| 89 | + "url": "https://www.bobswatches.com/used-rolex-submariner-date-starbucks-ref-126610lv-stainless-steel.html", | |
| 90 | + "color": "Stainless Steel", | |
| 91 | + "price": 15995, | |
| 92 | + "currency": "USD", | |
| 93 | + "availability": "InStock", | |
| 94 | + "image": "[object Object]", | |
| 95 | + "condition": "UsedCondition" | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "name": "Used Rolex Submariner Ref 16610 Black Aluminum Bezel Dial", | |
| 99 | + "mpn": "16610", | |
| 100 | + "sku": "192267", | |
| 101 | + "url": "https://www.bobswatches.com/used-rolex-submariner-ref-16610-black-aluminum-bezel-dial.html", | |
| 102 | + "color": "Stainless Steel", | |
| 103 | + "price": 10595, | |
| 104 | + "currency": "USD", | |
| 105 | + "availability": "InStock", | |
| 106 | + "image": "[object Object]", | |
| 107 | + "condition": "UsedCondition" | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + "name": "Used Rolex Black Dial Submariner Ref 16610 Stainless Steel", | |
| 111 | + "mpn": "16610", | |
| 112 | + "sku": "190878", | |
| 113 | + "url": "https://www.bobswatches.com/used-rolex-black-dial-submariner-ref-16610-stainless-steel.html", | |
| 114 | + "color": "Stainless Steel", | |
| 115 | + "price": 11995, | |
| 116 | + "currency": "USD", | |
| 117 | + "availability": "InStock", | |
| 118 | + "image": "[object Object]", | |
| 119 | + "condition": "UsedCondition" | |
| 120 | + }, | |
| 121 | + { | |
| 122 | + "name": "Pre-Owned Rolex Submariner Black Aluminum Bezel Ref 16610 Steel Oyster", | |
| 123 | + "mpn": "16610", | |
| 124 | + "sku": "192207", | |
| 125 | + "url": "https://www.bobswatches.com/pre-owned-rolex-submariner-black-aluminum-bezel-ref-16610-steel-oyster.html", | |
| 126 | + "color": "Stainless Steel", | |
| 127 | + "price": 10695, | |
| 128 | + "currency": "USD", | |
| 129 | + "availability": "InStock", | |
| 130 | + "image": "[object Object]", | |
| 131 | + "condition": "UsedCondition" | |
| 132 | + }, | |
| 133 | + { | |
| 134 | + "name": "Used Rolex Submariner Bluesy Ref 116613LB Steel & Yellow Gold", | |
| 135 | + "mpn": "116613", | |
| 136 | + "sku": "192988", | |
| 137 | + "url": "https://www.bobswatches.com/used-rolex-submariner-bluesy-ref-116613lb-steel-and-yellow-gold.html", | |
| 138 | + "color": "Steel and Gold", | |
| 139 | + "price": 15695, | |
| 140 | + "currency": "USD", | |
| 141 | + "availability": "InStock", | |
| 142 | + "image": "[object Object]", | |
| 143 | + "condition": "UsedCondition" | |
| 144 | + }, | |
| 145 | + { | |
| 146 | + "name": "Pre-Owned Rolex Submariner Ref 116610LN Black Dial", | |
| 147 | + "mpn": "116610", | |
| 148 | + "sku": "189508", | |
| 149 | + "url": "https://www.bobswatches.com/pre-owned-rolex-submariner-ref-116610ln-black-dial.html", | |
| 150 | + "color": "Stainless Steel", | |
| 151 | + "price": 11995, | |
| 152 | + "currency": "USD", | |
| 153 | + "availability": "InStock", | |
| 154 | + "image": "[object Object]", | |
| 155 | + "condition": "UsedCondition" | |
| 156 | + }, | |
| 157 | + { | |
| 158 | + "name": "Pre-Owned No Holes Case Rolex Submariner 16610T Stainless Steel Oyster", | |
| 159 | + "mpn": "16610T", | |
| 160 | + "sku": "192888", | |
| 161 | + "url": "https://www.bobswatches.com/pre-owned-no-holes-case-rolex-submariner-16610t-stainless-steel-oyster.html", | |
| 162 | + "color": "Stainless Steel", | |
| 163 | + "price": 12495, | |
| 164 | + "currency": "USD", | |
| 165 | + "availability": "InStock", | |
| 166 | + "image": "[object Object]", | |
| 167 | + "condition": "UsedCondition" | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "name": "Used Oyster Rolex Submariner Ref 16610 Black Aluminum Bezel", | |
| 171 | + "mpn": "16610", | |
| 172 | + "sku": "190011", | |
| 173 | + "url": "https://www.bobswatches.com/used-oyster-rolex-submariner-ref-16610-black-aluminum-bezel.html", | |
| 174 | + "color": "Stainless Steel", | |
| 175 | + "price": 12295, | |
| 176 | + "currency": "USD", | |
| 177 | + "availability": "InStock", | |
| 178 | + "image": "[object Object]", | |
| 179 | + "condition": "UsedCondition" | |
| 180 | + }, | |
| 181 | + { | |
| 182 | + "name": "Used Stainless Steel Rolex Submariner Ref 116610LN Black Dial", | |
| 183 | + "mpn": "116610", | |
| 184 | + "sku": "142346", | |
| 185 | + "url": "https://www.bobswatches.com/used-stainless-steel-rolex-submariner-ref-116610ln-black-dial.html", | |
| 186 | + "color": "Stainless Steel", | |
| 187 | + "price": 13795, | |
| 188 | + "currency": "USD", | |
| 189 | + "availability": "InStock", | |
| 190 | + "image": "[object Object]", | |
| 191 | + "condition": "UsedCondition" | |
| 192 | + }, | |
| 193 | + { | |
| 194 | + "name": "Pre-Owned Black No Date Dial Rolex Submariner Ref 114060", | |
| 195 | + "mpn": "114060", | |
| 196 | + "sku": "191211", | |
| 197 | + "url": "https://www.bobswatches.com/pre-owned-black-no-date-dial-rolex-submariner-ref-114060.html", | |
| 198 | + "color": "Stainless Steel", | |
| 199 | + "price": 12995, | |
| 200 | + "currency": "USD", | |
| 201 | + "availability": "InStock", | |
| 202 | + "image": "[object Object]", | |
| 203 | + "condition": "UsedCondition" | |
| 204 | + }, | |
| 205 | + { | |
| 206 | + "name": "Used Rolex Submariner Two Tone Bluesy Ref 16613", | |
| 207 | + "mpn": "16613", | |
| 208 | + "sku": "192841", | |
| 209 | + "url": "https://www.bobswatches.com/used-rolex-submariner-two-tone-bluesy-ref-16613.html", | |
| 210 | + "color": "Steel and Gold", | |
| 211 | + "price": 14595, | |
| 212 | + "currency": "USD", | |
| 213 | + "availability": "InStock", | |
| 214 | + "image": "[object Object]", | |
| 215 | + "condition": "UsedCondition" | |
| 216 | + }, | |
| 217 | + { | |
| 218 | + "name": "Pre-Owned Two Tone Bluesy Rolex Submariner Ref 16613", | |
| 219 | + "mpn": "16613", | |
| 220 | + "sku": "191307", | |
| 221 | + "url": "https://www.bobswatches.com/pre-owned-two-tone-bluesy-rolex-submariner-ref-16613.html", | |
| 222 | + "color": "Steel and Gold", | |
| 223 | + "price": 13995, | |
| 224 | + "currency": "USD", | |
| 225 | + "availability": "InStock", | |
| 226 | + "image": "[object Object]", | |
| 227 | + "condition": "UsedCondition" | |
| 228 | + }, | |
| 229 | + { | |
| 230 | + "name": "Used Two Tone Bluesy Rolex Submariner Ref 16613", | |
| 231 | + "mpn": "16613", | |
| 232 | + "sku": "192875", | |
| 233 | + "url": "https://www.bobswatches.com/used-two-tone-bluesy-rolex-submariner-ref-16613.html", | |
| 234 | + "color": "Steel and Gold", | |
| 235 | + "price": 14595, | |
| 236 | + "currency": "USD", | |
| 237 | + "availability": "InStock", | |
| 238 | + "image": "[object Object]", | |
| 239 | + "condition": "UsedCondition" | |
| 240 | + }, | |
| 241 | + { | |
| 242 | + "name": "Pre-Owned 18k White Gold Rolex Submariner Ref 126619LB Cookie Monster", | |
| 243 | + "mpn": "126619LB", | |
| 244 | + "sku": "191434", | |
| 245 | + "url": "https://www.bobswatches.com/pre-owned-18k-white-gold-rolex-submariner-ref-126619lb-cookie-monster.html", | |
| 246 | + "color": "White Gold", | |
| 247 | + "price": 37595, | |
| 248 | + "currency": "USD", | |
| 249 | + "availability": "InStock", | |
| 250 | + "image": "[object Object]", | |
| 251 | + "condition": "UsedCondition" | |
| 252 | + }, | |
| 253 | + { | |
| 254 | + "name": "Used Oyster Rolex Submariner Ref 16610 Black Dial", | |
| 255 | + "mpn": "16610", | |
| 256 | + "sku": "191273", | |
| 257 | + "url": "https://www.bobswatches.com/used-oyster-rolex-submariner-ref-16610-black-dial.html", | |
| 258 | + "color": "Stainless Steel", | |
| 259 | + "price": 10595, | |
| 260 | + "currency": "USD", | |
| 261 | + "availability": "InStock", | |
| 262 | + "image": "[object Object]", | |
| 263 | + "condition": "UsedCondition" | |
| 264 | + }, | |
| 265 | + { | |
| 266 | + "name": "190885 Used Two Tone Rolex Submariner Ref 16613 Black Dial", | |
| 267 | + "mpn": "16613", | |
| 268 | + "sku": "190885", | |
| 269 | + "url": "https://www.bobswatches.com/190885-used-two-tone-rolex-submariner-ref-16613-black-dial.html", | |
| 270 | + "color": "Steel and Gold", | |
| 271 | + "price": 13495, | |
| 272 | + "currency": "USD", | |
| 273 | + "availability": "InStock", | |
| 274 | + "image": "[object Object]", | |
| 275 | + "condition": "UsedCondition" | |
| 276 | + }, | |
| 277 | + { | |
| 278 | + "name": "Used Rolex Submariner Ref 124060 Black Chromalight Dial", | |
| 279 | + "mpn": "124060", | |
| 280 | + "sku": "190826", | |
| 281 | + "url": "https://www.bobswatches.com/used-rolex-submariner-ref-124060-black-chromalight-dial.html", | |
| 282 | + "color": "Stainless Steel", | |
| 283 | + "price": 14195, | |
| 284 | + "currency": "USD", | |
| 285 | + "availability": "InStock", | |
| 286 | + "image": "[object Object]", | |
| 287 | + "condition": "UsedCondition" | |
| 288 | + }, | |
| 289 | + { | |
| 290 | + "name": "Pre-Owned Steel Rolex Submariner 126610LN Black Dial", | |
| 291 | + "mpn": "126610", | |
| 292 | + "sku": "188326", | |
| 293 | + "url": "https://www.bobswatches.com/pre-owned-steel-rolex-submariner-126610ln-black-dial.html", | |
| 294 | + "color": "Stainless Steel", | |
| 295 | + "price": 15295, | |
| 296 | + "currency": "USD", | |
| 297 | + "availability": "InStock", | |
| 298 | + "image": "[object Object]", | |
| 299 | + "condition": "UsedCondition" | |
| 300 | + }, | |
| 301 | + { | |
| 302 | + "name": "Used Rolex Submariner No Date Ref 14060 Black Aluminum Dial", | |
| 303 | + "mpn": "14060", | |
| 304 | + "sku": "191286", | |
| 305 | + "url": "https://www.bobswatches.com/used-rolex-submariner-no-date-ref-14060-black-aluminum-dial.html", | |
| 306 | + "color": "Stainless Steel", | |
| 307 | + "price": 10595, | |
| 308 | + "currency": "USD", | |
| 309 | + "availability": "InStock", | |
| 310 | + "image": "[object Object]", | |
| 311 | + "condition": "UsedCondition" | |
| 312 | + }, | |
| 313 | + { | |
| 314 | + "name": "Pre-Owned Steel & Gold Rolex Submariner Ref 116613LB Blue Dial", | |
| 315 | + "mpn": "116613", | |
| 316 | + "sku": "192106", | |
| 317 | + "url": "https://www.bobswatches.com/pre-owned-steel-and-gold-rolex-submariner-ref-116613lb-blue-dial.html", | |
| 318 | + "color": "Steel and Gold", | |
| 319 | + "price": 15695, | |
| 320 | + "currency": "USD", | |
| 321 | + "availability": "InStock", | |
| 322 | + "image": "[object Object]", | |
| 323 | + "condition": "UsedCondition" | |
| 324 | + }, | |
| 325 | + { | |
| 326 | + "name": "Used Two Tone Rolex Submariner Blue Dial Ref 116613LB", | |
| 327 | + "mpn": "116613", | |
| 328 | + "sku": "189505", | |
| 329 | + "url": "https://www.bobswatches.com/used-two-tone-rolex-submariner-blue-dial-ref-116613lb.html", | |
| 330 | + "color": "Steel and Gold", | |
| 331 | + "price": 16995, | |
| 332 | + "currency": "USD", | |
| 333 | + "availability": "InStock", | |
| 334 | + "image": "[object Object]", | |
| 335 | + "condition": "UsedCondition" | |
| 336 | + }, | |
| 337 | + { | |
| 338 | + "name": "Pre-Owned Oyster Rolex Submariner Ref 126610 Black Chromalight Dial", | |
| 339 | + "mpn": "126610", | |
| 340 | + "sku": "191820", | |
| 341 | + "url": "https://www.bobswatches.com/pre-owned-oyster-rolex-submariner-ref-126610-black-chromalight-dial.html", | |
| 342 | + "color": "Stainless Steel", | |
| 343 | + "price": 15295, | |
| 344 | + "currency": "USD", | |
| 345 | + "availability": "InStock", | |
| 346 | + "image": "[object Object]", | |
| 347 | + "condition": "UsedCondition" | |
| 348 | + }, | |
| 349 | + { | |
| 350 | + "name": "Pre-Owned Rolex Submarine Ref 16610T Black Aluminum Bezel", | |
| 351 | + "mpn": "16610T", | |
| 352 | + "sku": "191287", | |
| 353 | + "url": "https://www.bobswatches.com/pre-owned-rolex-submarine-ref-16610t-black-aluminum-bezel.html", | |
| 354 | + "color": "Stainless Steel", | |
| 355 | + "price": 12995, | |
| 356 | + "currency": "USD", | |
| 357 | + "availability": "InStock", | |
| 358 | + "image": "[object Object]", | |
| 359 | + "condition": "UsedCondition" | |
| 360 | + }, | |
| 361 | + { | |
| 362 | + "name": "Used Oyster Rolex Submariner Black Aluminum Bezel Ref 16610", | |
| 363 | + "mpn": "16610", | |
| 364 | + "sku": "192451", | |
| 365 | + "url": "https://www.bobswatches.com/used-oyster-rolex-submariner-black-aluminum-bezel-ref-16610.html", | |
| 366 | + "color": "Stainless Steel", | |
| 367 | + "price": 10595, | |
| 368 | + "currency": "USD", | |
| 369 | + "availability": "InStock", | |
| 370 | + "image": "[object Object]", | |
| 371 | + "condition": "UsedCondition" | |
| 372 | + }, | |
| 373 | + { | |
| 374 | + "name": "Used Two Tone Rolex Submariner Ref 16613 Black Aluminum Dial", | |
| 375 | + "mpn": "16613", | |
| 376 | + "sku": "192459", | |
| 377 | + "url": "https://www.bobswatches.com/used-two-tone-rolex-submariner-ref-16613-black-aluminum-dial.html", | |
| 378 | + "color": "Steel and Gold", | |
| 379 | + "price": 13795, | |
| 380 | + "currency": "USD", | |
| 381 | + "availability": "InStock", | |
| 382 | + "image": "[object Object]", | |
| 383 | + "condition": "UsedCondition" | |
| 384 | + }, | |
| 385 | + { | |
| 386 | + "name": "Used Rolex Submarine Ref 16610 Black Aluminum Bezel", | |
| 387 | + "mpn": "16610", | |
| 388 | + "sku": "191952", | |
| 389 | + "url": "https://www.bobswatches.com/used-rolex-submarine-ref-16610-black-aluminum-bezel.html", | |
| 390 | + "color": "Stainless Steel", | |
| 391 | + "price": 10595, | |
| 392 | + "currency": "USD", | |
| 393 | + "availability": "InStock", | |
| 394 | + "image": "[object Object]", | |
| 395 | + "condition": "UsedCondition" | |
| 396 | + }, | |
| 397 | + { | |
| 398 | + "name": "Used Green Flat Four Kermit Rolex Submariner Ref 16610V", | |
| 399 | + "mpn": "16610V", | |
| 400 | + "sku": "188408", | |
| 401 | + "url": "https://www.bobswatches.com/used-green-flat-four-kermit-rolex-submariner-ref-16610v.html", | |
| 402 | + "color": "Stainless Steel", | |
| 403 | + "price": 15995, | |
| 404 | + "currency": "USD", | |
| 405 | + "availability": "InStock", | |
| 406 | + "image": "[object Object]", | |
| 407 | + "condition": "UsedCondition" | |
| 408 | + }, | |
| 409 | + { | |
| 410 | + "name": "Used Stainless Steel Rolex Submariner Black Dial Ref 16610", | |
| 411 | + "mpn": "16610", | |
| 412 | + "sku": "191984", | |
| 413 | + "url": "https://www.bobswatches.com/used-stainless-steel-rolex-submariner-black-dial-ref-16610.html", | |
| 414 | + "color": "Stainless Steel", | |
| 415 | + "price": 10595, | |
| 416 | + "currency": "USD", | |
| 417 | + "availability": "InStock", | |
| 418 | + "image": "[object Object]", | |
| 419 | + "condition": "UsedCondition" | |
| 420 | + }, | |
| 421 | + { | |
| 422 | + "name": "Used 18k White Gold Rolex Submariner 126619LB Cookie Monster", | |
| 423 | + "mpn": "126619LB", | |
| 424 | + "sku": "186599", | |
| 425 | + "url": "https://www.bobswatches.com/used-18k-white-gold-rolex-submariner-126619lb-cookie-monster.html", | |
| 426 | + "color": "White Gold", | |
| 427 | + "price": 34595, | |
| 428 | + "currency": "USD", | |
| 429 | + "availability": "InStock", | |
| 430 | + "image": "[object Object]", | |
| 431 | + "condition": "UsedCondition" | |
| 432 | + }, | |
| 433 | + { | |
| 434 | + "name": "Rolex Submariner Ref 16800 Oyster Bracelet Black Dial", | |
| 435 | + "mpn": "16800", | |
| 436 | + "sku": "189742", | |
| 437 | + "url": "https://www.bobswatches.com/rolex-submariner-ref-16800-oyster-bracelet-black-dial.html", | |
| 438 | + "color": "Stainless Steel", | |
| 439 | + "price": 10595, | |
| 440 | + "currency": "USD", | |
| 441 | + "availability": "InStock", | |
| 442 | + "image": "[object Object]", | |
| 443 | + "condition": "UsedCondition" | |
| 444 | + }, | |
| 445 | + { | |
| 446 | + "name": "Used Rolex Submariner Black Aluminum Bezel Ref 16610", | |
| 447 | + "mpn": "16610", | |
| 448 | + "sku": "192524", | |
| 449 | + "url": "https://www.bobswatches.com/used-rolex-submariner-black-aluminum-bezel-ref-16610.html", | |
| 450 | + "color": "Stainless Steel", | |
| 451 | + "price": 10595, | |
| 452 | + "currency": "USD", | |
| 453 | + "availability": "InStock", | |
| 454 | + "image": "[object Object]", | |
| 455 | + "condition": "UsedCondition" | |
| 456 | + }, | |
| 457 | + { | |
| 458 | + "name": "Used Rolex Submariner Ref 16613 Steel & 18k Yellow Gold Bluesy", | |
| 459 | + "mpn": "16613", | |
| 460 | + "sku": "190971", | |
| 461 | + "url": "https://www.bobswatches.com/used-rolex-submariner-ref-16613-steel-and-18k-yellow-gold-bluesy.html", | |
| 462 | + "color": "Steel and Gold", | |
| 463 | + "price": 13995, | |
| 464 | + "currency": "USD", | |
| 465 | + "availability": "InStock", | |
| 466 | + "image": "[object Object]", | |
| 467 | + "condition": "UsedCondition" | |
| 468 | + }, | |
| 469 | + { | |
| 470 | + "name": "Pre-Owned Rolex Submariner Bluesy Ref 16613T", | |
| 471 | + "mpn": "16613", | |
| 472 | + "sku": "192379", | |
| 473 | + "url": "https://www.bobswatches.com/pre-owned-rolex-submariner-bluesy-ref-16613t.html", | |
| 474 | + "color": "Steel and Gold", | |
| 475 | + "price": 14995, | |
| 476 | + "currency": "USD", | |
| 477 | + "availability": "InStock", | |
| 478 | + "image": "[object Object]", | |
| 479 | + "condition": "UsedCondition" | |
| 480 | + }, | |
| 481 | + { | |
| 482 | + "name": "Used Ceramic Bezel Rolex Submariner Ref 116613 Black Dial", | |
| 483 | + "mpn": "116613", | |
| 484 | + "sku": "192653", | |
| 485 | + "url": "https://www.bobswatches.com/used-ceramic-bezel-rolex-submariner-ref-116613-black-dial.html", | |
| 486 | + "color": "Steel and Gold", | |
| 487 | + "price": 15495, | |
| 488 | + "currency": "USD", | |
| 489 | + "availability": "InStock", | |
| 490 | + "image": "[object Object]", | |
| 491 | + "condition": "UsedCondition" | |
| 492 | + }, | |
| 493 | + { | |
| 494 | + "name": "Pre-Owned Stainless Steel Oyster Rolex Submariner Black Dial Ref 16610", | |
| 495 | + "mpn": "16610", | |
| 496 | + "sku": "191500", | |
| 497 | + "url": "https://www.bobswatches.com/pre-owned-stainless-steel-oyster-rolex-submariner-black-dial-ref-16610.html", | |
| 498 | + "color": "Stainless Steel", | |
| 499 | + "price": 11995, | |
| 500 | + "currency": "USD", | |
| 501 | + "availability": "InStock", | |
| 502 | + "image": "[object Object]", | |
| 503 | + "condition": "UsedCondition" | |
| 504 | + }, | |
| 505 | + { | |
| 506 | + "name": "Used Green Kermit Rolex Submariner Ref 16610V", | |
| 507 | + "mpn": "16610V", | |
| 508 | + "sku": "192458", | |
| 509 | + "url": "https://www.bobswatches.com/used-green-kermit-rolex-submariner-ref-16610v.html", | |
| 510 | + "color": "Stainless Steel", | |
| 511 | + "price": 14595, | |
| 512 | + "currency": "USD", | |
| 513 | + "availability": "InStock", | |
| 514 | + "image": "[object Object]", | |
| 515 | + "condition": "UsedCondition" | |
| 516 | + }, | |
| 517 | + { | |
| 518 | + "name": "Used Rolex Submariner Black Aluminum Dial Ref 16610", | |
| 519 | + "mpn": "16610", | |
| 520 | + "sku": "191313", | |
| 521 | + "url": "https://www.bobswatches.com/used-rolex-submariner-black-aluminum-dial-ref-16610.html", | |
| 522 | + "color": "Stainless Steel", | |
| 523 | + "price": 10595, | |
| 524 | + "currency": "USD", | |
| 525 | + "availability": "InStock", | |
| 526 | + "image": "[object Object]", | |
| 527 | + "condition": "UsedCondition" | |
| 528 | + }, | |
| 529 | + { | |
| 530 | + "name": "Used Black Dial Rolex Submariner Ref 16610 Steel Oyster", | |
| 531 | + "mpn": "16610", | |
| 532 | + "sku": "191888", | |
| 533 | + "url": "https://www.bobswatches.com/used-black-dial-rolex-submariner-ref-16610-steel-oyster.html", | |
| 534 | + "color": "Stainless Steel", | |
| 535 | + "price": 10595, | |
| 536 | + "currency": "USD", | |
| 537 | + "availability": "InStock", | |
| 538 | + "image": "[object Object]", | |
| 539 | + "condition": "UsedCondition" | |
| 540 | + }, | |
| 541 | + { | |
| 542 | + "name": "Used Rolex Submariner Ref 14060 No Date Dial", | |
| 543 | + "mpn": "14060", | |
| 544 | + "sku": "191527", | |
| 545 | + "url": "https://www.bobswatches.com/used-rolex-submariner-ref-14060-no-date-dial.html", | |
| 546 | + "color": "Stainless Steel", | |
| 547 | + "price": 10595, | |
| 548 | + "currency": "USD", | |
| 549 | + "availability": "InStock", | |
| 550 | + "image": "[object Object]", | |
| 551 | + "condition": "UsedCondition" | |
| 552 | + } | |
| 553 | + ] | |
| 554 | + } | |
| 555 | + }, | |
| 556 | + "expect": { | |
| 557 | + "minCount": 1, | |
| 558 | + "kinds": [ | |
| 559 | + "listing" | |
| 560 | + ] | |
| 561 | + }, | |
| 562 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 563 | + "capturedAt": "2026-09-07T06:28:36.373Z" | |
| 564 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/crown-caliber/brand-rolex-1.json
+1335 −0
@@ -0,0 +1,1335 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.europeanwatch.com/brand/rolex", | |
| 4 | + "externalId": "brand:rolex:1", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:38.547Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "brand_page", | |
| 10 | + "url": "https://www.europeanwatch.com/brand/rolex", | |
| 11 | + "brand": "rolex", | |
| 12 | + "page": 1, | |
| 13 | + "items": [ | |
| 14 | + { | |
| 15 | + "name": "Rolex 126234 Datejust 36 Jubilee SS Green Palm Dial", | |
| 16 | + "sku": "70343", | |
| 17 | + "url": "https://www.europeanwatch.com/watch/rolex-126234-126234-datejust-36-jubilee-ss-green-palm-dial-70343", | |
| 18 | + "price": 15950, | |
| 19 | + "currency": "USD", | |
| 20 | + "availability": "InStock", | |
| 21 | + "image": "https://images.europeanwatch.com/images/70/70343-1.jpg", | |
| 22 | + "condition": "UsedCondition" | |
| 23 | + }, | |
| 24 | + { | |
| 25 | + "name": "Rolex 16710 GMT-Master II Coke Bezel SS Black Dial Circa. 2000", | |
| 26 | + "sku": "70615", | |
| 27 | + "url": "https://www.europeanwatch.com/watch/rolex-16710-16710-gmt-master-ii-coke-bezel-ss-black-dial-circa-20-70615", | |
| 28 | + "price": 17050, | |
| 29 | + "currency": "USD", | |
| 30 | + "availability": "InStock", | |
| 31 | + "image": "https://images.europeanwatch.com/images/70/70615-1.jpg", | |
| 32 | + "condition": "UsedCondition" | |
| 33 | + }, | |
| 34 | + { | |
| 35 | + "name": "Rolex Datejust 36 Jubilee SS Fluted Roman Purple Diamonds Dail", | |
| 36 | + "sku": "70768", | |
| 37 | + "url": "https://www.europeanwatch.com/watch/rolex-126234-datejust-36-jubilee-ss-fluted-roman-purple-diamonds-70768", | |
| 38 | + "price": 16050, | |
| 39 | + "currency": "USD", | |
| 40 | + "availability": "InStock", | |
| 41 | + "image": "https://images.europeanwatch.com/images/70/70768-1.jpg", | |
| 42 | + "condition": "UsedCondition" | |
| 43 | + }, | |
| 44 | + { | |
| 45 | + "name": "Rolex 16234 Datejust 36mm SS Black Diamond Dial", | |
| 46 | + "sku": "71106", | |
| 47 | + "url": "https://www.europeanwatch.com/watch/rolex-16234-16234-datejust-36mm-ss-black-diamond-dial-71106", | |
| 48 | + "price": 8790, | |
| 49 | + "currency": "USD", | |
| 50 | + "availability": "InStock", | |
| 51 | + "image": "https://images.europeanwatch.com/images/71/71106-1.jpg", | |
| 52 | + "condition": "UsedCondition" | |
| 53 | + }, | |
| 54 | + { | |
| 55 | + "name": "Rolex 116523 Daytona Two-Tone 18K YG / SS White Diamond Dial", | |
| 56 | + "sku": "70560", | |
| 57 | + "url": "https://www.europeanwatch.com/watch/rolex-116523-116523-daytona-two-tone-18k-yg-ss-white-diamond-dial-70560", | |
| 58 | + "price": 25190, | |
| 59 | + "currency": "USD", | |
| 60 | + "availability": "InStock", | |
| 61 | + "image": "https://images.europeanwatch.com/images/70/70560-1.jpg", | |
| 62 | + "condition": "UsedCondition" | |
| 63 | + }, | |
| 64 | + { | |
| 65 | + "name": "Rolex 114060 Submariner No Date SS Black Dial", | |
| 66 | + "sku": "70726", | |
| 67 | + "url": "https://www.europeanwatch.com/watch/rolex-114060-114060-submariner-no-date-ss-black-dial-70726", | |
| 68 | + "price": 12150, | |
| 69 | + "currency": "USD", | |
| 70 | + "availability": "InStock", | |
| 71 | + "image": "https://images.europeanwatch.com/images/70/70726-1.jpg", | |
| 72 | + "condition": "UsedCondition" | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + "name": "Rolex 277200 Oyster Perpetual 31 SS Blue Dial 2025", | |
| 76 | + "sku": "70078", | |
| 77 | + "url": "https://www.europeanwatch.com/watch/rolex-277200-277200-oyster-perpetual-31-ss-blue-dial-2025-70078", | |
| 78 | + "price": 7550, | |
| 79 | + "currency": "USD", | |
| 80 | + "availability": "InStock", | |
| 81 | + "image": "https://images.europeanwatch.com/images/70/70078-1.jpg", | |
| 82 | + "condition": "UsedCondition" | |
| 83 | + }, | |
| 84 | + { | |
| 85 | + "name": "Rolex 126234 Datejust 36 SS Palm Dial", | |
| 86 | + "sku": "70408", | |
| 87 | + "url": "https://www.europeanwatch.com/watch/rolex-126234-126234-datejust-36-ss-palm-dial-70408", | |
| 88 | + "price": 14950, | |
| 89 | + "currency": "USD", | |
| 90 | + "availability": "InStock", | |
| 91 | + "image": "https://images.europeanwatch.com/images/70/70408-1.jpg", | |
| 92 | + "condition": "UsedCondition" | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + "name": "Rolex 126334 Datejust 41MM SS Green Motif Dial", | |
| 96 | + "sku": "69314", | |
| 97 | + "url": "https://www.europeanwatch.com/watch/rolex-126334-126334-datejust-41mm-ss-green-motif-dial-69314", | |
| 98 | + "price": 18050, | |
| 99 | + "currency": "USD", | |
| 100 | + "availability": "InStock", | |
| 101 | + "image": "https://images.europeanwatch.com/images/69/69314-1.jpg", | |
| 102 | + "condition": "UsedCondition" | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "name": "Rolex 277200 Oyster Perpetual 31 SS Blue Dial", | |
| 106 | + "sku": "69762", | |
| 107 | + "url": "https://www.europeanwatch.com/watch/rolex-277200-277200-oyster-perpetual-31-ss-blue-dial-69762", | |
| 108 | + "price": 7450, | |
| 109 | + "currency": "USD", | |
| 110 | + "availability": "InStock", | |
| 111 | + "image": "https://images.europeanwatch.com/images/69/69762-1.jpg", | |
| 112 | + "condition": "UsedCondition" | |
| 113 | + }, | |
| 114 | + { | |
| 115 | + "name": "Rolex 116200 Datejust SS Silver Tuxedo Dial", | |
| 116 | + "sku": "70779", | |
| 117 | + "url": "https://www.europeanwatch.com/watch/rolex-116200-116200-datejust-ss-silver-tuxedo-dial-70779", | |
| 118 | + "price": 8690, | |
| 119 | + "currency": "USD", | |
| 120 | + "availability": "InStock", | |
| 121 | + "image": "https://images.europeanwatch.com/images/70/70779-1.jpg", | |
| 122 | + "condition": "UsedCondition" | |
| 123 | + }, | |
| 124 | + { | |
| 125 | + "name": "Rolex 128238 Day-Date 36 18K Yellow Gold Mother of Pearl Diamond Dial", | |
| 126 | + "sku": "70364", | |
| 127 | + "url": "https://www.europeanwatch.com/watch/rolex-128238-128238-day-date-36-18k-yellow-gold-mother-of-pearl-d-70364", | |
| 128 | + "price": 45150, | |
| 129 | + "currency": "USD", | |
| 130 | + "availability": "InStock", | |
| 131 | + "image": "https://images.europeanwatch.com/images/70/70364-1.jpg", | |
| 132 | + "condition": "UsedCondition" | |
| 133 | + }, | |
| 134 | + { | |
| 135 | + "name": "Rolex 16613 Submariner Date 18K YG / SS Black Dial Circa. 2002", | |
| 136 | + "sku": "70658", | |
| 137 | + "url": "https://www.europeanwatch.com/watch/rolex-116613-16613-submariner-date-18k-yg-ss-black-dial-circa-200-70658", | |
| 138 | + "price": 13900, | |
| 139 | + "currency": "USD", | |
| 140 | + "availability": "InStock", | |
| 141 | + "image": "https://images.europeanwatch.com/images/70/70658-1.jpg", | |
| 142 | + "condition": "UsedCondition" | |
| 143 | + }, | |
| 144 | + { | |
| 145 | + "name": "Rolex 16570 Explorer II Polar SS White \"Chicchi Di Mais\" Dial Circa. 1991", | |
| 146 | + "sku": "71832", | |
| 147 | + "url": "https://www.europeanwatch.com/watch/rolex-16570-16570-explorer-ii-polar-ss-white-chicchi-di-mais-dial-71832", | |
| 148 | + "price": 10900, | |
| 149 | + "currency": "USD", | |
| 150 | + "availability": "InStock", | |
| 151 | + "image": "https://images.europeanwatch.com/images/71/71832-1.jpg", | |
| 152 | + "condition": "UsedCondition" | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "name": "Rolex 124270 Explorer 36MM SS Black Dial", | |
| 156 | + "sku": "69825", | |
| 157 | + "url": "https://www.europeanwatch.com/watch/rolex-124270-124270-explorer-36mm-ss-black-dial-69825", | |
| 158 | + "price": 9900, | |
| 159 | + "currency": "USD", | |
| 160 | + "availability": "OutOfStock", | |
| 161 | + "image": "https://images.europeanwatch.com/images/69/69825-1.jpg", | |
| 162 | + "condition": "UsedCondition" | |
| 163 | + }, | |
| 164 | + { | |
| 165 | + "name": "Rolex 214270 Explorer I SS Black Dial", | |
| 166 | + "sku": "71534", | |
| 167 | + "url": "https://www.europeanwatch.com/watch/rolex-214270-214270-explorer-i-ss-black-dial-71534", | |
| 168 | + "price": 8950, | |
| 169 | + "currency": "USD", | |
| 170 | + "availability": "InStock", | |
| 171 | + "image": "https://images.europeanwatch.com/images/71/71534-1.jpg", | |
| 172 | + "condition": "UsedCondition" | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "name": "Rolex 126710BLRO GMT-Master II Pepsi Bezel SS Black Dial", | |
| 176 | + "sku": "71777", | |
| 177 | + "url": "https://www.europeanwatch.com/watch/rolex-126710blro-126710blro-gmt-master-ii-pepsi-bezel-ss-black-di-71777", | |
| 178 | + "price": 26000, | |
| 179 | + "currency": "USD", | |
| 180 | + "availability": "OutOfStock", | |
| 181 | + "image": "https://images.europeanwatch.com/images/71/71777-1.jpg", | |
| 182 | + "condition": "UsedCondition" | |
| 183 | + }, | |
| 184 | + { | |
| 185 | + "name": "Rolex 15505 Vintage Oyster Perpetual Date 18K YG Capped Gold Dial Circa. 1985", | |
| 186 | + "sku": "66919", | |
| 187 | + "url": "https://www.europeanwatch.com/watch/rolex-15505-15505-vintage-oyster-perpetual-date-18k-yg-capped-gol-66919", | |
| 188 | + "price": 6950, | |
| 189 | + "currency": "USD", | |
| 190 | + "availability": "InStock", | |
| 191 | + "image": "https://images.europeanwatch.com/images/66/66919-1.jpg", | |
| 192 | + "condition": "UsedCondition" | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "name": "Rolex 127334 Land-Dweller 40MM SS White Dial 2026", | |
| 196 | + "sku": "71774", | |
| 197 | + "url": "https://www.europeanwatch.com/watch/rolex-127334-127334-land-dweller-40mm-ss-white-dial-2026-71774", | |
| 198 | + "price": 26500, | |
| 199 | + "currency": "USD", | |
| 200 | + "availability": "InStock", | |
| 201 | + "image": "https://images.europeanwatch.com/images/71/71774-1.jpg", | |
| 202 | + "condition": "UsedCondition" | |
| 203 | + }, | |
| 204 | + { | |
| 205 | + "name": "Rolex Oyster Perpetual 36mm SS \"White Grape\" Dial", | |
| 206 | + "sku": "71112", | |
| 207 | + "url": "https://www.europeanwatch.com/watch/rolex-116000-oyster-perpetual-36mm-ss-white-grape-dial-71112", | |
| 208 | + "price": 9900, | |
| 209 | + "currency": "USD", | |
| 210 | + "availability": "InStock", | |
| 211 | + "image": "https://images.europeanwatch.com/images/71/71112-1.jpg", | |
| 212 | + "condition": "UsedCondition" | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "name": "Rolex 5330/9 Cellini Cestello 18K White Gold White Roman Dial", | |
| 216 | + "sku": "71851", | |
| 217 | + "url": "https://www.europeanwatch.com/watch/rolex-5330-9-5330-9-cellini-cestello-18k-white-gold-white-roman-d-71851", | |
| 218 | + "price": 8900, | |
| 219 | + "currency": "USD", | |
| 220 | + "availability": "InStock", | |
| 221 | + "image": "https://images.europeanwatch.com/images/71/71851-1.jpg", | |
| 222 | + "condition": "UsedCondition" | |
| 223 | + }, | |
| 224 | + { | |
| 225 | + "name": "Rolex 126519LN Daytona Ceramic 18K White Gold Black Diamond Dial 2025", | |
| 226 | + "sku": "71519", | |
| 227 | + "url": "https://www.europeanwatch.com/watch/rolex-126519ln-126519ln-daytona-ceramic-18k-white-gold-black-diam-71519", | |
| 228 | + "price": 47500, | |
| 229 | + "currency": "USD", | |
| 230 | + "availability": "InStock", | |
| 231 | + "image": "https://images.europeanwatch.com/images/71/71519-1.jpg", | |
| 232 | + "condition": "UsedCondition" | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "name": "Rolex 116509 Daytona 18K White Gold Blue Dial", | |
| 236 | + "sku": "69551", | |
| 237 | + "url": "https://www.europeanwatch.com/watch/rolex-116509-116509-daytona-18k-white-gold-blue-dial-69551", | |
| 238 | + "price": 48500, | |
| 239 | + "currency": "USD", | |
| 240 | + "availability": "InStock", | |
| 241 | + "image": "https://images.europeanwatch.com/images/69/69551-1.jpg", | |
| 242 | + "condition": "UsedCondition" | |
| 243 | + }, | |
| 244 | + { | |
| 245 | + "name": "Rolex 116655 Yacht Master 40 18K Rose Gold Black Dial", | |
| 246 | + "sku": "71231", | |
| 247 | + "url": "https://www.europeanwatch.com/watch/rolex-116655-116655-yacht-master-40-18k-rose-gold-black-dial-71231", | |
| 248 | + "price": 27500, | |
| 249 | + "currency": "USD", | |
| 250 | + "availability": "OutOfStock", | |
| 251 | + "image": "https://images.europeanwatch.com/images/71/71231-1.jpg", | |
| 252 | + "condition": "UsedCondition" | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "name": "Rolex 116688 Yacht-Master II 18K Yellow Gold White Dial", | |
| 256 | + "sku": "68159", | |
| 257 | + "url": "https://www.europeanwatch.com/watch/rolex-116688-116688-yacht-master-ii-18k-yellow-gold-white-dial-68159", | |
| 258 | + "price": 36500, | |
| 259 | + "currency": "USD", | |
| 260 | + "availability": "InStock", | |
| 261 | + "image": "https://images.europeanwatch.com/images/68/68159-1.jpg", | |
| 262 | + "condition": "UsedCondition" | |
| 263 | + }, | |
| 264 | + { | |
| 265 | + "name": "Rolex 52506 1908 Platinum Ice Blue Guilloche Dial 2025", | |
| 266 | + "sku": "71945", | |
| 267 | + "url": "https://www.europeanwatch.com/watch/rolex-52506-52506-1908-platinum-ice-blue-guilloche-dial-2025-71945", | |
| 268 | + "price": 42500, | |
| 269 | + "currency": "USD", | |
| 270 | + "availability": "InStock", | |
| 271 | + "image": "https://images.europeanwatch.com/images/71/71945-1.jpg", | |
| 272 | + "condition": "UsedCondition" | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "name": "Rolex 116509 Daytona 18K White Gold Blue Dial", | |
| 276 | + "sku": "71409", | |
| 277 | + "url": "https://www.europeanwatch.com/watch/rolex-116509-116509-daytona-18k-white-gold-blue-dial-71409", | |
| 278 | + "price": 52500, | |
| 279 | + "currency": "USD", | |
| 280 | + "availability": "InStock", | |
| 281 | + "image": "https://images.europeanwatch.com/images/71/71409-1.jpg", | |
| 282 | + "condition": "UsedCondition" | |
| 283 | + }, | |
| 284 | + { | |
| 285 | + "name": "Rolex 116518LN Daytona \"Pikachu\" 18K Yellow Gold Champagne Dial", | |
| 286 | + "sku": "71647", | |
| 287 | + "url": "https://www.europeanwatch.com/watch/rolex-116518ln-116518ln-daytona-pikachu-18k-yellow-gold-champagne-71647", | |
| 288 | + "price": 52500, | |
| 289 | + "currency": "USD", | |
| 290 | + "availability": "InStock", | |
| 291 | + "image": "https://images.europeanwatch.com/images/71/71647-1.jpg", | |
| 292 | + "condition": "UsedCondition" | |
| 293 | + }, | |
| 294 | + { | |
| 295 | + "name": "Rolex 228238 Day-Date 40 18K Yellow Gold Black Motif Dial", | |
| 296 | + "sku": "71505", | |
| 297 | + "url": "https://www.europeanwatch.com/watch/rolex-228238-228238-day-date-40-18k-yellow-gold-black-motif-dial-71505", | |
| 298 | + "price": 48500, | |
| 299 | + "currency": "USD", | |
| 300 | + "availability": "OutOfStock", | |
| 301 | + "image": "https://images.europeanwatch.com/images/71/71505-1.jpg", | |
| 302 | + "condition": "UsedCondition" | |
| 303 | + }, | |
| 304 | + { | |
| 305 | + "name": "Rolex 126331 Datejust 41 18K RG / SS \"Wimbledon\" Dial", | |
| 306 | + "sku": "70703", | |
| 307 | + "url": "https://www.europeanwatch.com/watch/rolex-126331-126331-datejust-41-18k-rg-ss-wimbledon-dial-70703", | |
| 308 | + "price": 17050, | |
| 309 | + "currency": "USD", | |
| 310 | + "availability": "InStock", | |
| 311 | + "image": "https://images.europeanwatch.com/images/70/70703-1.jpg", | |
| 312 | + "condition": "UsedCondition" | |
| 313 | + }, | |
| 314 | + { | |
| 315 | + "name": "Rolex 124300 Oyster Perpetual SS Green Dial", | |
| 316 | + "sku": "71936", | |
| 317 | + "url": "https://www.europeanwatch.com/watch/rolex-124300-124300-oyster-perpetual-ss-green-dial-71936", | |
| 318 | + "price": 10900, | |
| 319 | + "currency": "USD", | |
| 320 | + "availability": "InStock", | |
| 321 | + "image": "https://images.europeanwatch.com/images/71/71936-1.jpg", | |
| 322 | + "condition": "UsedCondition" | |
| 323 | + }, | |
| 324 | + { | |
| 325 | + "name": "Rolex 16520 Daytona SS Black Dial Circa. 1998", | |
| 326 | + "sku": "71274", | |
| 327 | + "url": "https://www.europeanwatch.com/watch/rolex-16520-16520-daytona-ss-black-dial-circa-1998-71274", | |
| 328 | + "price": 29900, | |
| 329 | + "currency": "USD", | |
| 330 | + "availability": "InStock", | |
| 331 | + "image": "https://images.europeanwatch.com/images/71/71274-1.jpg", | |
| 332 | + "condition": "UsedCondition" | |
| 333 | + }, | |
| 334 | + { | |
| 335 | + "name": "Rolex 116500LN Daytona \"Panda\" Ceramic SS White Dial", | |
| 336 | + "sku": "71196", | |
| 337 | + "url": "https://www.europeanwatch.com/watch/rolex-116500ln-116500ln-daytona-panda-ceramic-ss-white-dial-71196", | |
| 338 | + "price": 33900, | |
| 339 | + "currency": "USD", | |
| 340 | + "availability": "InStock", | |
| 341 | + "image": "https://images.europeanwatch.com/images/71/71196-1.jpg", | |
| 342 | + "condition": "UsedCondition" | |
| 343 | + }, | |
| 344 | + { | |
| 345 | + "name": "Rolex 326939 Sky Dweller 18K White Gold Silver Roman Dial", | |
| 346 | + "sku": "71790", | |
| 347 | + "url": "https://www.europeanwatch.com/watch/rolex-326939-326939-sky-dweller-18k-white-gold-silver-roman-dial-71790", | |
| 348 | + "price": 35900, | |
| 349 | + "currency": "USD", | |
| 350 | + "availability": "InStock", | |
| 351 | + "image": "https://images.europeanwatch.com/images/71/71790-1.jpg", | |
| 352 | + "condition": "UsedCondition" | |
| 353 | + }, | |
| 354 | + { | |
| 355 | + "name": "Rolex 14060 Submariner No Date SS Black Dial Circa. 1991", | |
| 356 | + "sku": "70055", | |
| 357 | + "url": "https://www.europeanwatch.com/watch/rolex-14060-14060-submariner-no-date-ss-black-dial-circa-1991-70055", | |
| 358 | + "price": 11050, | |
| 359 | + "currency": "USD", | |
| 360 | + "availability": "InStock", | |
| 361 | + "image": "https://images.europeanwatch.com/images/70/70055-1.jpg", | |
| 362 | + "condition": "UsedCondition" | |
| 363 | + }, | |
| 364 | + { | |
| 365 | + "name": "Rolex 116505 Daytona 18K Rose Gold Rose Dial", | |
| 366 | + "sku": "67810", | |
| 367 | + "url": "https://www.europeanwatch.com/watch/rolex-116505-116505-daytona-18k-rose-gold-rose-dial-67810", | |
| 368 | + "price": 45550, | |
| 369 | + "currency": "USD", | |
| 370 | + "availability": "InStock", | |
| 371 | + "image": "https://images.europeanwatch.com/images/67/67810-1.jpg", | |
| 372 | + "condition": "UsedCondition" | |
| 373 | + }, | |
| 374 | + { | |
| 375 | + "name": "Rolex 116203 Datejust 36 18K YG / SS Gray Roman Dial", | |
| 376 | + "sku": "68758", | |
| 377 | + "url": "https://www.europeanwatch.com/watch/rolex-116203-116203-datejust-36-18k-yg-ss-gray-roman-dial-68758", | |
| 378 | + "price": 9900, | |
| 379 | + "currency": "USD", | |
| 380 | + "availability": "InStock", | |
| 381 | + "image": "https://images.europeanwatch.com/images/68/68758-1.jpg", | |
| 382 | + "condition": "UsedCondition" | |
| 383 | + }, | |
| 384 | + { | |
| 385 | + "name": "Rolex 126200 Datejust 36 SS Silver Dial", | |
| 386 | + "sku": "70330", | |
| 387 | + "url": "https://www.europeanwatch.com/watch/rolex-126200-126200-datejust-36-ss-silver-dial-70330", | |
| 388 | + "price": 9650, | |
| 389 | + "currency": "USD", | |
| 390 | + "availability": "InStock", | |
| 391 | + "image": "https://images.europeanwatch.com/images/70/70330-1.jpg", | |
| 392 | + "condition": "UsedCondition" | |
| 393 | + }, | |
| 394 | + { | |
| 395 | + "name": "Rolex 16220 Datejust SS Blue Dial", | |
| 396 | + "sku": "70527", | |
| 397 | + "url": "https://www.europeanwatch.com/watch/rolex-16220-16220-datejust-ss-blue-dial-70527", | |
| 398 | + "price": 8450, | |
| 399 | + "currency": "USD", | |
| 400 | + "availability": "InStock", | |
| 401 | + "image": "https://images.europeanwatch.com/images/70/70527-1.jpg", | |
| 402 | + "condition": "UsedCondition" | |
| 403 | + }, | |
| 404 | + { | |
| 405 | + "name": "Rolex 116400GV Milgauss Green Crystal SS Black Dial Circa. 2010", | |
| 406 | + "sku": "71537", | |
| 407 | + "url": "https://www.europeanwatch.com/watch/rolex-116400gv-116400gv-milgauss-green-crystal-ss-black-dial-circ-71537", | |
| 408 | + "price": 10500, | |
| 409 | + "currency": "USD", | |
| 410 | + "availability": "OutOfStock", | |
| 411 | + "image": "https://images.europeanwatch.com/images/71/71537-1.jpg", | |
| 412 | + "condition": "UsedCondition" | |
| 413 | + }, | |
| 414 | + { | |
| 415 | + "name": "Rolex 116400 Milgauss SS White Dial", | |
| 416 | + "sku": "71554", | |
| 417 | + "url": "https://www.europeanwatch.com/watch/rolex-116400-116400-milgauss-ss-white-dial-71554", | |
| 418 | + "price": 10650, | |
| 419 | + "currency": "USD", | |
| 420 | + "availability": "OutOfStock", | |
| 421 | + "image": "https://images.europeanwatch.com/images/71/71554-1.jpg", | |
| 422 | + "condition": "UsedCondition" | |
| 423 | + }, | |
| 424 | + { | |
| 425 | + "name": "Rolex 336238 Sky-Dweller 18K Yellow Gold Green Dial 2025", | |
| 426 | + "sku": "70302", | |
| 427 | + "url": "https://www.europeanwatch.com/watch/rolex-336238-336238-sky-dweller-18k-yellow-gold-green-dial-2025-70302", | |
| 428 | + "price": 50150, | |
| 429 | + "currency": "USD", | |
| 430 | + "availability": "InStock", | |
| 431 | + "image": "https://images.europeanwatch.com/images/70/70302-1.jpg", | |
| 432 | + "condition": "UsedCondition" | |
| 433 | + }, | |
| 434 | + { | |
| 435 | + "name": "Rolex 116508 Daytona \"John Mayer\" 18K Yellow Gold Green Dial", | |
| 436 | + "sku": "70354", | |
| 437 | + "url": "https://www.europeanwatch.com/watch/rolex-116508-116508-daytona-john-mayer-18k-yellow-gold-green-dial-70354", | |
| 438 | + "price": 79050, | |
| 439 | + "currency": "USD", | |
| 440 | + "availability": "InStock", | |
| 441 | + "image": "https://images.europeanwatch.com/images/70/70354-1.jpg", | |
| 442 | + "condition": "UsedCondition" | |
| 443 | + }, | |
| 444 | + { | |
| 445 | + "name": "Rolex 1603 Vintage Datejust SS Gray Dial Circa. 1974", | |
| 446 | + "sku": "71137", | |
| 447 | + "url": "https://www.europeanwatch.com/watch/rolex-1603-1603-vintage-datejust-ss-gray-dial-circa-1974-71137", | |
| 448 | + "price": 5950, | |
| 449 | + "currency": "USD", | |
| 450 | + "availability": "InStock", | |
| 451 | + "image": "https://images.europeanwatch.com/images/71/71137-1.jpg", | |
| 452 | + "condition": "UsedCondition" | |
| 453 | + }, | |
| 454 | + { | |
| 455 | + "name": "Rolex 128155RBR Day-Date 36 Diamonds 18K Rose Gold Pave Dial", | |
| 456 | + "sku": "71492", | |
| 457 | + "url": "https://www.europeanwatch.com/watch/rolex-128155rbr-128155rbr-day-date-36-diamonds-18k-rose-gold-pave-71492", | |
| 458 | + "price": 82500, | |
| 459 | + "currency": "USD", | |
| 460 | + "availability": "InStock", | |
| 461 | + "image": "https://images.europeanwatch.com/images/71/71492-1.jpg", | |
| 462 | + "condition": "UsedCondition" | |
| 463 | + }, | |
| 464 | + { | |
| 465 | + "name": "Rolex 116719BLRO GMT-Master II \"Pepsi\" 18K White Gold Black Dial", | |
| 466 | + "sku": "71553", | |
| 467 | + "url": "https://www.europeanwatch.com/watch/rolex-116719blro-116719blro-gmt-master-ii-pepsi-18k-white-gold-bl-71553", | |
| 468 | + "price": 39500, | |
| 469 | + "currency": "USD", | |
| 470 | + "availability": "InStock", | |
| 471 | + "image": "https://images.europeanwatch.com/images/71/71553-1.jpg", | |
| 472 | + "condition": "UsedCondition" | |
| 473 | + }, | |
| 474 | + { | |
| 475 | + "name": "Rolex 228239 Day-Date 40 18K White Gold Black Baguette Dial", | |
| 476 | + "sku": "71722", | |
| 477 | + "url": "https://www.europeanwatch.com/watch/rolex-228239-228239-day-date-40-18k-white-gold-black-baguette-dia-71722", | |
| 478 | + "price": 55900, | |
| 479 | + "currency": "USD", | |
| 480 | + "availability": "InStock", | |
| 481 | + "image": "https://images.europeanwatch.com/images/71/71722-1.jpg", | |
| 482 | + "condition": "UsedCondition" | |
| 483 | + }, | |
| 484 | + { | |
| 485 | + "name": "Rolex 218238 Day-Date II 18K Yellow Gold Champagne Dial", | |
| 486 | + "sku": "71564", | |
| 487 | + "url": "https://www.europeanwatch.com/watch/rolex-218238-218238-day-date-ii-18k-yellow-gold-champagne-dial-71564", | |
| 488 | + "price": 42500, | |
| 489 | + "currency": "USD", | |
| 490 | + "availability": "InStock", | |
| 491 | + "image": "https://images.europeanwatch.com/images/71/71564-1.jpg", | |
| 492 | + "condition": "UsedCondition" | |
| 493 | + }, | |
| 494 | + { | |
| 495 | + "name": "Rolex 116688 Yacht-Master II 18K Yellow Gold White Dial", | |
| 496 | + "sku": "71674", | |
| 497 | + "url": "https://www.europeanwatch.com/watch/rolex-116688-116688-yacht-master-ii-18k-yellow-gold-white-dial-71674", | |
| 498 | + "price": 39500, | |
| 499 | + "currency": "USD", | |
| 500 | + "availability": "InStock", | |
| 501 | + "image": "https://images.europeanwatch.com/images/71/71674-1.jpg", | |
| 502 | + "condition": "UsedCondition" | |
| 503 | + }, | |
| 504 | + { | |
| 505 | + "name": "Rolex 326934 Sky-Dweller SS Blue Dial", | |
| 506 | + "sku": "71124", | |
| 507 | + "url": "https://www.europeanwatch.com/watch/rolex-326934-326934-sky-dweller-ss-blue-dial-71124", | |
| 508 | + "price": 20900, | |
| 509 | + "currency": "USD", | |
| 510 | + "availability": "InStock", | |
| 511 | + "image": "https://images.europeanwatch.com/images/71/71124-1.jpg", | |
| 512 | + "condition": "UsedCondition" | |
| 513 | + }, | |
| 514 | + { | |
| 515 | + "name": "Rolex 118238 Day-Date President 18K Yellow Gold Silver Dial", | |
| 516 | + "sku": "71496", | |
| 517 | + "url": "https://www.europeanwatch.com/watch/rolex-118238-118238-day-date-president-18k-yellow-gold-silver-dia-71496", | |
| 518 | + "price": 32500, | |
| 519 | + "currency": "USD", | |
| 520 | + "availability": "InStock", | |
| 521 | + "image": "https://images.europeanwatch.com/images/71/71496-1.jpg", | |
| 522 | + "condition": "UsedCondition" | |
| 523 | + }, | |
| 524 | + { | |
| 525 | + "name": "Rolex 116578SACO Daytona 18K Yellow Gold MOP Orange Sapphire", | |
| 526 | + "sku": "70750", | |
| 527 | + "url": "https://www.europeanwatch.com/watch/rolex-116578saco-116578saco-daytona-18k-yellow-gold-mop-orange-sa-70750", | |
| 528 | + "price": 249000, | |
| 529 | + "currency": "USD", | |
| 530 | + "availability": "InStock", | |
| 531 | + "image": "https://images.europeanwatch.com/images/70/70750-1.jpg", | |
| 532 | + "condition": "UsedCondition" | |
| 533 | + }, | |
| 534 | + { | |
| 535 | + "name": "Rolex 126718GRNR GMT-Master II 18K Yellow Gold Black Dial", | |
| 536 | + "sku": "70551", | |
| 537 | + "url": "https://www.europeanwatch.com/watch/rolex-126718grnr-126718grnr-gmt-master-ii-18k-yellow-gold-black-d-70551", | |
| 538 | + "price": 48500, | |
| 539 | + "currency": "USD", | |
| 540 | + "availability": "InStock", | |
| 541 | + "image": "https://images.europeanwatch.com/images/70/70551-1.jpg", | |
| 542 | + "condition": "UsedCondition" | |
| 543 | + }, | |
| 544 | + { | |
| 545 | + "name": "Rolex 126234 Datejust 36 Jubilee SS Gradient Green Dial 2026 UNWORN", | |
| 546 | + "sku": "71118", | |
| 547 | + "url": "https://www.europeanwatch.com/watch/rolex-126234-126234-datejust-36-jubilee-ss-gradient-green-dial-20-71118", | |
| 548 | + "price": 16650, | |
| 549 | + "currency": "USD", | |
| 550 | + "availability": "InStock", | |
| 551 | + "image": "https://images.europeanwatch.com/images/71/71118-1.jpg", | |
| 552 | + "condition": "UsedCondition" | |
| 553 | + }, | |
| 554 | + { | |
| 555 | + "name": "Rolex 126333 Datejust 41 18K YG / SS Black Diamond Dial", | |
| 556 | + "sku": "70247", | |
| 557 | + "url": "https://www.europeanwatch.com/watch/rolex-126333-126333-datejust-41-18k-yg-ss-black-diamond-dial-70247", | |
| 558 | + "price": 17450, | |
| 559 | + "currency": "USD", | |
| 560 | + "availability": "InStock", | |
| 561 | + "image": "https://images.europeanwatch.com/images/70/70247-1.jpg", | |
| 562 | + "condition": "UsedCondition" | |
| 563 | + }, | |
| 564 | + { | |
| 565 | + "name": "Rolex 226659 Yacht-Master 18K White Gold Black Dial", | |
| 566 | + "sku": "71503", | |
| 567 | + "url": "https://www.europeanwatch.com/watch/rolex-226659-226659-yacht-master-18k-white-gold-black-dial-71503", | |
| 568 | + "price": 30500, | |
| 569 | + "currency": "USD", | |
| 570 | + "availability": "InStock", | |
| 571 | + "image": "https://images.europeanwatch.com/images/71/71503-1.jpg", | |
| 572 | + "condition": "UsedCondition" | |
| 573 | + }, | |
| 574 | + { | |
| 575 | + "name": "Rolex 126619LB Submariner Date 18K White Gold Black Dial", | |
| 576 | + "sku": "71632", | |
| 577 | + "url": "https://www.europeanwatch.com/watch/rolex-126619lb-126619lb-submariner-date-18k-white-gold-black-dial-71632", | |
| 578 | + "price": 37500, | |
| 579 | + "currency": "USD", | |
| 580 | + "availability": "InStock", | |
| 581 | + "image": "https://images.europeanwatch.com/images/71/71632-1.jpg", | |
| 582 | + "condition": "UsedCondition" | |
| 583 | + }, | |
| 584 | + { | |
| 585 | + "name": "Rolex 16600 Sea-Dweller SS Black Dial Circa. 1991", | |
| 586 | + "sku": "70334", | |
| 587 | + "url": "https://www.europeanwatch.com/watch/rolex-16600-16600-sea-dweller-ss-black-dial-circa-1991-70334", | |
| 588 | + "price": 10900, | |
| 589 | + "currency": "USD", | |
| 590 | + "availability": "InStock", | |
| 591 | + "image": "https://images.europeanwatch.com/images/70/70334-1.jpg", | |
| 592 | + "condition": "UsedCondition" | |
| 593 | + }, | |
| 594 | + { | |
| 595 | + "name": "Rolex 276200 Oyster Perpetual 28MM SS Blue Dial", | |
| 596 | + "sku": "66742", | |
| 597 | + "url": "https://www.europeanwatch.com/watch/rolex-276200-276200-oyster-perpetual-28mm-ss-blue-dial-66742", | |
| 598 | + "price": 6980, | |
| 599 | + "currency": "USD", | |
| 600 | + "availability": "InStock", | |
| 601 | + "image": "https://images.europeanwatch.com/images/66/66742-1.jpg", | |
| 602 | + "condition": "UsedCondition" | |
| 603 | + }, | |
| 604 | + { | |
| 605 | + "name": "Rolex 326935 Sky-Dweller 18K Rose Gold Silver Dial", | |
| 606 | + "sku": "70890", | |
| 607 | + "url": "https://www.europeanwatch.com/watch/rolex-326935-326935-sky-dweller-18k-rose-gold-silver-dial-70890", | |
| 608 | + "price": 44650, | |
| 609 | + "currency": "USD", | |
| 610 | + "availability": "InStock", | |
| 611 | + "image": "https://images.europeanwatch.com/images/70/70890-1.jpg", | |
| 612 | + "condition": "UsedCondition" | |
| 613 | + }, | |
| 614 | + { | |
| 615 | + "name": "Rolex 16610LV Kermit Submariner Date SS Black Dial Circa. 2003", | |
| 616 | + "sku": "71690", | |
| 617 | + "url": "https://www.europeanwatch.com/watch/rolex-16610lv-16610lv-kermit-submariner-date-ss-black-dial-circa-71690", | |
| 618 | + "price": 22500, | |
| 619 | + "currency": "USD", | |
| 620 | + "availability": "InStock", | |
| 621 | + "image": "https://images.europeanwatch.com/images/71/71690-1.jpg", | |
| 622 | + "condition": "UsedCondition" | |
| 623 | + }, | |
| 624 | + { | |
| 625 | + "name": "Rolex 228239 Day-Date 40MM 18K White Gold Black Dial", | |
| 626 | + "sku": "70868", | |
| 627 | + "url": "https://www.europeanwatch.com/watch/rolex-228239-228239-day-date-40mm-18k-white-gold-black-dial-70868", | |
| 628 | + "price": 45550, | |
| 629 | + "currency": "USD", | |
| 630 | + "availability": "InStock", | |
| 631 | + "image": "https://images.europeanwatch.com/images/70/70868-1.jpg", | |
| 632 | + "condition": "UsedCondition" | |
| 633 | + }, | |
| 634 | + { | |
| 635 | + "name": "Rolex 116200 Datejust SS Silver Tuxedo Dial Dial Circa. 2008", | |
| 636 | + "sku": "71330", | |
| 637 | + "url": "https://www.europeanwatch.com/watch/rolex-116200-116200-datejust-ss-silver-tuxedo-dial-dial-circa-200-71330", | |
| 638 | + "price": 9350, | |
| 639 | + "currency": "USD", | |
| 640 | + "availability": "InStock", | |
| 641 | + "image": "https://images.europeanwatch.com/images/71/71330-1.jpg", | |
| 642 | + "condition": "UsedCondition" | |
| 643 | + }, | |
| 644 | + { | |
| 645 | + "name": "Rolex 50535 Cellini Moonphase 18K Rose Gold White Dial", | |
| 646 | + "sku": "71716", | |
| 647 | + "url": "https://www.europeanwatch.com/watch/rolex-50535-50535-cellini-moonphase-18k-rose-gold-white-dial-71716", | |
| 648 | + "price": 29900, | |
| 649 | + "currency": "USD", | |
| 650 | + "availability": "InStock", | |
| 651 | + "image": "https://images.europeanwatch.com/images/71/71716-1.jpg", | |
| 652 | + "condition": "UsedCondition" | |
| 653 | + }, | |
| 654 | + { | |
| 655 | + "name": "Rolex 126900 Air-King SS Black Dial", | |
| 656 | + "sku": "71077", | |
| 657 | + "url": "https://www.europeanwatch.com/watch/rolex-126900-126900-air-king-ss-black-dial-71077", | |
| 658 | + "price": 9650, | |
| 659 | + "currency": "USD", | |
| 660 | + "availability": "InStock", | |
| 661 | + "image": "https://images.europeanwatch.com/images/71/71077-1.jpg", | |
| 662 | + "condition": "UsedCondition" | |
| 663 | + }, | |
| 664 | + { | |
| 665 | + "name": "Rolex 116000 Oyster Perpetual \"Harley Davidson\" SS Black Dial", | |
| 666 | + "sku": "71500", | |
| 667 | + "url": "https://www.europeanwatch.com/watch/rolex-116000-116000-oyster-perpetual-harley-davidson-ss-black-dia-71500", | |
| 668 | + "price": 7850, | |
| 669 | + "currency": "USD", | |
| 670 | + "availability": "InStock", | |
| 671 | + "image": "https://images.europeanwatch.com/images/71/71500-1.jpg", | |
| 672 | + "condition": "UsedCondition" | |
| 673 | + }, | |
| 674 | + { | |
| 675 | + "name": "Rolex 16200 Datejust 36 SS Black Dial Circa. 1997", | |
| 676 | + "sku": "71341", | |
| 677 | + "url": "https://www.europeanwatch.com/watch/rolex-16200-16200-datejust-36-ss-black-dial-circa-1997-71341", | |
| 678 | + "price": 6590, | |
| 679 | + "currency": "USD", | |
| 680 | + "availability": "InStock", | |
| 681 | + "image": "https://images.europeanwatch.com/images/71/71341-1.jpg", | |
| 682 | + "condition": "UsedCondition" | |
| 683 | + }, | |
| 684 | + { | |
| 685 | + "name": "Rolex 14010 Air-King SS Black Dial Circa. 1997", | |
| 686 | + "sku": "70960", | |
| 687 | + "url": "https://www.europeanwatch.com/watch/rolex-14010-14010-air-king-ss-black-dial-circa-1997-70960", | |
| 688 | + "price": 5950, | |
| 689 | + "currency": "USD", | |
| 690 | + "availability": "InStock", | |
| 691 | + "image": "https://images.europeanwatch.com/images/70/70960-1.jpg", | |
| 692 | + "condition": "UsedCondition" | |
| 693 | + }, | |
| 694 | + { | |
| 695 | + "name": "Rolex 16233 Datejust 18K YG / SS Gray Dial Circa. 2000", | |
| 696 | + "sku": "70857", | |
| 697 | + "url": "https://www.europeanwatch.com/watch/rolex-16233-16233-datejust-18k-yg-ss-gray-dial-circa-2000-70857", | |
| 698 | + "price": 6950, | |
| 699 | + "currency": "USD", | |
| 700 | + "availability": "InStock", | |
| 701 | + "image": "https://images.europeanwatch.com/images/70/70857-1.jpg", | |
| 702 | + "condition": "UsedCondition" | |
| 703 | + }, | |
| 704 | + { | |
| 705 | + "name": "Rolex 16628 Yacht Master 18K Yellow Gold Sunburst Blue Dial Circa. 2003", | |
| 706 | + "sku": "69797", | |
| 707 | + "url": "https://www.europeanwatch.com/watch/rolex-16628-16628-yacht-master-18k-yellow-gold-sunburst-blue-dial-69797", | |
| 708 | + "price": 26990, | |
| 709 | + "currency": "USD", | |
| 710 | + "availability": "InStock", | |
| 711 | + "image": "https://images.europeanwatch.com/images/69/69797-1.jpg", | |
| 712 | + "condition": "UsedCondition" | |
| 713 | + }, | |
| 714 | + { | |
| 715 | + "name": "Rolex 126301-0003 Datejust 41 SS / 18K RG Diamond Tobacco Brown Dial", | |
| 716 | + "sku": "70378", | |
| 717 | + "url": "https://www.europeanwatch.com/watch/rolex-126301-0003-126301-0003-datejust-41-ss-18k-rg-diamond-tobac-70378", | |
| 718 | + "price": 14750, | |
| 719 | + "currency": "USD", | |
| 720 | + "availability": "InStock", | |
| 721 | + "image": "https://images.europeanwatch.com/images/70/70378-1.jpg", | |
| 722 | + "condition": "UsedCondition" | |
| 723 | + }, | |
| 724 | + { | |
| 725 | + "name": "Rolex 16808 Submariner Date 18K Yellow Gold Blue Dial", | |
| 726 | + "sku": "69690", | |
| 727 | + "url": "https://www.europeanwatch.com/watch/rolex-1680-8-16808-submariner-date-18k-yellow-gold-blue-dial-69690", | |
| 728 | + "price": 31550, | |
| 729 | + "currency": "USD", | |
| 730 | + "availability": "OutOfStock", | |
| 731 | + "image": "https://images.europeanwatch.com/images/69/69690-1.jpg", | |
| 732 | + "condition": "UsedCondition" | |
| 733 | + }, | |
| 734 | + { | |
| 735 | + "name": "Rolex 6827 Datejust 31 18K Yellow Gold Champagne Dial Circa. 1978", | |
| 736 | + "sku": "69021", | |
| 737 | + "url": "https://www.europeanwatch.com/watch/rolex-6827-6827-datejust-31-18k-yellow-gold-champagne-dial-circa-69021", | |
| 738 | + "price": 17050, | |
| 739 | + "currency": "USD", | |
| 740 | + "availability": "InStock", | |
| 741 | + "image": "https://images.europeanwatch.com/images/69/69021-1.jpg", | |
| 742 | + "condition": "UsedCondition" | |
| 743 | + }, | |
| 744 | + { | |
| 745 | + "name": "Rolex 126234 Datejust 36 SS Palm Dial", | |
| 746 | + "sku": "71270", | |
| 747 | + "url": "https://www.europeanwatch.com/watch/rolex-126234-126234-datejust-36-ss-palm-dial-71270", | |
| 748 | + "price": 15900, | |
| 749 | + "currency": "USD", | |
| 750 | + "availability": "InStock", | |
| 751 | + "image": "https://images.europeanwatch.com/images/71/71270-1.jpg", | |
| 752 | + "condition": "UsedCondition" | |
| 753 | + }, | |
| 754 | + { | |
| 755 | + "name": "Rolex 336934 Sky-Dweller Jubilee SS Silver Dial 2026 STICKERED", | |
| 756 | + "sku": "71622", | |
| 757 | + "url": "https://www.europeanwatch.com/watch/rolex-336934-336934-sky-dweller-jubilee-ss-silver-dial-2026-stick-71622", | |
| 758 | + "price": 23900, | |
| 759 | + "currency": "USD", | |
| 760 | + "availability": "InStock", | |
| 761 | + "image": "https://images.europeanwatch.com/images/71/71622-1.jpg", | |
| 762 | + "condition": "UsedCondition" | |
| 763 | + }, | |
| 764 | + { | |
| 765 | + "name": "Rolex 226659 Yacht-Master 18K White Gold Black Dial", | |
| 766 | + "sku": "71779", | |
| 767 | + "url": "https://www.europeanwatch.com/watch/rolex-226659-226659-yacht-master-18k-white-gold-black-dial-71779", | |
| 768 | + "price": 31900, | |
| 769 | + "currency": "USD", | |
| 770 | + "availability": "InStock", | |
| 771 | + "image": "https://images.europeanwatch.com/images/71/71779-1.jpg", | |
| 772 | + "condition": "UsedCondition" | |
| 773 | + }, | |
| 774 | + { | |
| 775 | + "name": "Rolex 118206 Day-Date 36 Platinum Blue Wave Dial Circa. 2000", | |
| 776 | + "sku": "70498", | |
| 777 | + "url": "https://www.europeanwatch.com/watch/rolex-118206-118206-day-date-36-platinum-blue-wave-dial-circa-200-70498", | |
| 778 | + "price": 36590, | |
| 779 | + "currency": "USD", | |
| 780 | + "availability": "InStock", | |
| 781 | + "image": "https://images.europeanwatch.com/images/70/70498-1.jpg", | |
| 782 | + "condition": "UsedCondition" | |
| 783 | + }, | |
| 784 | + { | |
| 785 | + "name": "Rolex 128235 Day-Date 36 18K Everose Gold Eisenkiesel Diamond Roman Dial 2026", | |
| 786 | + "sku": "70664", | |
| 787 | + "url": "https://www.europeanwatch.com/watch/rolex-128235-128235-day-date-36-18k-everose-gold-eisenkiesel-diam-70664", | |
| 788 | + "price": 72850, | |
| 789 | + "currency": "USD", | |
| 790 | + "availability": "InStock", | |
| 791 | + "image": "https://images.europeanwatch.com/images/70/70664-1.jpg", | |
| 792 | + "condition": "UsedCondition" | |
| 793 | + }, | |
| 794 | + { | |
| 795 | + "name": "Rolex 336235 Sky Dweller 18K Rose Gold Gray Slate Dial 2026", | |
| 796 | + "sku": "70828", | |
| 797 | + "url": "https://www.europeanwatch.com/watch/rolex-336235-336235-sky-dweller-18k-rose-gold-gray-slate-dial-202-70828", | |
| 798 | + "price": 51350, | |
| 799 | + "currency": "USD", | |
| 800 | + "availability": "InStock", | |
| 801 | + "image": "https://images.europeanwatch.com/images/70/70828-1.jpg", | |
| 802 | + "condition": "UsedCondition" | |
| 803 | + }, | |
| 804 | + { | |
| 805 | + "name": "Rolex 126334 Datejust 41 SS Black Diamond Dial", | |
| 806 | + "sku": "68875", | |
| 807 | + "url": "https://www.europeanwatch.com/watch/rolex-126334-126334-datejust-41-ss-black-diamond-dial-68875", | |
| 808 | + "price": 16500, | |
| 809 | + "currency": "USD", | |
| 810 | + "availability": "InStock", | |
| 811 | + "image": "https://images.europeanwatch.com/images/68/68875-1.jpg", | |
| 812 | + "condition": "UsedCondition" | |
| 813 | + }, | |
| 814 | + { | |
| 815 | + "name": "Rolex 116333 Datejust II SS / 18K Yellow Gold Black Roman Dial", | |
| 816 | + "sku": "71292", | |
| 817 | + "url": "https://www.europeanwatch.com/watch/rolex-116333-116333-datejust-ii-ss-18k-yellow-gold-black-roman-di-71292", | |
| 818 | + "price": 13900, | |
| 819 | + "currency": "USD", | |
| 820 | + "availability": "InStock", | |
| 821 | + "image": "https://images.europeanwatch.com/images/71/71292-1.jpg", | |
| 822 | + "condition": "UsedCondition" | |
| 823 | + }, | |
| 824 | + { | |
| 825 | + "name": "Rolex 16710 GMT-Master II Coke Bezel SS Black Dial Circa. 1997", | |
| 826 | + "sku": "70706", | |
| 827 | + "url": "https://www.europeanwatch.com/watch/rolex-16710-16710-gmt-master-ii-coke-bezel-ss-black-dial-circa-19-70706", | |
| 828 | + "price": 17900, | |
| 829 | + "currency": "USD", | |
| 830 | + "availability": "OutOfStock", | |
| 831 | + "image": "https://images.europeanwatch.com/images/70/70706-1.jpg", | |
| 832 | + "condition": "UsedCondition" | |
| 833 | + }, | |
| 834 | + { | |
| 835 | + "name": "Rolex 126720VTNR GMT-Master II Left-Handed SS Black Dial", | |
| 836 | + "sku": "71531", | |
| 837 | + "url": "https://www.europeanwatch.com/watch/rolex-126720vtnr-126720vtnr-gmt-master-ii-left-handed-ss-black-di-71531", | |
| 838 | + "price": 17900, | |
| 839 | + "currency": "USD", | |
| 840 | + "availability": "InStock", | |
| 841 | + "image": "https://images.europeanwatch.com/images/71/71531-1.jpg", | |
| 842 | + "condition": "UsedCondition" | |
| 843 | + }, | |
| 844 | + { | |
| 845 | + "name": "Rolex 126660 Deepsea Sea-Dweller SS Black Dial", | |
| 846 | + "sku": "67002", | |
| 847 | + "url": "https://www.europeanwatch.com/watch/rolex-126660-126660-deepsea-sea-dweller-ss-black-dial-67002", | |
| 848 | + "price": 11450, | |
| 849 | + "currency": "USD", | |
| 850 | + "availability": "InStock", | |
| 851 | + "image": "https://images.europeanwatch.com/images/67/67002-1.jpg", | |
| 852 | + "condition": "UsedCondition" | |
| 853 | + }, | |
| 854 | + { | |
| 855 | + "name": "Rolex 114200 Air-King SS Orange Concentric Silver Dial Circa. 2008", | |
| 856 | + "sku": "71340", | |
| 857 | + "url": "https://www.europeanwatch.com/watch/rolex-114200-114200-air-king-ss-orange-concentric-silver-dial-cir-71340", | |
| 858 | + "price": 8900, | |
| 859 | + "currency": "USD", | |
| 860 | + "availability": "InStock", | |
| 861 | + "image": "https://images.europeanwatch.com/images/71/71340-1.jpg", | |
| 862 | + "condition": "UsedCondition" | |
| 863 | + }, | |
| 864 | + { | |
| 865 | + "name": "Rolex 16570 Explorer II SS Black Dial Circa. 1997", | |
| 866 | + "sku": "71229", | |
| 867 | + "url": "https://www.europeanwatch.com/watch/rolex-16570-16570-explorer-ii-ss-black-dial-circa-1997-71229", | |
| 868 | + "price": 10900, | |
| 869 | + "currency": "USD", | |
| 870 | + "availability": "InStock", | |
| 871 | + "image": "https://images.europeanwatch.com/images/71/71229-1.jpg", | |
| 872 | + "condition": "UsedCondition" | |
| 873 | + }, | |
| 874 | + { | |
| 875 | + "name": "Rolex 228235 Day-Date 40MM 18K Everose Slate Gray Ombre 2025", | |
| 876 | + "sku": "71400", | |
| 877 | + "url": "https://www.europeanwatch.com/watch/rolex-228235-228235-day-date-40mm-18k-everose-slate-gray-ombre-20-71400", | |
| 878 | + "price": 67900, | |
| 879 | + "currency": "USD", | |
| 880 | + "availability": "InStock", | |
| 881 | + "image": "https://images.europeanwatch.com/images/71/71400-1.jpg", | |
| 882 | + "condition": "UsedCondition" | |
| 883 | + }, | |
| 884 | + { | |
| 885 | + "name": "Rolex 126519LN Daytona Ceramic 18K White Gold Black Dial 2026", | |
| 886 | + "sku": "71371", | |
| 887 | + "url": "https://www.europeanwatch.com/watch/rolex-126519ln-126519ln-daytona-ceramic-18k-white-gold-black-dial-71371", | |
| 888 | + "price": 53500, | |
| 889 | + "currency": "USD", | |
| 890 | + "availability": "InStock", | |
| 891 | + "image": "https://images.europeanwatch.com/images/71/71371-1.jpg", | |
| 892 | + "condition": "UsedCondition" | |
| 893 | + }, | |
| 894 | + { | |
| 895 | + "name": "Rolex 228206 Day-Date President Platinum Ice Blue Diagonal Motif Dial", | |
| 896 | + "sku": "71178", | |
| 897 | + "url": "https://www.europeanwatch.com/watch/rolex-228206-228206-day-date-president-platinum-ice-blue-diagonal-71178", | |
| 898 | + "price": 64500, | |
| 899 | + "currency": "USD", | |
| 900 | + "availability": "OutOfStock", | |
| 901 | + "image": "https://images.europeanwatch.com/images/71/71178-1.jpg", | |
| 902 | + "condition": "UsedCondition" | |
| 903 | + }, | |
| 904 | + { | |
| 905 | + "name": "Rolex 16600 Sea-Dweller SS Black Dial", | |
| 906 | + "sku": "69869", | |
| 907 | + "url": "https://www.europeanwatch.com/watch/rolex-16600-16600-sea-dweller-ss-black-dial-69869", | |
| 908 | + "price": 10900, | |
| 909 | + "currency": "USD", | |
| 910 | + "availability": "InStock", | |
| 911 | + "image": "https://images.europeanwatch.com/images/69/69869-1.jpg", | |
| 912 | + "condition": "UsedCondition" | |
| 913 | + }, | |
| 914 | + { | |
| 915 | + "name": "Rolex 16618 Submariner 18K Yellow Gold Blue Dial Circa. 1997", | |
| 916 | + "sku": "68931", | |
| 917 | + "url": "https://www.europeanwatch.com/watch/rolex-16618-16618-submariner-18k-yellow-gold-blue-dial-circa-1997-68931", | |
| 918 | + "price": 33900, | |
| 919 | + "currency": "USD", | |
| 920 | + "availability": "InStock", | |
| 921 | + "image": "https://images.europeanwatch.com/images/68/68931-1.jpg", | |
| 922 | + "condition": "UsedCondition" | |
| 923 | + }, | |
| 924 | + { | |
| 925 | + "name": "Rolex 1680 Submariner SS Black Dial Circa. 1979", | |
| 926 | + "sku": "67859", | |
| 927 | + "url": "https://www.europeanwatch.com/watch/rolex-1680-1680-submariner-ss-black-dial-circa-1979-67859", | |
| 928 | + "price": 14750, | |
| 929 | + "currency": "USD", | |
| 930 | + "availability": "InStock", | |
| 931 | + "image": "https://images.europeanwatch.com/images/67/67859-1.jpg", | |
| 932 | + "condition": "UsedCondition" | |
| 933 | + }, | |
| 934 | + { | |
| 935 | + "name": "Rolex 16570 Explorer II SS Black Dial", | |
| 936 | + "sku": "71107", | |
| 937 | + "url": "https://www.europeanwatch.com/watch/rolex-16570-16570-explorer-ii-ss-black-dial-71107", | |
| 938 | + "price": 10900, | |
| 939 | + "currency": "USD", | |
| 940 | + "availability": "InStock", | |
| 941 | + "image": "https://images.europeanwatch.com/images/71/71107-1.jpg", | |
| 942 | + "condition": "UsedCondition" | |
| 943 | + }, | |
| 944 | + { | |
| 945 | + "name": "Rolex 16200 Datejust 36 SS White Dial Circa. 2000", | |
| 946 | + "sku": "69767", | |
| 947 | + "url": "https://www.europeanwatch.com/watch/rolex-16200-16200-datejust-36-ss-white-dial-circa-2000-69767", | |
| 948 | + "price": 6450, | |
| 949 | + "currency": "USD", | |
| 950 | + "availability": "InStock", | |
| 951 | + "image": "https://images.europeanwatch.com/images/69/69767-1.jpg", | |
| 952 | + "condition": "UsedCondition" | |
| 953 | + }, | |
| 954 | + { | |
| 955 | + "name": "Rolex 126619LB Submariner Date 18K White Gold Black Dial", | |
| 956 | + "sku": "70341", | |
| 957 | + "url": "https://www.europeanwatch.com/watch/rolex-126619lb-126619lb-submariner-date-18k-white-gold-black-dial-70341", | |
| 958 | + "price": 37550, | |
| 959 | + "currency": "USD", | |
| 960 | + "availability": "InStock", | |
| 961 | + "image": "https://images.europeanwatch.com/images/70/70341-1.jpg", | |
| 962 | + "condition": "UsedCondition" | |
| 963 | + }, | |
| 964 | + { | |
| 965 | + "name": "Rolex 16618 Submariner 18K Yellow Gold Blue Dial Circa. 1993", | |
| 966 | + "sku": "66770", | |
| 967 | + "url": "https://www.europeanwatch.com/watch/rolex-16618-16618-submariner-18k-yellow-gold-blue-dial-circa-1993-66770", | |
| 968 | + "price": 33150, | |
| 969 | + "currency": "USD", | |
| 970 | + "availability": "InStock", | |
| 971 | + "image": "https://images.europeanwatch.com/images/66/66770-1.jpg", | |
| 972 | + "condition": "UsedCondition" | |
| 973 | + }, | |
| 974 | + { | |
| 975 | + "name": "Rolex 126003 Oyster Perpetual 36 \"100 Years\" SS/ 18K YG Gray Slate Dial 2026", | |
| 976 | + "sku": "71267", | |
| 977 | + "url": "https://www.europeanwatch.com/watch/rolex-126003-126003-oyster-perpetual-36-100-years-ss-18k-yg-gray-71267", | |
| 978 | + "price": 14900, | |
| 979 | + "currency": "USD", | |
| 980 | + "availability": "InStock", | |
| 981 | + "image": "https://images.europeanwatch.com/images/71/71267-1.jpg", | |
| 982 | + "condition": "UsedCondition" | |
| 983 | + }, | |
| 984 | + { | |
| 985 | + "name": "Rolex 228238 Day-Date 40MM 18K Yellow Gold Green Roman Dial 2025", | |
| 986 | + "sku": "70293", | |
| 987 | + "url": "https://www.europeanwatch.com/watch/rolex-228238-228238-day-date-40mm-18k-yellow-gold-green-roman-dia-70293", | |
| 988 | + "price": 65500, | |
| 989 | + "currency": "USD", | |
| 990 | + "availability": "InStock", | |
| 991 | + "image": "https://images.europeanwatch.com/images/70/70293-1.jpg", | |
| 992 | + "condition": "UsedCondition" | |
| 993 | + }, | |
| 994 | + { | |
| 995 | + "name": "Rolex 228236 Day-Date President Platinum Black Dial 2025", | |
| 996 | + "sku": "71212", | |
| 997 | + "url": "https://www.europeanwatch.com/watch/rolex-228236-228236-day-date-president-platinum-black-dial-2025-71212", | |
| 998 | + "price": 71500, | |
| 999 | + "currency": "USD", | |
| 1000 | + "availability": "InStock", | |
| 1001 | + "image": "https://images.europeanwatch.com/images/71/71212-1.jpg", | |
| 1002 | + "condition": "UsedCondition" | |
| 1003 | + }, | |
| 1004 | + { | |
| 1005 | + "name": "Rolex 116505 Daytona 18K Rose Gold Brown Chocolate Dial", | |
| 1006 | + "sku": "71287", | |
| 1007 | + "url": "https://www.europeanwatch.com/watch/rolex-116505-116505-daytona-18k-rose-gold-brown-chocolate-dial-71287", | |
| 1008 | + "price": 56500, | |
| 1009 | + "currency": "USD", | |
| 1010 | + "availability": "InStock", | |
| 1011 | + "image": "https://images.europeanwatch.com/images/71/71287-1.jpg", | |
| 1012 | + "condition": "UsedCondition" | |
| 1013 | + }, | |
| 1014 | + { | |
| 1015 | + "name": "Rolex 118339 Day-Date 36 Diamonds 18K WG Mother of Pearl", | |
| 1016 | + "sku": "71147", | |
| 1017 | + "url": "https://www.europeanwatch.com/watch/rolex-118339-118339-day-date-36-diamonds-18k-wg-mother-of-pearl-71147", | |
| 1018 | + "price": 45900, | |
| 1019 | + "currency": "USD", | |
| 1020 | + "availability": "InStock", | |
| 1021 | + "image": "https://images.europeanwatch.com/images/71/71147-1.jpg", | |
| 1022 | + "condition": "UsedCondition" | |
| 1023 | + }, | |
| 1024 | + { | |
| 1025 | + "name": "Rolex 126610LV Submariner Date \"Starbucks\" SS Black Dial 2025", | |
| 1026 | + "sku": "71052", | |
| 1027 | + "url": "https://www.europeanwatch.com/watch/rolex-126610lv-126610lv-submariner-date-starbucks-ss-black-dial-2-71052", | |
| 1028 | + "price": 15900, | |
| 1029 | + "currency": "USD", | |
| 1030 | + "availability": "OutOfStock", | |
| 1031 | + "image": "https://images.europeanwatch.com/images/71/71052-1.jpg", | |
| 1032 | + "condition": "UsedCondition" | |
| 1033 | + }, | |
| 1034 | + { | |
| 1035 | + "name": "Rolex 126334 Datejust 41 SS White Stick Dial", | |
| 1036 | + "sku": "69728", | |
| 1037 | + "url": "https://www.europeanwatch.com/watch/rolex-126334-126334-datejust-41-ss-white-stick-dial-69728", | |
| 1038 | + "price": 12900, | |
| 1039 | + "currency": "USD", | |
| 1040 | + "availability": "InStock", | |
| 1041 | + "image": "https://images.europeanwatch.com/images/69/69728-1.jpg", | |
| 1042 | + "condition": "UsedCondition" | |
| 1043 | + }, | |
| 1044 | + { | |
| 1045 | + "name": "Rolex 126300 Datejust 41 SS Black Dial", | |
| 1046 | + "sku": "69640", | |
| 1047 | + "url": "https://www.europeanwatch.com/watch/rolex-126300-126300-datejust-41-ss-black-dial-69640", | |
| 1048 | + "price": 10990, | |
| 1049 | + "currency": "USD", | |
| 1050 | + "availability": "InStock", | |
| 1051 | + "image": "https://images.europeanwatch.com/images/69/69640-1.jpg", | |
| 1052 | + "condition": "UsedCondition" | |
| 1053 | + }, | |
| 1054 | + { | |
| 1055 | + "name": "Rolex 116233 Datejust 36 Jubilee SS / 18K YG Champagne Dial", | |
| 1056 | + "sku": "70045", | |
| 1057 | + "url": "https://www.europeanwatch.com/watch/rolex-116233-116233-datejust-36-jubilee-ss-18k-yg-champagne-dial-70045", | |
| 1058 | + "price": 11700, | |
| 1059 | + "currency": "USD", | |
| 1060 | + "availability": "InStock", | |
| 1061 | + "image": "https://images.europeanwatch.com/images/70/70045-1.jpg", | |
| 1062 | + "condition": "UsedCondition" | |
| 1063 | + }, | |
| 1064 | + { | |
| 1065 | + "name": "Rolex 178274 Datejust 31 SS Silver Diamond Dial", | |
| 1066 | + "sku": "71021", | |
| 1067 | + "url": "https://www.europeanwatch.com/watch/rolex-178274-178274-datejust-31-ss-silver-diamond-dial-71021", | |
| 1068 | + "price": 11450, | |
| 1069 | + "currency": "USD", | |
| 1070 | + "availability": "InStock", | |
| 1071 | + "image": "https://images.europeanwatch.com/images/71/71021-1.jpg", | |
| 1072 | + "condition": "UsedCondition" | |
| 1073 | + }, | |
| 1074 | + { | |
| 1075 | + "name": "Rolex 124300 Oyster Perpetual 41 SS Black Dial", | |
| 1076 | + "sku": "71175", | |
| 1077 | + "url": "https://www.europeanwatch.com/watch/rolex-124300-124300-oyster-perpetual-41-ss-black-dial-71175", | |
| 1078 | + "price": 10300, | |
| 1079 | + "currency": "USD", | |
| 1080 | + "availability": "InStock", | |
| 1081 | + "image": "https://images.europeanwatch.com/images/71/71175-1.jpg", | |
| 1082 | + "condition": "UsedCondition" | |
| 1083 | + }, | |
| 1084 | + { | |
| 1085 | + "name": "Rolex 116660 Sea-Dweller Deepsea SS Black Dial", | |
| 1086 | + "sku": "71549", | |
| 1087 | + "url": "https://www.europeanwatch.com/watch/rolex-116660-116660-sea-dweller-deepsea-ss-black-dial-71549", | |
| 1088 | + "price": 11350, | |
| 1089 | + "currency": "USD", | |
| 1090 | + "availability": "InStock", | |
| 1091 | + "image": "https://images.europeanwatch.com/images/71/71549-1.jpg", | |
| 1092 | + "condition": "UsedCondition" | |
| 1093 | + }, | |
| 1094 | + { | |
| 1095 | + "name": "Rolex 326934 Sky Dweller SS Silver Dial", | |
| 1096 | + "sku": "70906", | |
| 1097 | + "url": "https://www.europeanwatch.com/watch/rolex-326934-326934-sky-dweller-ss-silver-dial-70906", | |
| 1098 | + "price": 21500, | |
| 1099 | + "currency": "USD", | |
| 1100 | + "availability": "InStock", | |
| 1101 | + "image": "https://images.europeanwatch.com/images/70/70906-1.jpg", | |
| 1102 | + "condition": "UsedCondition" | |
| 1103 | + }, | |
| 1104 | + { | |
| 1105 | + "name": "Rolex 16610LV Kermit Submariner Date \"Flat 4\" SS Black Dial", | |
| 1106 | + "sku": "70005", | |
| 1107 | + "url": "https://www.europeanwatch.com/watch/rolex-16610lv-16610lv-kermit-submariner-date-flat-4-ss-black-dial-70005", | |
| 1108 | + "price": 24900, | |
| 1109 | + "currency": "USD", | |
| 1110 | + "availability": "InStock", | |
| 1111 | + "image": "https://images.europeanwatch.com/images/70/70005-1.jpg", | |
| 1112 | + "condition": "UsedCondition" | |
| 1113 | + }, | |
| 1114 | + { | |
| 1115 | + "name": "Rolex 124200 Oyster Perpetual 34 SS Beige Dial 2026 UNWORN", | |
| 1116 | + "sku": "68754", | |
| 1117 | + "url": "https://www.europeanwatch.com/watch/rolex-124200-124200-oyster-perpetual-34-ss-beige-dial-2026-unworn-68754", | |
| 1118 | + "price": 10450, | |
| 1119 | + "currency": "USD", | |
| 1120 | + "availability": "InStock", | |
| 1121 | + "image": "https://images.europeanwatch.com/images/68/68754-1.jpg", | |
| 1122 | + "condition": "UsedCondition" | |
| 1123 | + }, | |
| 1124 | + { | |
| 1125 | + "name": "Rolex 126715CHNR GMT Master II \"Root Beer\" 18K Everose Gold Black Dial", | |
| 1126 | + "sku": "71109", | |
| 1127 | + "url": "https://www.europeanwatch.com/watch/rolex-126715chnr-126715chnr-gmt-master-ii-root-beer-18k-everose-g-71109", | |
| 1128 | + "price": 42500, | |
| 1129 | + "currency": "USD", | |
| 1130 | + "availability": "OutOfStock", | |
| 1131 | + "image": "https://images.europeanwatch.com/images/71/71109-1.jpg", | |
| 1132 | + "condition": "UsedCondition" | |
| 1133 | + }, | |
| 1134 | + { | |
| 1135 | + "name": "Rolex 326934 Sky-Dweller SS Silver Dial", | |
| 1136 | + "sku": "70255", | |
| 1137 | + "url": "https://www.europeanwatch.com/watch/rolex-326934-326934-sky-dweller-ss-silver-dial-70255", | |
| 1138 | + "price": 18500, | |
| 1139 | + "currency": "USD", | |
| 1140 | + "availability": "OutOfStock", | |
| 1141 | + "image": "https://images.europeanwatch.com/images/70/70255-1.jpg", | |
| 1142 | + "condition": "UsedCondition" | |
| 1143 | + }, | |
| 1144 | + { | |
| 1145 | + "name": "Rolex 18349 Day-Date President Diamonds 18K WG Champagne Dial Circa. 1989", | |
| 1146 | + "sku": "70169", | |
| 1147 | + "url": "https://www.europeanwatch.com/watch/rolex-18349-18349-day-date-president-diamonds-18k-wg-champagne-di-70169", | |
| 1148 | + "price": 32500, | |
| 1149 | + "currency": "USD", | |
| 1150 | + "availability": "InStock", | |
| 1151 | + "image": "https://images.europeanwatch.com/images/70/70169-1.jpg", | |
| 1152 | + "condition": "UsedCondition" | |
| 1153 | + }, | |
| 1154 | + { | |
| 1155 | + "name": "Rolex 1675/8 GMT Master 18K Yellow Gold Black Dial Circa. 1978", | |
| 1156 | + "sku": "66777", | |
| 1157 | + "url": "https://www.europeanwatch.com/watch/rolex-1675-8-1675-8-gmt-master-18k-yellow-gold-black-dial-circa-1-66777", | |
| 1158 | + "price": 33500, | |
| 1159 | + "currency": "USD", | |
| 1160 | + "availability": "InStock", | |
| 1161 | + "image": "https://images.europeanwatch.com/images/66/66777-1.jpg", | |
| 1162 | + "condition": "UsedCondition" | |
| 1163 | + }, | |
| 1164 | + { | |
| 1165 | + "name": "Rolex 126500LN Daytona SS Black Dial", | |
| 1166 | + "sku": "70969", | |
| 1167 | + "url": "https://www.europeanwatch.com/watch/rolex-126500ln-126500ln-daytona-ss-black-dial-70969", | |
| 1168 | + "price": 34500, | |
| 1169 | + "currency": "USD", | |
| 1170 | + "availability": "InStock", | |
| 1171 | + "image": "https://images.europeanwatch.com/images/70/70969-1.jpg", | |
| 1172 | + "condition": "UsedCondition" | |
| 1173 | + }, | |
| 1174 | + { | |
| 1175 | + "name": "Rolex 5512 Submariner SS Matte Black Dial Circa. 1971", | |
| 1176 | + "sku": "69267", | |
| 1177 | + "url": "https://www.europeanwatch.com/watch/rolex-5512-5512-submariner-ss-matte-black-dial-circa-1971-69267", | |
| 1178 | + "price": 20100, | |
| 1179 | + "currency": "USD", | |
| 1180 | + "availability": "InStock", | |
| 1181 | + "image": "https://images.europeanwatch.com/images/69/69267-1.jpg", | |
| 1182 | + "condition": "UsedCondition" | |
| 1183 | + }, | |
| 1184 | + { | |
| 1185 | + "name": "Rolex 1625 Datejust Turn-O-Graph 18K Yellow Gold Champagne Dial Circa. 1970", | |
| 1186 | + "sku": "66767", | |
| 1187 | + "url": "https://www.europeanwatch.com/watch/rolex-1625-1625-datejust-turn-o-graph-18k-yellow-gold-champagne-d-66767", | |
| 1188 | + "price": 23600, | |
| 1189 | + "currency": "USD", | |
| 1190 | + "availability": "InStock", | |
| 1191 | + "image": "https://images.europeanwatch.com/images/66/66767-1.jpg", | |
| 1192 | + "condition": "UsedCondition" | |
| 1193 | + }, | |
| 1194 | + { | |
| 1195 | + "name": "Rolex 126331 Datejust 41 18K RG / SS Gray Wimbledon Dial", | |
| 1196 | + "sku": "68800", | |
| 1197 | + "url": "https://www.europeanwatch.com/watch/rolex-126331-126331-datejust-41-18k-rg-ss-gray-wimbledon-dial-68800", | |
| 1198 | + "price": 16200, | |
| 1199 | + "currency": "USD", | |
| 1200 | + "availability": "InStock", | |
| 1201 | + "image": "https://images.europeanwatch.com/images/68/68800-1.jpg", | |
| 1202 | + "condition": "UsedCondition" | |
| 1203 | + }, | |
| 1204 | + { | |
| 1205 | + "name": "Rolex 126500LN Daytona SS Black Dial 2026 UNWORN", | |
| 1206 | + "sku": "70798", | |
| 1207 | + "url": "https://www.europeanwatch.com/watch/rolex-126500ln-126500ln-daytona-ss-black-dial-2026-unworn-70798", | |
| 1208 | + "price": 36500, | |
| 1209 | + "currency": "USD", | |
| 1210 | + "availability": "InStock", | |
| 1211 | + "image": "https://images.europeanwatch.com/images/70/70798-1.jpg", | |
| 1212 | + "condition": "UsedCondition" | |
| 1213 | + }, | |
| 1214 | + { | |
| 1215 | + "name": "Rolex 116600 Sea-Dweller SS Black Dial", | |
| 1216 | + "sku": "71013", | |
| 1217 | + "url": "https://www.europeanwatch.com/watch/rolex-116600-116600-sea-dweller-ss-black-dial-71013", | |
| 1218 | + "price": 15500, | |
| 1219 | + "currency": "USD", | |
| 1220 | + "availability": "OutOfStock", | |
| 1221 | + "image": "https://images.europeanwatch.com/images/71/71013-1.jpg", | |
| 1222 | + "condition": "UsedCondition" | |
| 1223 | + }, | |
| 1224 | + { | |
| 1225 | + "name": "Rolex 126331 Datejust 41 18K RG / SS Gray Wimbledon Dial", | |
| 1226 | + "sku": "68696", | |
| 1227 | + "url": "https://www.europeanwatch.com/watch/rolex-126331-126331-datejust-41-18k-rg-ss-gray-wimbledon-dial-68696", | |
| 1228 | + "price": 15900, | |
| 1229 | + "currency": "USD", | |
| 1230 | + "availability": "InStock", | |
| 1231 | + "image": "https://images.europeanwatch.com/images/68/68696-1.jpg", | |
| 1232 | + "condition": "UsedCondition" | |
| 1233 | + }, | |
| 1234 | + { | |
| 1235 | + "name": "Rolex 14233 Oyster Perpetual 18K Yellow Gold / SS Champagne Dial Circa. 1993", | |
| 1236 | + "sku": "66274", | |
| 1237 | + "url": "https://www.europeanwatch.com/watch/rolex-14233-14233-oyster-perpetual-18k-yellow-gold-ss-champagne-d-66274", | |
| 1238 | + "price": 7000, | |
| 1239 | + "currency": "USD", | |
| 1240 | + "availability": "InStock", | |
| 1241 | + "image": "https://images.europeanwatch.com/images/66/66274-1.jpg", | |
| 1242 | + "condition": "UsedCondition" | |
| 1243 | + }, | |
| 1244 | + { | |
| 1245 | + "name": "Rolex 52508 Perpetual 1908 18K Yellow Gold Black Dial 2026", | |
| 1246 | + "sku": "69073", | |
| 1247 | + "url": "https://www.europeanwatch.com/watch/rolex-52508-52508-perpetual-1908-18k-yellow-gold-black-dial-2026-69073", | |
| 1248 | + "price": 45000, | |
| 1249 | + "currency": "USD", | |
| 1250 | + "availability": "InStock", | |
| 1251 | + "image": "https://images.europeanwatch.com/images/69/69073-1.jpg", | |
| 1252 | + "condition": "UsedCondition" | |
| 1253 | + }, | |
| 1254 | + { | |
| 1255 | + "name": "Rolex 116618LB Submariner Date 18K Yellow Gold Blue Dial", | |
| 1256 | + "sku": "68613", | |
| 1257 | + "url": "https://www.europeanwatch.com/watch/rolex-116618lb-116618lb-submariner-date-18k-yellow-gold-blue-dial-68613", | |
| 1258 | + "price": 35500, | |
| 1259 | + "currency": "USD", | |
| 1260 | + "availability": "OutOfStock", | |
| 1261 | + "image": "https://images.europeanwatch.com/images/68/68613-1.jpg", | |
| 1262 | + "condition": "UsedCondition" | |
| 1263 | + }, | |
| 1264 | + { | |
| 1265 | + "name": "Rolex 128348RBR Day Date 36MM Diamonds 18K Yellow Gold Green Dial", | |
| 1266 | + "sku": "66415", | |
| 1267 | + "url": "https://www.europeanwatch.com/watch/rolex-128348rbr-128348rbr-day-date-36mm-diamonds-18k-yellow-gold-66415", | |
| 1268 | + "price": 62500, | |
| 1269 | + "currency": "USD", | |
| 1270 | + "availability": "InStock", | |
| 1271 | + "image": "https://images.europeanwatch.com/images/66/66415-1.jpg", | |
| 1272 | + "condition": "UsedCondition" | |
| 1273 | + }, | |
| 1274 | + { | |
| 1275 | + "name": "Rolex 6605 Datejust 18K Yellow Gold White Dial Circa. 1956", | |
| 1276 | + "sku": "66790", | |
| 1277 | + "url": "https://www.europeanwatch.com/watch/rolex-6605-6605-datejust-18k-yellow-gold-white-dial-circa-1956-66790", | |
| 1278 | + "price": 39900, | |
| 1279 | + "currency": "USD", | |
| 1280 | + "availability": "InStock", | |
| 1281 | + "image": "https://images.europeanwatch.com/images/66/66790-1.jpg", | |
| 1282 | + "condition": "UsedCondition" | |
| 1283 | + }, | |
| 1284 | + { | |
| 1285 | + "name": "Rolex 18308 Day-Date Bark Finish 18K YG Ferrite Roman Dial Circa. 1989 RARE", | |
| 1286 | + "sku": "66761", | |
| 1287 | + "url": "https://www.europeanwatch.com/watch/rolex-18308-18308-day-date-bark-finish-18k-yg-ferrite-roman-dial-66761", | |
| 1288 | + "price": 46700, | |
| 1289 | + "currency": "USD", | |
| 1290 | + "availability": "InStock", | |
| 1291 | + "image": "https://images.europeanwatch.com/images/66/66761-1.jpg", | |
| 1292 | + "condition": "UsedCondition" | |
| 1293 | + }, | |
| 1294 | + { | |
| 1295 | + "name": "Rolex 1803 Day Date 18K Yellow Gold Oxblood Stella Dial RARE Circa. 1971", | |
| 1296 | + "sku": "66787", | |
| 1297 | + "url": "https://www.europeanwatch.com/watch/rolex-1803-1803-day-date-18k-yellow-gold-oxblood-stella-dial-rare-66787", | |
| 1298 | + "price": 54100, | |
| 1299 | + "currency": "USD", | |
| 1300 | + "availability": "InStock", | |
| 1301 | + "image": "https://images.europeanwatch.com/images/66/66787-1.jpg", | |
| 1302 | + "condition": "UsedCondition" | |
| 1303 | + }, | |
| 1304 | + { | |
| 1305 | + "name": "Rolex 118206 Day Date 36mm Platinum \"Glacier\" Baguette Diamond Dial", | |
| 1306 | + "sku": "67338", | |
| 1307 | + "url": "https://www.europeanwatch.com/watch/rolex-118206-118206-day-date-36mm-platinum-glacier-baguette-diamo-67338", | |
| 1308 | + "price": 39900, | |
| 1309 | + "currency": "USD", | |
| 1310 | + "availability": "InStock", | |
| 1311 | + "image": "https://images.europeanwatch.com/images/67/67338-1.jpg", | |
| 1312 | + "condition": "UsedCondition" | |
| 1313 | + }, | |
| 1314 | + { | |
| 1315 | + "name": "Rolex 16618 Submariner Date 18K Yellow Gold Black Dial Circa. 1999", | |
| 1316 | + "sku": "65727", | |
| 1317 | + "url": "https://www.europeanwatch.com/watch/rolex-16618-16618-submariner-date-18k-yellow-gold-black-dial-circ-65727", | |
| 1318 | + "price": 30400, | |
| 1319 | + "currency": "USD", | |
| 1320 | + "availability": "InStock", | |
| 1321 | + "image": "https://images.europeanwatch.com/images/65/65727-1.jpg", | |
| 1322 | + "condition": "UsedCondition" | |
| 1323 | + } | |
| 1324 | + ] | |
| 1325 | + } | |
| 1326 | + }, | |
| 1327 | + "expect": { | |
| 1328 | + "minCount": 1, | |
| 1329 | + "kinds": [ | |
| 1330 | + "listing" | |
| 1331 | + ] | |
| 1332 | + }, | |
| 1333 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 1334 | + "capturedAt": "2026-09-07T06:28:38.677Z" | |
| 1335 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/fashionphile/collection-hermes-1.json
+865 −0
@@ -0,0 +1,865 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.fashionphile.com/collections/hermes/products.json?page=1", | |
| 4 | + "externalId": "collection:hermes:1", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:30.628Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "shopify_page", | |
| 10 | + "url": "https://www.fashionphile.com/collections/hermes/products.json?page=1", | |
| 11 | + "seed": "hermes", | |
| 12 | + "page": 1, | |
| 13 | + "products": [ | |
| 14 | + { | |
| 15 | + "id": 10724165648687, | |
| 16 | + "title": "Swift Kelly Cut Clutch Pochette Black", | |
| 17 | + "handle": "hermes-swift-kelly-cut-clutch-pochette-black-1700285", | |
| 18 | + "body_html": "This is an authentic HERMES Swift Kelly Cut Clutch Pochette in Black. This chic clutch is crafted of fine black swift calfskin leather. In the style of the Kelly handbags, the clutch has a leather strap handle, a cross-over flap, and strap closure with a palladium turn lock. This opens to a matte black leather interior with a patch pocket.", | |
| 19 | + "published_at": "2026-09-02T13:35:33-07:00", | |
| 20 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 21 | + "vendor": "Hermes", | |
| 22 | + "product_type": "Bags", | |
| 23 | + "tags": [ | |
| 24 | + "Hermes Kelly Pochette", | |
| 25 | + "US" | |
| 26 | + ], | |
| 27 | + "variants": [ | |
| 28 | + { | |
| 29 | + "id": 51774714151215, | |
| 30 | + "title": "Default Title", | |
| 31 | + "price": "14500.00", | |
| 32 | + "compare_at_price": "14500.00", | |
| 33 | + "available": true, | |
| 34 | + "sku": "1700285", | |
| 35 | + "option1": "Default Title", | |
| 36 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 37 | + } | |
| 38 | + ], | |
| 39 | + "images": [ | |
| 40 | + { | |
| 41 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/02098cbf355b52a174fc0a55e0d36c2b.jpg?v=1788381337" | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/d95c4200718b455593d4b157062670e2.jpg?v=1788381338" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "options": [ | |
| 48 | + { | |
| 49 | + "name": "Title" | |
| 50 | + } | |
| 51 | + ] | |
| 52 | + }, | |
| 53 | + { | |
| 54 | + "id": 10722774417711, | |
| 55 | + "title": "Swift Kelly Moove Sesame", | |
| 56 | + "handle": "hermes-swift-kelly-moove-sesame-1701679", | |
| 57 | + "body_html": "This is an authentic HERMES Swift Kelly Moove in Etoupe. This stylish shoulder bag is crafted of swift leather in dark beige. The bag features a lengthy crossbody strap with a coin purse attached and polished gold hardware. The gold turn lock opens to a matching interior with a pocket.", | |
| 58 | + "published_at": "2025-08-13T12:21:36-07:00", | |
| 59 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 60 | + "vendor": "Hermes", | |
| 61 | + "product_type": "Bags", | |
| 62 | + "tags": [ | |
| 63 | + "Aug-10-incident", | |
| 64 | + "Every Era Sale", | |
| 65 | + "Fall Finds Sale", | |
| 66 | + "Live Shopping with FASHIONPHILE", | |
| 67 | + "Live Shopping with FASHIONPHILEPUSH", | |
| 68 | + "Luxury Lovers Event", | |
| 69 | + "PUSH", | |
| 70 | + "refresh-051726", | |
| 71 | + "refresh-promo-inclusions", | |
| 72 | + "refresh-shopall", | |
| 73 | + "US" | |
| 74 | + ], | |
| 75 | + "variants": [ | |
| 76 | + { | |
| 77 | + "id": 51770389266735, | |
| 78 | + "title": "Default Title", | |
| 79 | + "price": "11250.00", | |
| 80 | + "compare_at_price": "11250.00", | |
| 81 | + "available": true, | |
| 82 | + "sku": "1701679", | |
| 83 | + "option1": "Default Title", | |
| 84 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 85 | + } | |
| 86 | + ], | |
| 87 | + "images": [ | |
| 88 | + { | |
| 89 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/3fec3bbdc0aaea6e54524ea404c7e717_e8d00758-abe9-4b9e-9efd-6792e9651ee6.jpg?v=1767649420" | |
| 90 | + }, | |
| 91 | + { | |
| 92 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/50b9b3616e16a5b8f8c65b484689555d_2ece8400-7d44-482a-9db9-c4015c890dd2.jpg?v=1767649419" | |
| 93 | + } | |
| 94 | + ], | |
| 95 | + "options": [ | |
| 96 | + { | |
| 97 | + "name": "Title" | |
| 98 | + } | |
| 99 | + ] | |
| 100 | + }, | |
| 101 | + { | |
| 102 | + "id": 14547333579055, | |
| 103 | + "title": "Chevre Mysore Constance 18 Gris Perle", | |
| 104 | + "handle": "hermes-chevre-mysore-constance-18-gris-perle-1846205", | |
| 105 | + "body_html": "This is an authentic HERMES Chevre Mysore Constance 18 in Gris Perle. This is a chic small bag that is finely crafted of luxurious light grey chevre goatskin leather. The shoulder bag features a leather shoulder strap, a front flap, and a polished gold H logo clasp. The flap opens to a partitioned, smooth leather interior with patch pockets.", | |
| 106 | + "published_at": "2026-04-07T07:13:21-07:00", | |
| 107 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 108 | + "vendor": "Hermes", | |
| 109 | + "product_type": "Bags", | |
| 110 | + "tags": [ | |
| 111 | + "Aug-10-incident", | |
| 112 | + "refresh-051726", | |
| 113 | + "US" | |
| 114 | + ], | |
| 115 | + "variants": [ | |
| 116 | + { | |
| 117 | + "id": 56376092786991, | |
| 118 | + "title": "Default Title", | |
| 119 | + "price": "13295.00", | |
| 120 | + "compare_at_price": "13295.00", | |
| 121 | + "available": true, | |
| 122 | + "sku": "1846205", | |
| 123 | + "option1": "Default Title", | |
| 124 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 125 | + } | |
| 126 | + ], | |
| 127 | + "images": [ | |
| 128 | + { | |
| 129 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/4651ca92ee36544338acae3df9a21d3f.jpg?v=1775571202" | |
| 130 | + }, | |
| 131 | + { | |
| 132 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/6877857411a29ea998399fc17875b91c.jpg?v=1775571202" | |
| 133 | + } | |
| 134 | + ], | |
| 135 | + "options": [ | |
| 136 | + { | |
| 137 | + "name": "Title" | |
| 138 | + } | |
| 139 | + ] | |
| 140 | + }, | |
| 141 | + { | |
| 142 | + "id": 11501987103023, | |
| 143 | + "title": "Taurillon Clemence Chevre Mysore Silk Petit H Apple Leaf Bag Charm", | |
| 144 | + "handle": "hermes-taurillon-clemence-chevre-mysore-silk-petit-h-apple-leaf-bag-charm-1787982", | |
| 145 | + "body_html": "This is an authentic HERMES Taurillon Clemence Chevre Mysore Silk Petit H Apple Leaf Bag Charm. This bag charm is crafted of brown clemence and chevre leather in the shape of an apple on a silk chord.", | |
| 146 | + "published_at": "2026-01-18T17:03:16-08:00", | |
| 147 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 148 | + "vendor": "Hermes", | |
| 149 | + "product_type": "Accessories", | |
| 150 | + "tags": [ | |
| 151 | + "Fall Finds Sale", | |
| 152 | + "US" | |
| 153 | + ], | |
| 154 | + "variants": [ | |
| 155 | + { | |
| 156 | + "id": 52860666642735, | |
| 157 | + "title": "Default Title", | |
| 158 | + "price": "420.00", | |
| 159 | + "compare_at_price": "420.00", | |
| 160 | + "available": true, | |
| 161 | + "sku": "1787982", | |
| 162 | + "option1": "Default Title", | |
| 163 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 164 | + } | |
| 165 | + ], | |
| 166 | + "images": [ | |
| 167 | + { | |
| 168 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/872ace2e73800e92a834c09cf8ecb4fc.jpg?v=1768784597" | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/84340fbee8a2feae8c0d1f7ceb6155ab.jpg?v=1768784597" | |
| 172 | + } | |
| 173 | + ], | |
| 174 | + "options": [ | |
| 175 | + { | |
| 176 | + "name": "Title" | |
| 177 | + } | |
| 178 | + ] | |
| 179 | + }, | |
| 180 | + { | |
| 181 | + "id": 11691292950831, | |
| 182 | + "title": "Taurillon Clemence Evelyne III PM Orange", | |
| 183 | + "handle": "hermes-taurillon-clemence-evelyne-iii-pm-orange-1796485", | |
| 184 | + "body_html": "This is an authentic HERMES Taurillon Clemence Evelyne III PM in Orange. This stylish messenger bag is crafted of fine Taurillon Clemence calfskin leather in orange. The shoulder bag features an adjustable canvas shoulder strap, polished palladium hardware and a perforated Hermes H oval on the front of the bag. The top of the bag opens with a cross over strap to a matching suede interior.", | |
| 185 | + "published_at": "2026-01-23T07:22:21-08:00", | |
| 186 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 187 | + "vendor": "Hermes", | |
| 188 | + "product_type": "Bags", | |
| 189 | + "tags": [ | |
| 190 | + "Aug-10-incident", | |
| 191 | + "Fall Finds Sale", | |
| 192 | + "Luxury Lovers Event", | |
| 193 | + "US" | |
| 194 | + ], | |
| 195 | + "variants": [ | |
| 196 | + { | |
| 197 | + "id": 53059156312367, | |
| 198 | + "title": "Default Title", | |
| 199 | + "price": "1910.00", | |
| 200 | + "compare_at_price": "2010.00", | |
| 201 | + "available": true, | |
| 202 | + "sku": "1796485", | |
| 203 | + "option1": "Default Title", | |
| 204 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 205 | + } | |
| 206 | + ], | |
| 207 | + "images": [ | |
| 208 | + { | |
| 209 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/cee91843e3f1a415e4886c93eedab54c.jpg?v=1769181742" | |
| 210 | + }, | |
| 211 | + { | |
| 212 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/9003b3369545a268c5ee5dca3fe95f32.jpg?v=1769181742" | |
| 213 | + } | |
| 214 | + ], | |
| 215 | + "options": [ | |
| 216 | + { | |
| 217 | + "name": "Title" | |
| 218 | + } | |
| 219 | + ] | |
| 220 | + }, | |
| 221 | + { | |
| 222 | + "id": 14365102801199, | |
| 223 | + "title": "Swift Epsom 24mm Mini Constance H Belt 75 30 Black Etoupe", | |
| 224 | + "handle": "hermes-swift-epsom-24mm-mini-constance-h-belt-75-30-black-etoupe-1842584", | |
| 225 | + "body_html": "This is an authentic HERMES Swift Epsom 24mm Mini Constance H Belt 75 in Black and Etoupe. This stylish belt is reversible, featuring beautiful smooth calfskin leather in black and Epsom leather in taupe. It is completed with the iconic Palladium H buckle.", | |
| 226 | + "published_at": "2026-04-03T11:16:33-07:00", | |
| 227 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 228 | + "vendor": "Hermes", | |
| 229 | + "product_type": "Accessories", | |
| 230 | + "tags": [ | |
| 231 | + "Aug-10-incident", | |
| 232 | + "US" | |
| 233 | + ], | |
| 234 | + "variants": [ | |
| 235 | + { | |
| 236 | + "id": 56177249124655, | |
| 237 | + "title": "Default Title", | |
| 238 | + "price": "750.00", | |
| 239 | + "compare_at_price": "750.00", | |
| 240 | + "available": true, | |
| 241 | + "sku": "1842584", | |
| 242 | + "option1": "Default Title", | |
| 243 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 244 | + } | |
| 245 | + ], | |
| 246 | + "images": [ | |
| 247 | + { | |
| 248 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/272062c61185470e6a496146978686f5.jpg?v=1775240194" | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/eeed1d808e9980eb1ebcab6f242feb38.jpg?v=1775240194" | |
| 252 | + } | |
| 253 | + ], | |
| 254 | + "options": [ | |
| 255 | + { | |
| 256 | + "name": "Title" | |
| 257 | + } | |
| 258 | + ] | |
| 259 | + }, | |
| 260 | + { | |
| 261 | + "id": 10818458288431, | |
| 262 | + "title": "Canvas Small Bride-A-Brac Pouch Natural", | |
| 263 | + "handle": "hermes-canvas-small-bride-a-brac-pouch-natural-1775322", | |
| 264 | + "body_html": "This is an authentic HERMES Canvas Small Bride-A-Brac Pouch in Natural. This pouch is crafted of beige canvas with a dark beige and white stripe around the middle that extends into a snapped top handle. The top zipper opens to a natural canvas interior with patch pockets.", | |
| 265 | + "published_at": "2025-12-16T08:24:56-08:00", | |
| 266 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 267 | + "vendor": "Hermes", | |
| 268 | + "product_type": "Accessories", | |
| 269 | + "tags": [ | |
| 270 | + "Every Era Sale", | |
| 271 | + "Luxury Lovers Event", | |
| 272 | + "refresh-promo-inclusions", | |
| 273 | + "US" | |
| 274 | + ], | |
| 275 | + "variants": [ | |
| 276 | + { | |
| 277 | + "id": 52124401434927, | |
| 278 | + "title": "Default Title", | |
| 279 | + "price": "735.00", | |
| 280 | + "compare_at_price": "735.00", | |
| 281 | + "available": true, | |
| 282 | + "sku": "1775322", | |
| 283 | + "option1": "Default Title", | |
| 284 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 285 | + } | |
| 286 | + ], | |
| 287 | + "images": [ | |
| 288 | + { | |
| 289 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/adcc3fdbf5beefa944522278062a11a7.jpg?v=1765902297" | |
| 290 | + }, | |
| 291 | + { | |
| 292 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/9aaf3f945293b8107868b162da9cb16e.jpg?v=1765902297" | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "options": [ | |
| 296 | + { | |
| 297 | + "name": "Title" | |
| 298 | + } | |
| 299 | + ] | |
| 300 | + }, | |
| 301 | + { | |
| 302 | + "id": 10801961500975, | |
| 303 | + "title": "Aluminium Evelyne Sunset Cuff Bracelet T2 Orange Tropique", | |
| 304 | + "handle": "hermes-aluminium-evelyne-sunset-cuff-bracelet-t2-orange-tropique-1757661", | |
| 305 | + "body_html": "This is an authentic HERMES Aluminium Evelyne Sunset Cuff Bracelet T2 in Orange Tropique. This chic dog collar bracelet is crafted of aluminium in an orange red hue.", | |
| 306 | + "published_at": "2025-11-20T11:30:05-08:00", | |
| 307 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 308 | + "vendor": "Hermes", | |
| 309 | + "product_type": "Jewelry", | |
| 310 | + "tags": [ | |
| 311 | + "Every Era Sale", | |
| 312 | + "Fall Finds Sale", | |
| 313 | + "refresh-051726", | |
| 314 | + "US" | |
| 315 | + ], | |
| 316 | + "variants": [ | |
| 317 | + { | |
| 318 | + "id": 52042440343855, | |
| 319 | + "title": "Default Title", | |
| 320 | + "price": "400.00", | |
| 321 | + "compare_at_price": "420.00", | |
| 322 | + "available": true, | |
| 323 | + "sku": "1757661", | |
| 324 | + "option1": "Default Title", | |
| 325 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 326 | + } | |
| 327 | + ], | |
| 328 | + "images": [ | |
| 329 | + { | |
| 330 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/20c4f0553556ceddbdc818162eecd6d7.jpg?v=1764846993" | |
| 331 | + }, | |
| 332 | + { | |
| 333 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/a64882234d19d2acb6eb1bb75836e3ae.jpg?v=1764846993" | |
| 334 | + } | |
| 335 | + ], | |
| 336 | + "options": [ | |
| 337 | + { | |
| 338 | + "name": "Title" | |
| 339 | + } | |
| 340 | + ] | |
| 341 | + }, | |
| 342 | + { | |
| 343 | + "id": 10783793283375, | |
| 344 | + "title": "Taurillon Clemence Picotin Lock 26 GM\u00a0Bamboo", | |
| 345 | + "handle": "hermes-taurillon-clemence-picotin-lock-26-gm-bamboo-1748559", | |
| 346 | + "body_html": "This is an authentic HERMES Taurillon Clemence Picotin Lock 26 GM in Bamboo. This stunning tote is crafted of luxuriously soft clemence calfskin leather in green. The handbag features a unique one-piece reinforced design with leather top handles with a clip and that is can be secured by a padlock at the other end. The top strap opens to a spacious green suede leather interior. This is an excellent tote for everyday wear, both practical and chic, from Hermes!", | |
| 347 | + "published_at": "2025-11-02T18:42:42-08:00", | |
| 348 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 349 | + "vendor": "Hermes", | |
| 350 | + "product_type": "Bags", | |
| 351 | + "tags": [ | |
| 352 | + "Aug-10-incident", | |
| 353 | + "Fall Finds Sale", | |
| 354 | + "Live Shopping with FASHIONPHILE", | |
| 355 | + "Live Shopping with FASHIONPHILEPUSH", | |
| 356 | + "refresh-promo-inclusions", | |
| 357 | + "refresh-shopall", | |
| 358 | + "US" | |
| 359 | + ], | |
| 360 | + "variants": [ | |
| 361 | + { | |
| 362 | + "id": 51982023033135, | |
| 363 | + "title": "Default Title", | |
| 364 | + "price": "2915.00", | |
| 365 | + "compare_at_price": "3240.00", | |
| 366 | + "available": true, | |
| 367 | + "sku": "1748559", | |
| 368 | + "option1": "Default Title", | |
| 369 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 370 | + } | |
| 371 | + ], | |
| 372 | + "images": [ | |
| 373 | + { | |
| 374 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/15d4175b5978f4068046e4392f300979.jpg?v=1764847650" | |
| 375 | + }, | |
| 376 | + { | |
| 377 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/f6f022e29c1e24fb285e666a56172c63.jpg?v=1764847650" | |
| 378 | + } | |
| 379 | + ], | |
| 380 | + "options": [ | |
| 381 | + { | |
| 382 | + "name": "Title" | |
| 383 | + } | |
| 384 | + ] | |
| 385 | + }, | |
| 386 | + { | |
| 387 | + "id": 10650641727791, | |
| 388 | + "title": "Graphene Nickel Rubber 39mm H08 Automatic Watch Noir", | |
| 389 | + "handle": "hermes-graphene-nickel-rubber-39mm-h08-automatic-watch-noir-1658901", | |
| 390 | + "body_html": "This is an authentic HERMES Graphene Nickel Rubber 39mm H08 Automatic Watch Noir. The watch is crafted of graphene composite and features a dark grey dial, slate hour markers, deployment clasp, black rubber strap, sapphire crystal, and an automatic movement.", | |
| 391 | + "published_at": "2026-07-09T04:55:24-07:00", | |
| 392 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 393 | + "vendor": "Hermes", | |
| 394 | + "product_type": "Watches", | |
| 395 | + "tags": [ | |
| 396 | + "Aug-10-incident", | |
| 397 | + "Fall Finds Sale", | |
| 398 | + "Fall Things Sale", | |
| 399 | + "Fashion Watches", | |
| 400 | + "Men's Watches", | |
| 401 | + "refresh-promo-inclusions", | |
| 402 | + "refresh-shopall", | |
| 403 | + "refresh-tag", | |
| 404 | + "The Men's Edit", | |
| 405 | + "update-sale-collection", | |
| 406 | + "US", | |
| 407 | + "Year-End Sale" | |
| 408 | + ], | |
| 409 | + "variants": [ | |
| 410 | + { | |
| 411 | + "id": 51511924916527, | |
| 412 | + "title": "Default Title", | |
| 413 | + "price": "3560.00", | |
| 414 | + "compare_at_price": "3750.00", | |
| 415 | + "available": true, | |
| 416 | + "sku": "1658901", | |
| 417 | + "option1": "Default Title", | |
| 418 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 419 | + } | |
| 420 | + ], | |
| 421 | + "images": [ | |
| 422 | + { | |
| 423 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/4be47f72409a9f22c9fe0ac27d14dead.jpg?v=1764850022" | |
| 424 | + }, | |
| 425 | + { | |
| 426 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/a3478ee8a1e44325140d7381236dfbae.jpg?v=1764850022" | |
| 427 | + } | |
| 428 | + ], | |
| 429 | + "options": [ | |
| 430 | + { | |
| 431 | + "name": "Title" | |
| 432 | + } | |
| 433 | + ] | |
| 434 | + }, | |
| 435 | + { | |
| 436 | + "id": 10693400887599, | |
| 437 | + "title": "Toile Officier Swift Neo Evelyne TPM Feu Jaune D'Or Orange Minium", | |
| 438 | + "handle": "hermes-toile-officier-swift-neo-evelyne-tpm-feu-jaune-dor-orange-minium-1680236", | |
| 439 | + "body_html": "This is an authentic HERMES Toile Officier Swift Neo Evelyne TPM in Feu, Jaune D'Or, and Orange Minium. This small chic messenger bag is crafted of orange toile fabric with orange leather trim. The shoulder bag features a orange leather body strap, silver plated hardware, and a perforated Hermes H oval logo. The top of the bag opens to orange fabric interior.", | |
| 440 | + "published_at": "2026-07-09T04:27:10-07:00", | |
| 441 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 442 | + "vendor": "Hermes", | |
| 443 | + "product_type": "Bags", | |
| 444 | + "tags": [ | |
| 445 | + "Fall Finds Sale", | |
| 446 | + "Fall Things Sale", | |
| 447 | + "Live Shopping with FASHIONPHILE", | |
| 448 | + "Live Shopping with FASHIONPHILEPUSH", | |
| 449 | + "refresh-promo-inclusions", | |
| 450 | + "refresh-shopall", | |
| 451 | + "refresh-tag", | |
| 452 | + "relisted-1", | |
| 453 | + "update-sale-collection", | |
| 454 | + "US", | |
| 455 | + "Year-End Sale" | |
| 456 | + ], | |
| 457 | + "variants": [ | |
| 458 | + { | |
| 459 | + "id": 51659456250159, | |
| 460 | + "title": "Default Title", | |
| 461 | + "price": "4595.00", | |
| 462 | + "compare_at_price": "4595.00", | |
| 463 | + "available": true, | |
| 464 | + "sku": "1680236", | |
| 465 | + "option1": "Default Title", | |
| 466 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 467 | + } | |
| 468 | + ], | |
| 469 | + "images": [ | |
| 470 | + { | |
| 471 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/5ef5cb53a37229d7564435ce5a03b1ec.jpg?v=1764849795" | |
| 472 | + }, | |
| 473 | + { | |
| 474 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/cbeac741cb718b958bc49271e5b08205_46afd227-7244-4956-8791-1a90241ec5f0.jpg?v=1764849795" | |
| 475 | + } | |
| 476 | + ], | |
| 477 | + "options": [ | |
| 478 | + { | |
| 479 | + "name": "Title" | |
| 480 | + } | |
| 481 | + ] | |
| 482 | + }, | |
| 483 | + { | |
| 484 | + "id": 16101958910255, | |
| 485 | + "title": "Cashmere Silk Le Livre D'Esope Shawl 140 Orange Vert Bleu Ciel", | |
| 486 | + "handle": "hermes-cashmere-silk-le-livre-desope-shawl-140-orange-vert-bleu-ciel-1937697", | |
| 487 | + "body_html": "This is an authentic HERMES Cashmere Silk Le Livre D'Esope Shawl 140 in Orange Vert Bleu Ciel. This large shawl is 70% cashmere and 30% silk in natural beige with a multicolor print inspired by Aesop's fables.", | |
| 488 | + "published_at": "2026-07-25T07:48:10-07:00", | |
| 489 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 490 | + "vendor": "Hermes", | |
| 491 | + "product_type": "Accessories", | |
| 492 | + "tags": [ | |
| 493 | + "Aug-10-incident", | |
| 494 | + "US" | |
| 495 | + ], | |
| 496 | + "variants": [ | |
| 497 | + { | |
| 498 | + "id": 58253541572911, | |
| 499 | + "title": "Default Title", | |
| 500 | + "price": "1150.00", | |
| 501 | + "compare_at_price": "1150.00", | |
| 502 | + "available": true, | |
| 503 | + "sku": "1937697", | |
| 504 | + "option1": "Default Title", | |
| 505 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 506 | + } | |
| 507 | + ], | |
| 508 | + "images": [ | |
| 509 | + { | |
| 510 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/4cdb3bdea07c864330e289b494c71626.jpg?v=1784990892" | |
| 511 | + }, | |
| 512 | + { | |
| 513 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/f3c6be01f400f0db5892c1ba0e30900c.jpg?v=1784990892" | |
| 514 | + } | |
| 515 | + ], | |
| 516 | + "options": [ | |
| 517 | + { | |
| 518 | + "name": "Title" | |
| 519 | + } | |
| 520 | + ] | |
| 521 | + }, | |
| 522 | + { | |
| 523 | + "id": 15378155143471, | |
| 524 | + "title": "Calfskin Oran Sandals 39 Rose Mexico", | |
| 525 | + "handle": "hermes-calfskin-oran-sandals-39-rose-mexico-1865093", | |
| 526 | + "body_html": "This is an authentic pair of HERMES Epsom Oran Sandals size 38.5 in Rose Mexico. These stylish slippers are crafted of Epsom calfskin leather in dark pink. They feature a toe strap with a crossover design, similar to the large Hermes H.", | |
| 527 | + "published_at": "2026-04-25T08:18:33-07:00", | |
| 528 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 529 | + "vendor": "Hermes", | |
| 530 | + "product_type": "Accessories", | |
| 531 | + "tags": [ | |
| 532 | + "Aug-10-incident", | |
| 533 | + "US" | |
| 534 | + ], | |
| 535 | + "variants": [ | |
| 536 | + { | |
| 537 | + "id": 57242855145775, | |
| 538 | + "title": "Default Title", | |
| 539 | + "price": "895.00", | |
| 540 | + "compare_at_price": "895.00", | |
| 541 | + "available": true, | |
| 542 | + "sku": "1865093", | |
| 543 | + "option1": "Default Title", | |
| 544 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 545 | + } | |
| 546 | + ], | |
| 547 | + "images": [ | |
| 548 | + { | |
| 549 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/1cf632f2a4cad9415d93f02ee14cbe86.jpg?v=1777130315" | |
| 550 | + }, | |
| 551 | + { | |
| 552 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/00147c08e67ead06a6bb0e9f1a93da85.jpg?v=1777130314" | |
| 553 | + } | |
| 554 | + ], | |
| 555 | + "options": [ | |
| 556 | + { | |
| 557 | + "name": "Title" | |
| 558 | + } | |
| 559 | + ] | |
| 560 | + }, | |
| 561 | + { | |
| 562 | + "id": 10986317349167, | |
| 563 | + "title": "Epsom Kelly Sellier 25 Rose Jaipur", | |
| 564 | + "handle": "hermes-epsom-kelly-sellier-25-rose-jaipur-1780165", | |
| 565 | + "body_html": "This is an authentic HERMES Epsom Kelly Sellier 25 in Rose Jaipur. This iconic and classic tote is crafted of beautifully textured epsom leather in pink. The bag features a sturdy rolled leather top handle with silver palladium plated links, an optional shoulder strap with silver clasps, and a crossover flap and strap closure with a signature Kelly turn lock and padlock with a clochette for the keys. This opens to a spacious and durable matte leather interior with zippered and flat pockets.", | |
| 566 | + "published_at": "2025-12-31T09:11:14-08:00", | |
| 567 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 568 | + "vendor": "Hermes", | |
| 569 | + "product_type": "Bags", | |
| 570 | + "tags": [ | |
| 571 | + "Every Era Sale", | |
| 572 | + "Hermes Kelly 25", | |
| 573 | + "Live Shopping with FASHIONPHILE", | |
| 574 | + "Live Shopping with FASHIONPHILEPUSH", | |
| 575 | + "refresh-promo-inclusions", | |
| 576 | + "US" | |
| 577 | + ], | |
| 578 | + "variants": [ | |
| 579 | + { | |
| 580 | + "id": 52321030111535, | |
| 581 | + "title": "Default Title", | |
| 582 | + "price": "17485.00", | |
| 583 | + "compare_at_price": "17485.00", | |
| 584 | + "available": true, | |
| 585 | + "sku": "1780165", | |
| 586 | + "option1": "Default Title", | |
| 587 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 588 | + } | |
| 589 | + ], | |
| 590 | + "images": [ | |
| 591 | + { | |
| 592 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/ffea8f40352ddf44cecb97904fae6f73.jpg?v=1767201075" | |
| 593 | + }, | |
| 594 | + { | |
| 595 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/7593c0a1a91bbcf487191ae68437f627.jpg?v=1767201075" | |
| 596 | + } | |
| 597 | + ], | |
| 598 | + "options": [ | |
| 599 | + { | |
| 600 | + "name": "Title" | |
| 601 | + } | |
| 602 | + ] | |
| 603 | + }, | |
| 604 | + { | |
| 605 | + "id": 14547693076783, | |
| 606 | + "title": "Sterling Silver Farandole Necklace 160", | |
| 607 | + "handle": "hermes-sterling-silver-farandole-necklace-160-1850926", | |
| 608 | + "body_html": "This is an authentic HERMES Sterling Silver Farandole Necklace 160. The necklace is crafted of sterling silver and features Chaine d' Ancre links in various sizes throughout the chain.", | |
| 609 | + "published_at": "2026-04-07T07:25:16-07:00", | |
| 610 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 611 | + "vendor": "Hermes", | |
| 612 | + "product_type": "Jewelry", | |
| 613 | + "tags": [ | |
| 614 | + "Fall Finds Sale", | |
| 615 | + "Fine Jewelry", | |
| 616 | + "Silver Jewelry", | |
| 617 | + "US" | |
| 618 | + ], | |
| 619 | + "variants": [ | |
| 620 | + { | |
| 621 | + "id": 56376454775087, | |
| 622 | + "title": "Default Title", | |
| 623 | + "price": "3495.00", | |
| 624 | + "compare_at_price": "3495.00", | |
| 625 | + "available": true, | |
| 626 | + "sku": "1850926", | |
| 627 | + "option1": "Default Title", | |
| 628 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 629 | + } | |
| 630 | + ], | |
| 631 | + "images": [ | |
| 632 | + { | |
| 633 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/13677e8ab7fb0aa5477c180fbf0d7a69.jpg?v=1775571918" | |
| 634 | + }, | |
| 635 | + { | |
| 636 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/a86b753e890cc0d700d9903486df2cc4.jpg?v=1775571918" | |
| 637 | + } | |
| 638 | + ], | |
| 639 | + "options": [ | |
| 640 | + { | |
| 641 | + "name": "Title" | |
| 642 | + } | |
| 643 | + ] | |
| 644 | + }, | |
| 645 | + { | |
| 646 | + "id": 15979281613103, | |
| 647 | + "title": "Taurillon Clemence Evelyne TPM Gold", | |
| 648 | + "handle": "hermes-taurillon-clemence-evelyne-tpm-gold-1882282", | |
| 649 | + "body_html": "This is an authentic HERMES Taurillon Clemence Evelyne TPM in Gold. This petite messenger bag is crafted of rich taurillon clemence calfskin leather in golden brown and features a perforated Hermes H oval logo on one side. The bag has a golden brown canvas shoulder strap with polished gold-tone hardware. The top of the bag opens with a crossover strap to a golden brown suede interior.", | |
| 650 | + "published_at": "2026-05-08T09:37:39-07:00", | |
| 651 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 652 | + "vendor": "Hermes", | |
| 653 | + "product_type": "Bags", | |
| 654 | + "tags": [ | |
| 655 | + "Every Era Sale", | |
| 656 | + "US" | |
| 657 | + ], | |
| 658 | + "variants": [ | |
| 659 | + { | |
| 660 | + "id": 57909111849263, | |
| 661 | + "title": "Default Title", | |
| 662 | + "price": "2895.00", | |
| 663 | + "compare_at_price": "2895.00", | |
| 664 | + "available": true, | |
| 665 | + "sku": "1882282", | |
| 666 | + "option1": "Default Title", | |
| 667 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 668 | + } | |
| 669 | + ], | |
| 670 | + "images": [ | |
| 671 | + { | |
| 672 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/164ef501a210ab55d28ab4867cf704fa_5d6c870c-d866-4454-8e5c-27f98323f1e9.jpg?v=1778258261" | |
| 673 | + }, | |
| 674 | + { | |
| 675 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/4dd33d4fbc92c313f42b90c79d510e24_6faaa4ec-ca04-41f4-a810-4d9ad4b5a787.jpg?v=1778258261" | |
| 676 | + } | |
| 677 | + ], | |
| 678 | + "options": [ | |
| 679 | + { | |
| 680 | + "name": "Title" | |
| 681 | + } | |
| 682 | + ] | |
| 683 | + }, | |
| 684 | + { | |
| 685 | + "id": 15572014858543, | |
| 686 | + "title": "Togo Sangle Cordage Videpoches Alezan Beige Marfa Gris Pale", | |
| 687 | + "handle": "hermes-togo-sangle-cordage-videpoches-alezan-beige-marfa-gris-pale-1856086", | |
| 688 | + "body_html": "This is an authentic HERMES Togo Sangle Cordage Videpoches in Alezan ,Beige Marfa and Gris Pale. This is a small messenger style bag that is finely crafted of leather in brown. This bag features a striped beige and pale grey canvas shoulder strap with silver palladium hardware. The fold over top flap opens to a matching leather interior with a flat pocket.", | |
| 689 | + "published_at": "2026-04-29T09:13:25-07:00", | |
| 690 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 691 | + "vendor": "Hermes", | |
| 692 | + "product_type": "Bags", | |
| 693 | + "tags": [ | |
| 694 | + "Fall Finds Sale", | |
| 695 | + "refresh-051726", | |
| 696 | + "refresh-090326", | |
| 697 | + "US" | |
| 698 | + ], | |
| 699 | + "variants": [ | |
| 700 | + { | |
| 701 | + "id": 57450487316783, | |
| 702 | + "title": "Default Title", | |
| 703 | + "price": "3750.00", | |
| 704 | + "compare_at_price": "3750.00", | |
| 705 | + "available": true, | |
| 706 | + "sku": "1856086", | |
| 707 | + "option1": "Default Title", | |
| 708 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 709 | + } | |
| 710 | + ], | |
| 711 | + "images": [ | |
| 712 | + { | |
| 713 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/c7f99f898b07d1110b444b1dd3ab6adf.jpg?v=1777479206" | |
| 714 | + }, | |
| 715 | + { | |
| 716 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/819b149545c0242025d3e9a11db9eecc.jpg?v=1777479206" | |
| 717 | + } | |
| 718 | + ], | |
| 719 | + "options": [ | |
| 720 | + { | |
| 721 | + "name": "Title" | |
| 722 | + } | |
| 723 | + ] | |
| 724 | + }, | |
| 725 | + { | |
| 726 | + "id": 11258637615407, | |
| 727 | + "title": "Taurillon Clemence Picotin Lock 18 PM Etoupe", | |
| 728 | + "handle": "hermes-taurillon-clemence-picotin-lock-18-pm-etoupe-1794590", | |
| 729 | + "body_html": "This is an authentic HERMES Taurillon Clemence Picotin Lock 18 PM in Etoupe. This mini tote is crafted of grained calfskin leather in taupe with white contrast stitching. It features two top handles and opens to a taupe suede and leather interior.", | |
| 730 | + "published_at": "2026-01-13T13:01:14-08:00", | |
| 731 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 732 | + "vendor": "Hermes", | |
| 733 | + "product_type": "Bags", | |
| 734 | + "tags": [ | |
| 735 | + "Every Era Sale", | |
| 736 | + "Fall Finds Sale", | |
| 737 | + "US" | |
| 738 | + ], | |
| 739 | + "variants": [ | |
| 740 | + { | |
| 741 | + "id": 52609244987695, | |
| 742 | + "title": "Default Title", | |
| 743 | + "price": "3950.00", | |
| 744 | + "compare_at_price": "3950.00", | |
| 745 | + "available": true, | |
| 746 | + "sku": "1794590", | |
| 747 | + "option1": "Default Title", | |
| 748 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 749 | + } | |
| 750 | + ], | |
| 751 | + "images": [ | |
| 752 | + { | |
| 753 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/e97a3a03ab2aa27de765636f5348a79b.jpg?v=1774373208" | |
| 754 | + }, | |
| 755 | + { | |
| 756 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/2af2eefd9c0930ef154f62d8a6d957f8.jpg?v=1774373208" | |
| 757 | + } | |
| 758 | + ], | |
| 759 | + "options": [ | |
| 760 | + { | |
| 761 | + "name": "Title" | |
| 762 | + } | |
| 763 | + ] | |
| 764 | + }, | |
| 765 | + { | |
| 766 | + "id": 10986346578223, | |
| 767 | + "title": "Chevre Mysore Mini Plume Rose Darling", | |
| 768 | + "handle": "hermes-chevre-mysore-mini-plume-rose-darling-1784414", | |
| 769 | + "body_html": "This is an authentic HERMES Chevre Mysore Mini Plume in Rose Darling. This simple, elegant tote is crafted of goatskin leather in light pink, with thin rolled leather handles, and polished palladium hardware. The zipper opens to a matching leather interior.", | |
| 770 | + "published_at": "2025-12-31T10:59:33-08:00", | |
| 771 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 772 | + "vendor": "Hermes", | |
| 773 | + "product_type": "Bags", | |
| 774 | + "tags": [ | |
| 775 | + "Every Era Sale", | |
| 776 | + "Fall Finds Sale", | |
| 777 | + "Live Shopping with FASHIONPHILE", | |
| 778 | + "Live Shopping with FASHIONPHILEPUSH", | |
| 779 | + "Luxury Lovers Event", | |
| 780 | + "Must-have Mini Bags", | |
| 781 | + "refresh-051726", | |
| 782 | + "refresh-promo-inclusions", | |
| 783 | + "Spring Fever", | |
| 784 | + "US" | |
| 785 | + ], | |
| 786 | + "variants": [ | |
| 787 | + { | |
| 788 | + "id": 52321112228143, | |
| 789 | + "title": "Default Title", | |
| 790 | + "price": "7645.00", | |
| 791 | + "compare_at_price": "8050.00", | |
| 792 | + "available": true, | |
| 793 | + "sku": "1784414", | |
| 794 | + "option1": "Default Title", | |
| 795 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 796 | + } | |
| 797 | + ], | |
| 798 | + "images": [ | |
| 799 | + { | |
| 800 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/e0e2593d6300c3645b5a3b244f19468e.jpg?v=1767265511" | |
| 801 | + }, | |
| 802 | + { | |
| 803 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/bc851f73e81ab41d9dddb29a9fb1dcf9.jpg?v=1767265511" | |
| 804 | + } | |
| 805 | + ], | |
| 806 | + "options": [ | |
| 807 | + { | |
| 808 | + "name": "Title" | |
| 809 | + } | |
| 810 | + ] | |
| 811 | + }, | |
| 812 | + { | |
| 813 | + "id": 15020928958767, | |
| 814 | + "title": "Taurillon Clemence Mini Lindy 20 Sun", | |
| 815 | + "handle": "hermes-taurillon-clemence-mini-lindy-20-sun-1860484", | |
| 816 | + "body_html": "This is an authentic HERMES Taurillon Clemence Mini Lindy 20 in Sun. This mini shoulder bag is crafted of calfskin leather in yellow. The bag features rolled leather top handles, connected with a looping shoulder strap and gold hardware. The top zippers open to a matching leather interior with patch pockets.", | |
| 817 | + "published_at": "2026-04-17T08:45:48-07:00", | |
| 818 | + "updated_at": "2026-09-06T23:28:30-07:00", | |
| 819 | + "vendor": "Hermes", | |
| 820 | + "product_type": "Bags", | |
| 821 | + "tags": [ | |
| 822 | + "Aug-10-incident", | |
| 823 | + "Every Era Sale", | |
| 824 | + "Must-have Mini Bags", | |
| 825 | + "refresh-051726", | |
| 826 | + "US" | |
| 827 | + ], | |
| 828 | + "variants": [ | |
| 829 | + { | |
| 830 | + "id": 56870419398959, | |
| 831 | + "title": "Default Title", | |
| 832 | + "price": "6595.00", | |
| 833 | + "compare_at_price": "6595.00", | |
| 834 | + "available": true, | |
| 835 | + "sku": "1860484", | |
| 836 | + "option1": "Default Title", | |
| 837 | + "updated_at": "2026-09-06T23:28:30-07:00" | |
| 838 | + } | |
| 839 | + ], | |
| 840 | + "images": [ | |
| 841 | + { | |
| 842 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/a2298b492de1a8e26f7c6dc10dd7482f.jpg?v=1776440750" | |
| 843 | + }, | |
| 844 | + { | |
| 845 | + "src": "https://cdn.shopify.com/s/files/1/0894/3186/7695/files/1a5da16b174bf0ef9378caaf4a86f538.jpg?v=1776440750" | |
| 846 | + } | |
| 847 | + ], | |
| 848 | + "options": [ | |
| 849 | + { | |
| 850 | + "name": "Title" | |
| 851 | + } | |
| 852 | + ] | |
| 853 | + } | |
| 854 | + ] | |
| 855 | + } | |
| 856 | + }, | |
| 857 | + "expect": { | |
| 858 | + "minCount": 1, | |
| 859 | + "kinds": [ | |
| 860 | + "listing" | |
| 861 | + ] | |
| 862 | + }, | |
| 863 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts; trimmed to 25 products for size", | |
| 864 | + "capturedAt": "2026-09-07T06:28:30.639Z" | |
| 865 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/flight-club/search-air-jordans-1.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.flightclub.com/air-jordans", | |
| 4 | + "externalId": "search:air-jordans:1", | |
| 5 | + "kind": "catalog_item", | |
| 6 | + "engine": "scrapfly", | |
| 7 | + "fetchedAt": "2026-09-07T06:29:14.353Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "search_page", | |
| 10 | + "url": "https://www.flightclub.com/air-jordans", | |
| 11 | + "seed": "air-jordans", | |
| 12 | + "page": 1, | |
| 13 | + "currency": "USD", | |
| 14 | + "total": 10000, | |
| 15 | + "items": [ | |
| 16 | + { | |
| 17 | + "id": "1709894", | |
| 18 | + "name": "Jordan 4 Retro 'Rare Air - Tour Yellow' 2026", | |
| 19 | + "brand": "Air Jordan", | |
| 20 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1709894/118285336/1.jpeg", | |
| 21 | + "price": 171, | |
| 22 | + "retail": 220, | |
| 23 | + "slug": "air-jordan-4-retro-rare-air-tour-yellow-io2463-102" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "id": "1725548", | |
| 27 | + "name": "Comme des GarçOns x Jordan 11 Retro 'Black'", | |
| 28 | + "brand": "Air Jordan", | |
| 29 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1725548/118512328/1.jpeg", | |
| 30 | + "price": 559, | |
| 31 | + "retail": 250, | |
| 32 | + "slug": "comme-des-garcons-x-air-jordan-11-retro-black-iv7639-001" | |
| 33 | + }, | |
| 34 | + { | |
| 35 | + "id": "1775278", | |
| 36 | + "name": "Jordan 8 Retro 'Chrome' 2026", | |
| 37 | + "brand": "Air Jordan", | |
| 38 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1775278/117974505/1.jpeg", | |
| 39 | + "price": 230, | |
| 40 | + "retail": 215, | |
| 41 | + "slug": "air-jordan-8-chrome-305381-007" | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "id": "1758807", | |
| 45 | + "name": "Jordan 12 Retro 'Bucks'", | |
| 46 | + "brand": "Air Jordan", | |
| 47 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1758807/118494790/1.jpeg", | |
| 48 | + "price": 247, | |
| 49 | + "retail": 200, | |
| 50 | + "slug": "air-jordan-12-retro-bucks-ct8013-103" | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + "id": "1708808", | |
| 54 | + "name": "Jordan 9 Retro OG 'Space Jam' 2026", | |
| 55 | + "brand": "Air Jordan", | |
| 56 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1708808/118349032/1.jpeg", | |
| 57 | + "price": 253, | |
| 58 | + "retail": 215, | |
| 59 | + "slug": "air-jordan-9-retro-og-space-jam-2026-hv4794-106" | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + "id": "1653296", | |
| 63 | + "name": "Jordan 4 Retro 'Toro Bravo' 2026", | |
| 64 | + "brand": "Air Jordan", | |
| 65 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1653296/115979193/1.jpeg", | |
| 66 | + "price": 107, | |
| 67 | + "retail": 220, | |
| 68 | + "slug": "air-jordan-4-retro-toro-bravo-2026-fq8138-600" | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "id": "1773219", | |
| 72 | + "name": "Jordan 3 'True Blue' 2026", | |
| 73 | + "brand": "Air Jordan", | |
| 74 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1773219/117652138/1.jpeg", | |
| 75 | + "price": 178, | |
| 76 | + "retail": 230, | |
| 77 | + "slug": "air-jordan-3-true-blue-2026-if4396-104" | |
| 78 | + }, | |
| 79 | + { | |
| 80 | + "id": "1711594", | |
| 81 | + "name": "Jordan 5 Retro 'Black Carolina / UNC' 2026", | |
| 82 | + "brand": "Air Jordan", | |
| 83 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1711594/116977807/1.jpeg", | |
| 84 | + "price": 251, | |
| 85 | + "retail": 220, | |
| 86 | + "slug": "air-jordan-5-retro-black-university-blue-2026-dd0587-008" | |
| 87 | + }, | |
| 88 | + { | |
| 89 | + "id": "1734993", | |
| 90 | + "name": "Awake NY x Jordan 6 Retro 'Blueberry'", | |
| 91 | + "brand": "Air Jordan", | |
| 92 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1734993/118494786/1.jpeg", | |
| 93 | + "price": 287, | |
| 94 | + "retail": 230, | |
| 95 | + "slug": "awake-ny-x-air-jordan-6-retro-midnight-navy-infrared-iq5706-400" | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "id": "1725546", | |
| 99 | + "name": "Comme des GarçOns x Jordan 11 Retro 'White'", | |
| 100 | + "brand": "Air Jordan", | |
| 101 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1725546/118512277/1.jpeg", | |
| 102 | + "price": 575, | |
| 103 | + "retail": 250, | |
| 104 | + "slug": "comme-des-garcons-x-air-jordan-11-retro-white-iv7639-100" | |
| 105 | + }, | |
| 106 | + { | |
| 107 | + "id": "1567409", | |
| 108 | + "name": "Jordan 11 Retro 'Gamma Blue' 2025", | |
| 109 | + "brand": "Air Jordan", | |
| 110 | + "image": "https://cdn.flightclub.com/TEMPLATE/479033/1.jpg", | |
| 111 | + "price": 238, | |
| 112 | + "retail": 230, | |
| 113 | + "slug": "air-jordan-11-retro-gamma-blue-2025-ct8012-017" | |
| 114 | + }, | |
| 115 | + { | |
| 116 | + "id": "1771073", | |
| 117 | + "name": "Jordan 5 Retro GS 'Black Carolina / UNC' 2026", | |
| 118 | + "brand": "Air Jordan", | |
| 119 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1771073/117093270/1.jpeg", | |
| 120 | + "price": 165, | |
| 121 | + "retail": 155, | |
| 122 | + "slug": "air-jordan-5-retro-gs-black-university-blue-2026-440888-008" | |
| 123 | + }, | |
| 124 | + { | |
| 125 | + "id": "1648826", | |
| 126 | + "name": "Jordan 5 Retro 'Wolf Grey' 2026", | |
| 127 | + "brand": "Air Jordan", | |
| 128 | + "image": "https://cdn.flightclub.com/TEMPLATE/499850/1.jpg", | |
| 129 | + "price": 206, | |
| 130 | + "retail": 220, | |
| 131 | + "slug": "air-jordan-5-retro-wolf-grey-2026-dd0587-002" | |
| 132 | + }, | |
| 133 | + { | |
| 134 | + "id": "1567309", | |
| 135 | + "name": "Jordan 4 Retro 'Black Cat' 2025", | |
| 136 | + "brand": "Air Jordan", | |
| 137 | + "image": "https://cdn.flightclub.com/TEMPLATE/478961/1.jpg", | |
| 138 | + "price": 334, | |
| 139 | + "retail": 225, | |
| 140 | + "slug": "air-jordan-4-retro-black-cat-2025-fv5029-010" | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "id": "1487425", | |
| 144 | + "name": "Jordan 4 Retro OG 'White Cement' 2025", | |
| 145 | + "brand": "Air Jordan", | |
| 146 | + "image": "https://cdn.flightclub.com/TEMPLATE/463428/1.jpg", | |
| 147 | + "price": 260, | |
| 148 | + "retail": 215, | |
| 149 | + "slug": "air-jordan-4-retro-og-white-cement-2025-fv5029-100" | |
| 150 | + }, | |
| 151 | + { | |
| 152 | + "id": "1708924", | |
| 153 | + "name": "Nigel Sylvester x Jordan 4 Retro OG SP 'Brick After Brick'", | |
| 154 | + "brand": "Air Jordan", | |
| 155 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1708924/116481215/1.jpeg", | |
| 156 | + "price": 252, | |
| 157 | + "retail": 225, | |
| 158 | + "slug": "nigel-sylvester-x-air-jordan-4-retro-og-sail-iq8055-100" | |
| 159 | + }, | |
| 160 | + { | |
| 161 | + "id": "1716875", | |
| 162 | + "name": "Travis Scott x Jordan 1 Retro Low OG 'Sail Tropical Pink'", | |
| 163 | + "brand": "Air Jordan", | |
| 164 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1716875/116600901/1.jpeg", | |
| 165 | + "price": 288, | |
| 166 | + "retail": 155, | |
| 167 | + "slug": "travis-scott-x-air-jordan-1-retro-low-og-sail-tropical-pink-iq7604-101" | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "id": "1403298", | |
| 171 | + "name": "Jordan 5 Retro OG 'Black Metallic Reimagined'", | |
| 172 | + "brand": "Air Jordan", | |
| 173 | + "image": "https://cdn.flightclub.com/TEMPLATE/406513/1.jpg", | |
| 174 | + "price": 384, | |
| 175 | + "retail": 210, | |
| 176 | + "slug": "air-jordan-5-og-metallic-2025-hf3975-001" | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "id": "1758284", | |
| 180 | + "name": "Free The Youth x Jordan 16 Retro OG 'Metallic Silver'", | |
| 181 | + "brand": "Air Jordan", | |
| 182 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1758284/117974490/1.jpeg", | |
| 183 | + "price": 282, | |
| 184 | + "retail": 250, | |
| 185 | + "slug": "free-the-youth-x-air-jordan-16-retro-og-metallic-silver-iv7638-001" | |
| 186 | + }, | |
| 187 | + { | |
| 188 | + "id": "1562975", | |
| 189 | + "name": "Jordan 13 Retro 'Chicago' 2026", | |
| 190 | + "brand": "Air Jordan", | |
| 191 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1562975/115793438/1.jpeg", | |
| 192 | + "price": 151, | |
| 193 | + "retail": 200, | |
| 194 | + "slug": "air-jordan-13-retro-he-got-game-2026-414571-102" | |
| 195 | + }, | |
| 196 | + { | |
| 197 | + "id": "1711596", | |
| 198 | + "name": "Jordan 13 Retro 'Flint' 2026", | |
| 199 | + "brand": "Air Jordan", | |
| 200 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1711596/117766931/1.jpeg", | |
| 201 | + "price": 169, | |
| 202 | + "retail": 215, | |
| 203 | + "slug": "air-jordan-13-retro-flint-2026-iw3808-400" | |
| 204 | + }, | |
| 205 | + { | |
| 206 | + "id": "1707102", | |
| 207 | + "name": "Jordan 4 Retro 'Flight Club'", | |
| 208 | + "brand": "Air Jordan", | |
| 209 | + "image": "https://cdn.flightclub.com/TEMPLATE/509845/1.jpg", | |
| 210 | + "price": 165, | |
| 211 | + "retail": 220, | |
| 212 | + "slug": "air-jordan-4-retro-flight-club-im4002-100" | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "id": "1618893", | |
| 216 | + "name": "Jordan 6 Retro 'Reverse Infrared Salesman'", | |
| 217 | + "brand": "Air Jordan", | |
| 218 | + "image": "https://cdn.flightclub.com/TEMPLATE/492142/1.jpg", | |
| 219 | + "price": 171, | |
| 220 | + "retail": 210, | |
| 221 | + "slug": "air-jordan-6-retro-reverse-infrared-ct8529-001" | |
| 222 | + }, | |
| 223 | + { | |
| 224 | + "id": "1734994", | |
| 225 | + "name": "Awake NY x Jordan 6 Retro 'Bubblegum'", | |
| 226 | + "brand": "Air Jordan", | |
| 227 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1734994/117655662/1.jpeg", | |
| 228 | + "price": 395, | |
| 229 | + "retail": 230, | |
| 230 | + "slug": "awake-ny-x-air-jordan-6-retro-playful-pink-infrared-iq5706-600" | |
| 231 | + }, | |
| 232 | + { | |
| 233 | + "id": "1581606", | |
| 234 | + "name": "Undefeated x Jordan 4 Retro 2025", | |
| 235 | + "brand": "Air Jordan", | |
| 236 | + "image": "https://cdn.flightclub.com/TEMPLATE/481883/1.jpg", | |
| 237 | + "price": 170, | |
| 238 | + "retail": 225, | |
| 239 | + "slug": "undefeated-x-air-jordan-4-retro-ib1519-200" | |
| 240 | + }, | |
| 241 | + { | |
| 242 | + "id": "1686126", | |
| 243 | + "name": "Jordan 12 Retro 'Bloodline'", | |
| 244 | + "brand": "Air Jordan", | |
| 245 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1686126/116600869/1.jpeg", | |
| 246 | + "price": 196, | |
| 247 | + "retail": 215, | |
| 248 | + "slug": "air-jordan-jordan-12-retro-bloodline-ct8013-003" | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "id": "1779780", | |
| 252 | + "name": "Jordan 8 Retro GS 'Chrome' 2026", | |
| 253 | + "brand": "Air Jordan", | |
| 254 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1779780/118406277/1.jpeg", | |
| 255 | + "price": 154, | |
| 256 | + "retail": 165, | |
| 257 | + "slug": "air-jordan-8-retro-gs-chrome-2026-305368-007" | |
| 258 | + }, | |
| 259 | + { | |
| 260 | + "id": "1729522", | |
| 261 | + "name": "Jordan 6 Retro 'Oreo' 2026", | |
| 262 | + "brand": "Air Jordan", | |
| 263 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1729522/117974464/1.jpeg", | |
| 264 | + "price": 157, | |
| 265 | + "retail": null, | |
| 266 | + "slug": "air-jordan-6-oreo-2026-ct8529-108" | |
| 267 | + }, | |
| 268 | + { | |
| 269 | + "id": "1715425", | |
| 270 | + "name": "Travis Scott x Jordan 1 Retro Low OG 'Shy Pink'", | |
| 271 | + "brand": "Air Jordan", | |
| 272 | + "image": "https://cdn.flightclub.com/PRODUCT_TEMPLATE/1715425/116720449/1.jpeg", | |
| 273 | + "price": 312, | |
| 274 | + "retail": 155, | |
| 275 | + "slug": "travis-scott-x-air-jordan-1-retro-low-og-muslin-pink-iq7604-100" | |
| 276 | + }, | |
| 277 | + { | |
| 278 | + "id": "1410752", | |
| 279 | + "name": "Nike SB x Jordan 4 Retro SP 'Navy'", | |
| 280 | + "brand": "Air Jordan", | |
| 281 | + "image": "https://cdn.flightclub.com/TEMPLATE/408296/1.jpg", | |
| 282 | + "price": 166, | |
| 283 | + "retail": 225, | |
| 284 | + "slug": "nike-sb-x-air-jordan-4-retro-sp-navy-dr5415-100" | |
| 285 | + } | |
| 286 | + ] | |
| 287 | + } | |
| 288 | + }, | |
| 289 | + "expect": { | |
| 290 | + "minCount": 1, | |
| 291 | + "kinds": [ | |
| 292 | + "catalog_item", | |
| 293 | + "price_observation", | |
| 294 | + "listing" | |
| 295 | + ] | |
| 296 | + }, | |
| 297 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 298 | + "capturedAt": "2026-09-07T06:29:14.360Z" | |
| 299 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/hypeboost/grid-air-jordan-1.json
+423 −0
@@ -0,0 +1,423 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://hypeboost.com/en/category/sneakers/air-jordan", | |
| 4 | + "externalId": "grid:air-jordan:1", | |
| 5 | + "kind": "catalog_item", | |
| 6 | + "engine": "firecrawl", | |
| 7 | + "fetchedAt": "2026-09-07T06:29:15.423Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "grid_page", | |
| 10 | + "url": "https://hypeboost.com/en/category/sneakers/air-jordan", | |
| 11 | + "seed": "air-jordan", | |
| 12 | + "page": 1, | |
| 13 | + "total": 2161, | |
| 14 | + "items": [ | |
| 15 | + { | |
| 16 | + "id": "53484", | |
| 17 | + "name": "Jordan 4 Retro Cave Stone", | |
| 18 | + "brand": "Air Jordan", | |
| 19 | + "category": "Jordan 4", | |
| 20 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-cave-stone", | |
| 21 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-cave-stone/w300/img01.jpg", | |
| 22 | + "price": 155, | |
| 23 | + "currency": "EUR", | |
| 24 | + "sku": null | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "id": "56064", | |
| 28 | + "name": "Jordan 1 Retro Low OG SP Travis Scott Shy Pink", | |
| 29 | + "brand": "Air Jordan", | |
| 30 | + "category": "Jordan 1", | |
| 31 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-low-og-sp-travis-scott-shy-pink", | |
| 32 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-low-og-sp-travis-scott-shy-pink/w300/img01.jpg", | |
| 33 | + "price": 360, | |
| 34 | + "currency": "EUR", | |
| 35 | + "sku": null | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "id": "55651", | |
| 39 | + "name": "Jordan 1 Retro High Virgil Abloh Archive Alaska", | |
| 40 | + "brand": "Air Jordan", | |
| 41 | + "category": "Jordan 1", | |
| 42 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-high-virgil-abloh-archive-alaska", | |
| 43 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-high-virgil-abloh-archive-alaska/w300/img01.jpg", | |
| 44 | + "price": 328, | |
| 45 | + "currency": "EUR", | |
| 46 | + "sku": null | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + "id": "55092", | |
| 50 | + "name": "Jordan 4 Retro OG Flight Club", | |
| 51 | + "brand": "Air Jordan", | |
| 52 | + "category": "", | |
| 53 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-og-flight-club", | |
| 54 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-og-flight-club/w300/img01.jpg", | |
| 55 | + "price": 164, | |
| 56 | + "currency": "EUR", | |
| 57 | + "sku": null | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + "id": "53331", | |
| 61 | + "name": "Jordan 4 Retro Black Cat (2025)", | |
| 62 | + "brand": "Air Jordan", | |
| 63 | + "category": "Jordan 4", | |
| 64 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-black-cat-2025", | |
| 65 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-black-cat-2025/w300/img01.jpg", | |
| 66 | + "price": 242, | |
| 67 | + "currency": "EUR", | |
| 68 | + "sku": null | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "id": "56416", | |
| 72 | + "name": "Jordan 1 Mid Summit White Vintage Lichen", | |
| 73 | + "brand": "Air Jordan", | |
| 74 | + "category": "Jordan 1", | |
| 75 | + "url": "https://hypeboost.com/en/product/air-jordan-1-mid-summit-white-vintage-lichen", | |
| 76 | + "image": "https://img.hypeboost.com/products/air-jordan-1-mid-summit-white-vintage-lichen/w300/img01.jpg", | |
| 77 | + "price": 110, | |
| 78 | + "currency": "EUR", | |
| 79 | + "sku": null | |
| 80 | + }, | |
| 81 | + { | |
| 82 | + "id": "56361", | |
| 83 | + "name": "Jordan Spizike Low Black Gamma Blue", | |
| 84 | + "brand": "Air Jordan", | |
| 85 | + "category": "Other Jordans", | |
| 86 | + "url": "https://hypeboost.com/en/product/air-jordan-spizike-low-black-gamma-blue", | |
| 87 | + "image": "https://img.hypeboost.com/products/air-jordan-spizike-low-black-gamma-blue/w300/img01.jpg", | |
| 88 | + "price": 129, | |
| 89 | + "currency": "EUR", | |
| 90 | + "sku": null | |
| 91 | + }, | |
| 92 | + { | |
| 93 | + "id": "56238", | |
| 94 | + "name": "Jordan 1 Retro Low OG SP Travis Scott Sail Tropical Pink (PS)", | |
| 95 | + "brand": "Air Jordan", | |
| 96 | + "category": "", | |
| 97 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-low-og-sp-travis-scott-sail-tropical-pink-ps", | |
| 98 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-low-og-sp-travis-scott-sail-tropical-pink-ps/w300/img01.jpg", | |
| 99 | + "price": 136, | |
| 100 | + "currency": "EUR", | |
| 101 | + "sku": null | |
| 102 | + }, | |
| 103 | + { | |
| 104 | + "id": "55839", | |
| 105 | + "name": "Jordan 1 Retro High OG Flight Club", | |
| 106 | + "brand": "Air Jordan", | |
| 107 | + "category": "Jordan 1", | |
| 108 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-high-og-flight-club", | |
| 109 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-high-og-flight-club/w300/img01.jpg", | |
| 110 | + "price": 129, | |
| 111 | + "currency": "EUR", | |
| 112 | + "sku": null | |
| 113 | + }, | |
| 114 | + { | |
| 115 | + "id": "55264", | |
| 116 | + "name": "Jordan 4 Retro Valentine's Day Sierra Red (Women's)", | |
| 117 | + "brand": "Air Jordan", | |
| 118 | + "category": "Jordan 4", | |
| 119 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-pale-ivory-sierra-red-womens", | |
| 120 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-pale-ivory-sierra-red-womens/w300/img01.jpg", | |
| 121 | + "price": 180, | |
| 122 | + "currency": "EUR", | |
| 123 | + "sku": null | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "id": "53658", | |
| 127 | + "name": "Jordan MVP Off White Gym Red White Black (W)", | |
| 128 | + "brand": "Air Jordan", | |
| 129 | + "category": "", | |
| 130 | + "url": "https://hypeboost.com/en/product/air-jordan-jumpman-mvp-off-white-gym-red-white-black-womens", | |
| 131 | + "image": "https://img.hypeboost.com/products/air-jordan-jumpman-mvp-off-white-gym-red-white-black-womens/w300/img01.jpg", | |
| 132 | + "price": 107, | |
| 133 | + "currency": "EUR", | |
| 134 | + "sku": null | |
| 135 | + }, | |
| 136 | + { | |
| 137 | + "id": "53379", | |
| 138 | + "name": "Jordan 1 Mid Cobalt Bliss (GS)", | |
| 139 | + "brand": "Air Jordan", | |
| 140 | + "category": "Jordan 1", | |
| 141 | + "url": "https://hypeboost.com/en/product/air-jordan-1-mid-cobalt-bliss-gs", | |
| 142 | + "image": "https://img.hypeboost.com/products/air-jordan-1-mid-cobalt-bliss-gs/w300/img01.jpg", | |
| 143 | + "price": 115, | |
| 144 | + "currency": "EUR", | |
| 145 | + "sku": null | |
| 146 | + }, | |
| 147 | + { | |
| 148 | + "id": "52463", | |
| 149 | + "name": "Jordan 4 Retro 'White Cement' (2025)", | |
| 150 | + "brand": "Air Jordan", | |
| 151 | + "category": "Jordan 4", | |
| 152 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-white-cement-2025", | |
| 153 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-white-cement-2025/w300/img01.jpg", | |
| 154 | + "price": 204, | |
| 155 | + "currency": "EUR", | |
| 156 | + "sku": null | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "id": "52313", | |
| 160 | + "name": "Jordan 4 Retro SB 'Navy'", | |
| 161 | + "brand": "Air Jordan", | |
| 162 | + "category": "Jordan 4", | |
| 163 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-sb-navy", | |
| 164 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-sb-navy/w300/img01.jpg", | |
| 165 | + "price": 199, | |
| 166 | + "currency": "EUR", | |
| 167 | + "sku": null | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "id": "52105", | |
| 171 | + "name": "Jordan 4 Retro 'Fear' (2024) (GS)", | |
| 172 | + "brand": "Air Jordan", | |
| 173 | + "category": "Jordan 4", | |
| 174 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-fear-2024-gs", | |
| 175 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-fear-2024-gs/w300/img01.jpg", | |
| 176 | + "price": 134, | |
| 177 | + "currency": "EUR", | |
| 178 | + "sku": null | |
| 179 | + }, | |
| 180 | + { | |
| 181 | + "id": "51599", | |
| 182 | + "name": "Jordan 1 Retro Low OG SP 'Travis Scott Medium Olive'", | |
| 183 | + "brand": "Air Jordan", | |
| 184 | + "category": "Jordan 1", | |
| 185 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-low-og-sp-travis-scott-medium-olive", | |
| 186 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-low-og-sp-travis-scott-medium-olive/w300/img01.jpg", | |
| 187 | + "price": 568, | |
| 188 | + "currency": "EUR", | |
| 189 | + "sku": null | |
| 190 | + }, | |
| 191 | + { | |
| 192 | + "id": "50874", | |
| 193 | + "name": "Jordan 4 Retro 'Oxidized Green'", | |
| 194 | + "brand": "Air Jordan", | |
| 195 | + "category": "Jordan 4", | |
| 196 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-oxidized-green", | |
| 197 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-oxidized-green/w300/img01.jpg", | |
| 198 | + "price": 220, | |
| 199 | + "currency": "EUR", | |
| 200 | + "sku": null | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "id": "50853", | |
| 204 | + "name": "Jordan 1 Retro Low OG SP 'Travis Scott Canary' (W)", | |
| 205 | + "brand": "Air Jordan", | |
| 206 | + "category": "Jordan 1", | |
| 207 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-low-og-sp-travis-scott-canary-womens", | |
| 208 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-low-og-sp-travis-scott-canary-womens/w300/img01.jpg", | |
| 209 | + "price": 370, | |
| 210 | + "currency": "EUR", | |
| 211 | + "sku": null | |
| 212 | + }, | |
| 213 | + { | |
| 214 | + "id": "50659", | |
| 215 | + "name": "Jordan 4 Retro 'Military Blue' (2024)", | |
| 216 | + "brand": "Air Jordan", | |
| 217 | + "category": "Jordan 4", | |
| 218 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-military-blue-2024", | |
| 219 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-military-blue-2024/w300/img01.jpg", | |
| 220 | + "price": 198, | |
| 221 | + "currency": "EUR", | |
| 222 | + "sku": null | |
| 223 | + }, | |
| 224 | + { | |
| 225 | + "id": "50400", | |
| 226 | + "name": "Air Jordan 4 Retro 'Bred Reimagined' (GS)", | |
| 227 | + "brand": "Air Jordan", | |
| 228 | + "category": "Jordan 4", | |
| 229 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-bred-reimagined-gs", | |
| 230 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-bred-reimagined-gs/w300/img01.jpg", | |
| 231 | + "price": 97, | |
| 232 | + "currency": "EUR", | |
| 233 | + "sku": null | |
| 234 | + }, | |
| 235 | + { | |
| 236 | + "id": "50395", | |
| 237 | + "name": "Jordan 4 Retro 'Bred Reimagined'", | |
| 238 | + "brand": "Air Jordan", | |
| 239 | + "category": "Jordan 4", | |
| 240 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-bred-reimagined", | |
| 241 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-bred-reimagined/w300/img01.jpg", | |
| 242 | + "price": 167, | |
| 243 | + "currency": "EUR", | |
| 244 | + "sku": null | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "id": "49063", | |
| 248 | + "name": "Air Jordan 4 Retro 'Thunder' (2023)", | |
| 249 | + "brand": "Air Jordan", | |
| 250 | + "category": "Jordan 4", | |
| 251 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-thunder-2023", | |
| 252 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-thunder-2023/w300/img01.jpg", | |
| 253 | + "price": 240, | |
| 254 | + "currency": "EUR", | |
| 255 | + "sku": null | |
| 256 | + }, | |
| 257 | + { | |
| 258 | + "id": "47888", | |
| 259 | + "name": "Air Jordan 1 Retro High OG Yellow Toe", | |
| 260 | + "brand": "Air Jordan", | |
| 261 | + "category": "Jordan 1", | |
| 262 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-high-og-yellow-toe", | |
| 263 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-high-og-yellow-toe/w300/img01.jpg", | |
| 264 | + "price": 109, | |
| 265 | + "currency": "EUR", | |
| 266 | + "sku": null | |
| 267 | + }, | |
| 268 | + { | |
| 269 | + "id": "44695", | |
| 270 | + "name": "Air Jordan 1 Retro High Dark Mocha", | |
| 271 | + "brand": "Air Jordan", | |
| 272 | + "category": "Jordan 1", | |
| 273 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-high-dark-mocha", | |
| 274 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-high-dark-mocha/w300/img01.jpg", | |
| 275 | + "price": 204, | |
| 276 | + "currency": "EUR", | |
| 277 | + "sku": null | |
| 278 | + }, | |
| 279 | + { | |
| 280 | + "id": "56537", | |
| 281 | + "name": "Jordan 6 Retro Awake NY Midnight Navy", | |
| 282 | + "brand": "Air Jordan", | |
| 283 | + "category": "Jordan 6", | |
| 284 | + "url": "https://hypeboost.com/en/product/air-jordan-6-retro-awake-ny-midnight-navy", | |
| 285 | + "image": "https://img.hypeboost.com/products/air-jordan-6-retro-awake-ny-midnight-navy/w300/img01.jpg", | |
| 286 | + "price": 283, | |
| 287 | + "currency": "EUR", | |
| 288 | + "sku": null | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "id": "56510", | |
| 292 | + "name": "Jordan 17 Low Black Patent", | |
| 293 | + "brand": "Air Jordan", | |
| 294 | + "category": "Jordan 17", | |
| 295 | + "url": "https://hypeboost.com/en/product/air-jordan-17-low-black-patent", | |
| 296 | + "image": "https://img.hypeboost.com/products/air-jordan-17-low-black-patent/w300/img01.jpg", | |
| 297 | + "price": 319, | |
| 298 | + "currency": "EUR", | |
| 299 | + "sku": null | |
| 300 | + }, | |
| 301 | + { | |
| 302 | + "id": "56418", | |
| 303 | + "name": "Jordan 1 Low Vintage Lichen", | |
| 304 | + "brand": "Air Jordan", | |
| 305 | + "category": "Jordan 1", | |
| 306 | + "url": "https://hypeboost.com/en/product/air-jordan-1-low-vintage-lichen", | |
| 307 | + "image": "https://img.hypeboost.com/products/air-jordan-1-low-vintage-lichen/w300/img01.jpg", | |
| 308 | + "price": 110, | |
| 309 | + "currency": "EUR", | |
| 310 | + "sku": null | |
| 311 | + }, | |
| 312 | + { | |
| 313 | + "id": "56328", | |
| 314 | + "name": "Jordan 1 Retro Low OG SP Travis Scott Shy Pink (PS)", | |
| 315 | + "brand": "Air Jordan", | |
| 316 | + "category": "Jordan 1", | |
| 317 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-low-og-sp-travis-scott-shy-pink-ps", | |
| 318 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-low-og-sp-travis-scott-shy-pink-ps/w300/img01.jpg", | |
| 319 | + "price": 149, | |
| 320 | + "currency": "EUR", | |
| 321 | + "sku": null | |
| 322 | + }, | |
| 323 | + { | |
| 324 | + "id": "56065", | |
| 325 | + "name": "Jordan 1 Retro Low OG SP Travis Scott Sail Tropical Pink", | |
| 326 | + "brand": "Air Jordan", | |
| 327 | + "category": "Jordan 1", | |
| 328 | + "url": "https://hypeboost.com/en/product/air-jordan-1-retro-low-og-sp-travis-scott-sail-tropical-pink", | |
| 329 | + "image": "https://img.hypeboost.com/products/air-jordan-1-retro-low-og-sp-travis-scott-sail-tropical-pink/w300/img01.jpg", | |
| 330 | + "price": 418, | |
| 331 | + "currency": "EUR", | |
| 332 | + "sku": null | |
| 333 | + }, | |
| 334 | + { | |
| 335 | + "id": "56022", | |
| 336 | + "name": "Jordan 4 Retro OG Nigel Sylvester Brick After Brick", | |
| 337 | + "brand": "Air Jordan", | |
| 338 | + "category": "Jordan 4", | |
| 339 | + "url": "https://hypeboost.com/en/product/air-jordan-4-retro-og-nigel-sylvester-brick-after-brick", | |
| 340 | + "image": "https://img.hypeboost.com/products/air-jordan-4-retro-og-nigel-sylvester-brick-after-brick/w300/img01.jpg", | |
| 341 | + "price": 201, | |
| 342 | + "currency": "EUR", | |
| 343 | + "sku": null | |
| 344 | + }, | |
| 345 | + { | |
| 346 | + "id": "55897", | |
| 347 | + "name": "Jordan 1 Mid SE White Cave Stone", | |
| 348 | + "brand": "Air Jordan", | |
| 349 | + "category": "Jordan 1", | |
| 350 | + "url": "https://hypeboost.com/en/product/air-jordan-1-mid-se-white-cave-stone", | |
| 351 | + "image": "https://img.hypeboost.com/products/air-jordan-1-mid-se-white-cave-stone/w300/img01.jpg", | |
| 352 | + "price": 107, | |
| 353 | + "currency": "EUR", | |
| 354 | + "sku": null | |
| 355 | + }, | |
| 356 | + { | |
| 357 | + "id": "55801", | |
| 358 | + "name": "Jordan 8 Retro White True Red (2025)", | |
| 359 | + "brand": "Air Jordan", | |
| 360 | + "category": "Jordan 8", | |
| 361 | + "url": "https://hypeboost.com/en/product/air-jordan-8-retro-white-true-red-2025", | |
| 362 | + "image": "https://img.hypeboost.com/products/air-jordan-8-retro-white-true-red-2025/w300/img01.jpg", | |
| 363 | + "price": 150, | |
| 364 | + "currency": "EUR", | |
| 365 | + "sku": null | |
| 366 | + }, | |
| 367 | + { | |
| 368 | + "id": "55491", | |
| 369 | + "name": "Jordan 1 Low Medium Soft Pink White (GS)", | |
| 370 | + "brand": "Air Jordan", | |
| 371 | + "category": "Jordan 1", | |
| 372 | + "url": "https://hypeboost.com/en/product/air-jordan-1-low-medium-soft-pink-white-gs", | |
| 373 | + "image": "https://img.hypeboost.com/products/air-jordan-1-low-medium-soft-pink-white-gs/w300/img01.jpg", | |
| 374 | + "price": 109, | |
| 375 | + "currency": "EUR", | |
| 376 | + "sku": null | |
| 377 | + }, | |
| 378 | + { | |
| 379 | + "id": "55488", | |
| 380 | + "name": "Jordan 5 Retro Wolf Grey (2026)", | |
| 381 | + "brand": "Air Jordan", | |
| 382 | + "category": "", | |
| 383 | + "url": "https://hypeboost.com/en/product/air-jordan-5-retro-wolf-grey-2026", | |
| 384 | + "image": "https://img.hypeboost.com/products/air-jordan-5-retro-wolf-grey-2026/w300/img01.jpg", | |
| 385 | + "price": 220, | |
| 386 | + "currency": "EUR", | |
| 387 | + "sku": null | |
| 388 | + }, | |
| 389 | + { | |
| 390 | + "id": "55463", | |
| 391 | + "name": "Jordan 1 Mid Summit White Off Noir Rattan", | |
| 392 | + "brand": "Air Jordan", | |
| 393 | + "category": "Jordan 1", | |
| 394 | + "url": "https://hypeboost.com/en/product/air-jordan-1-mid-summit-white-off-noir-rattan", | |
| 395 | + "image": "https://img.hypeboost.com/products/air-jordan-1-mid-summit-white-off-noir-rattan/w300/img01.jpg", | |
| 396 | + "price": 120, | |
| 397 | + "currency": "EUR", | |
| 398 | + "sku": null | |
| 399 | + }, | |
| 400 | + { | |
| 401 | + "id": "55308", | |
| 402 | + "name": "Jordan 1 Mule Golf University Blue", | |
| 403 | + "brand": "Air Jordan", | |
| 404 | + "category": "Jordan 1", | |
| 405 | + "url": "https://hypeboost.com/en/product/air-jordan-1-mule-golf-university-blue", | |
| 406 | + "image": "https://img.hypeboost.com/products/air-jordan-1-mule-golf-university-blue/w300/img01.jpg", | |
| 407 | + "price": 137, | |
| 408 | + "currency": "EUR", | |
| 409 | + "sku": null | |
| 410 | + } | |
| 411 | + ] | |
| 412 | + } | |
| 413 | + }, | |
| 414 | + "expect": { | |
| 415 | + "minCount": 1, | |
| 416 | + "kinds": [ | |
| 417 | + "catalog_item", | |
| 418 | + "listing" | |
| 419 | + ] | |
| 420 | + }, | |
| 421 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 422 | + "capturedAt": "2026-09-07T06:29:15.465Z" | |
| 423 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/hypeboost/grid-nike-1.json
+423 −0
@@ -0,0 +1,423 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://hypeboost.com/en/category/sneakers/nike", | |
| 4 | + "externalId": "grid:nike:1", | |
| 5 | + "kind": "catalog_item", | |
| 6 | + "engine": "firecrawl", | |
| 7 | + "fetchedAt": "2026-09-07T06:29:21.912Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "grid_page", | |
| 10 | + "url": "https://hypeboost.com/en/category/sneakers/nike", | |
| 11 | + "seed": "nike", | |
| 12 | + "page": 1, | |
| 13 | + "total": 4750, | |
| 14 | + "items": [ | |
| 15 | + { | |
| 16 | + "id": "56471", | |
| 17 | + "name": "Nike Mind 001 Slide Triple Black", | |
| 18 | + "brand": "Nike", | |
| 19 | + "category": "Mind", | |
| 20 | + "url": "https://hypeboost.com/en/product/nike-mind-001-slide-triple-black", | |
| 21 | + "image": "https://img.hypeboost.com/products/nike-mind-001-slide-triple-black/w300/img01.jpg", | |
| 22 | + "price": 120, | |
| 23 | + "currency": "EUR", | |
| 24 | + "sku": null | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "id": "56524", | |
| 28 | + "name": "Nike Kobe 5 Protro Dodgers", | |
| 29 | + "brand": "Nike", | |
| 30 | + "category": "Kobe", | |
| 31 | + "url": "https://hypeboost.com/en/product/nike-kobe-5-protro-dodgers", | |
| 32 | + "image": "https://img.hypeboost.com/products/nike-kobe-5-protro-dodgers/w300/img01.jpg", | |
| 33 | + "price": 235, | |
| 34 | + "currency": "EUR", | |
| 35 | + "sku": null | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "id": "56539", | |
| 39 | + "name": "Nike Mind 001 Slide Cucumber Calm", | |
| 40 | + "brand": "Nike", | |
| 41 | + "category": "Mind", | |
| 42 | + "url": "https://hypeboost.com/en/product/nike-mind-001-slide-cucumber-calm", | |
| 43 | + "image": "https://img.hypeboost.com/products/nike-mind-001-slide-cucumber-calm/w300/img01.jpg", | |
| 44 | + "price": 121, | |
| 45 | + "currency": "EUR", | |
| 46 | + "sku": null | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + "id": "55110", | |
| 50 | + "name": "Nike Mind 001 Slide Black Chrome (Women's)", | |
| 51 | + "brand": "Nike", | |
| 52 | + "category": "Mind", | |
| 53 | + "url": "https://hypeboost.com/en/product/nike-mind-001-slide-black-chrome-womens", | |
| 54 | + "image": "https://img.hypeboost.com/products/nike-mind-001-slide-black-chrome-womens/w300/img01.jpg", | |
| 55 | + "price": 117, | |
| 56 | + "currency": "EUR", | |
| 57 | + "sku": null | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + "id": "56562", | |
| 61 | + "name": "Nike Ja 3 White Label", | |
| 62 | + "brand": "Nike", | |
| 63 | + "category": "Ja", | |
| 64 | + "url": "https://hypeboost.com/en/product/nike-ja-3-white-label", | |
| 65 | + "image": "https://img.hypeboost.com/products/nike-ja-3-white-label/w300/img01.jpg", | |
| 66 | + "price": 187, | |
| 67 | + "currency": "EUR", | |
| 68 | + "sku": null | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "id": "56136", | |
| 72 | + "name": "Nike Mind 001 Flyknit Slide Bronze Eclipse Total Orange", | |
| 73 | + "brand": "Nike", | |
| 74 | + "category": "Mind", | |
| 75 | + "url": "https://hypeboost.com/en/product/nike-mind-001-flyknit-slide-bronze-eclipse-total-orange", | |
| 76 | + "image": "https://img.hypeboost.com/products/nike-mind-001-flyknit-slide-bronze-eclipse-total-orange/w300/img01.jpg", | |
| 77 | + "price": 124, | |
| 78 | + "currency": "EUR", | |
| 79 | + "sku": null | |
| 80 | + }, | |
| 81 | + { | |
| 82 | + "id": "56540", | |
| 83 | + "name": "Nike Mind 001 Slide Summit White (Women's)", | |
| 84 | + "brand": "Nike", | |
| 85 | + "category": "Mind", | |
| 86 | + "url": "https://hypeboost.com/en/product/nike-mind-001-slide-summit-white-womens", | |
| 87 | + "image": "https://img.hypeboost.com/products/nike-mind-001-slide-summit-white-womens/w300/img01.jpg", | |
| 88 | + "price": 114, | |
| 89 | + "currency": "EUR", | |
| 90 | + "sku": null | |
| 91 | + }, | |
| 92 | + { | |
| 93 | + "id": "55078", | |
| 94 | + "name": "Nike Mind 001 Slide Black Chrome", | |
| 95 | + "brand": "Nike", | |
| 96 | + "category": "Mind", | |
| 97 | + "url": "https://hypeboost.com/en/product/nike-mind-001-slide-black-chrome", | |
| 98 | + "image": "https://img.hypeboost.com/products/nike-mind-001-slide-black-chrome/w300/img01.jpg", | |
| 99 | + "price": 159, | |
| 100 | + "currency": "EUR", | |
| 101 | + "sku": null | |
| 102 | + }, | |
| 103 | + { | |
| 104 | + "id": "48596", | |
| 105 | + "name": "Nike P-6000 White Gold Red (W)", | |
| 106 | + "brand": "Nike", | |
| 107 | + "category": "P-6000", | |
| 108 | + "url": "https://hypeboost.com/en/product/nike-p-6000-white-gold-red-w", | |
| 109 | + "image": "https://img.hypeboost.com/products/nike-p-6000-white-gold-red-w/w300/img01.jpg", | |
| 110 | + "price": 99, | |
| 111 | + "currency": "EUR", | |
| 112 | + "sku": null | |
| 113 | + }, | |
| 114 | + { | |
| 115 | + "id": "55186", | |
| 116 | + "name": "Nike Air Max 90 Premium Light British Tan Velvet Brown", | |
| 117 | + "brand": "Nike", | |
| 118 | + "category": "Air Max", | |
| 119 | + "url": "https://hypeboost.com/en/product/nike-air-max-90-premium-light-british-tan-velvet-brown", | |
| 120 | + "image": "https://img.hypeboost.com/products/nike-air-max-90-premium-light-british-tan-velvet-brown/w300/img01.jpg", | |
| 121 | + "price": 149, | |
| 122 | + "currency": "EUR", | |
| 123 | + "sku": null | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "id": "52807", | |
| 127 | + "name": "Nike Air Max 95 OG Big Bubble Neon (2025)", | |
| 128 | + "brand": "Nike", | |
| 129 | + "category": "Air Max", | |
| 130 | + "url": "https://hypeboost.com/en/product/nike-air-max-95-og-big-bubble-neon-2025", | |
| 131 | + "image": "https://img.hypeboost.com/products/nike-air-max-95-og-big-bubble-neon-2025/w300/img01.jpg", | |
| 132 | + "price": 113, | |
| 133 | + "currency": "EUR", | |
| 134 | + "sku": null | |
| 135 | + }, | |
| 136 | + { | |
| 137 | + "id": "47688", | |
| 138 | + "name": "Nike Air Force 1 Mid Off-White Black", | |
| 139 | + "brand": "Nike", | |
| 140 | + "category": "Air Force 1", | |
| 141 | + "url": "https://hypeboost.com/en/product/nike-air-force-1-mid-off-white-black", | |
| 142 | + "image": "https://img.hypeboost.com/products/nike-air-force-1-mid-off-white-black/w300/img01.jpg", | |
| 143 | + "price": 115, | |
| 144 | + "currency": "EUR", | |
| 145 | + "sku": null | |
| 146 | + }, | |
| 147 | + { | |
| 148 | + "id": "56567", | |
| 149 | + "name": "Nike Air Max 95 Big Bubble Black Game Royal", | |
| 150 | + "brand": "Nike", | |
| 151 | + "category": "Air Max", | |
| 152 | + "url": "https://hypeboost.com/en/product/nike-air-max-95-big-bubble-black-game-royal", | |
| 153 | + "image": "https://img.hypeboost.com/products/nike-air-max-95-big-bubble-black-game-royal/w300/img01.jpg", | |
| 154 | + "price": 140, | |
| 155 | + "currency": "EUR", | |
| 156 | + "sku": null | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "id": "56532", | |
| 160 | + "name": "Nike Air Tech Challenge II Back to the US Open", | |
| 161 | + "brand": "Nike", | |
| 162 | + "category": "", | |
| 163 | + "url": "https://hypeboost.com/en/product/nike-air-tech-challenge-ii-back-to-the-us-open", | |
| 164 | + "image": "https://img.hypeboost.com/products/nike-air-tech-challenge-ii-back-to-the-us-open/w300/img01.jpg", | |
| 165 | + "price": 321, | |
| 166 | + "currency": "EUR", | |
| 167 | + "sku": null | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "id": "56419", | |
| 171 | + "name": "Nike Air Max Plus Black Pencil Point Team Crimson", | |
| 172 | + "brand": "Nike", | |
| 173 | + "category": "Air Max", | |
| 174 | + "url": "https://hypeboost.com/en/product/nike-air-max-plus-black-pencil-point-team-crimson", | |
| 175 | + "image": "https://img.hypeboost.com/products/nike-air-max-plus-black-pencil-point-team-crimson/w300/img01.jpg", | |
| 176 | + "price": 160, | |
| 177 | + "currency": "EUR", | |
| 178 | + "sku": null | |
| 179 | + }, | |
| 180 | + { | |
| 181 | + "id": "56312", | |
| 182 | + "name": "Nike Mercurial Superfly 1 RGN FG SE CR7 Metallic Gold Star", | |
| 183 | + "brand": "Nike", | |
| 184 | + "category": "", | |
| 185 | + "url": "https://hypeboost.com/en/product/nike-mercurial-superfly-1-rgn-fg-se-cr7-metallic-gold-star", | |
| 186 | + "image": "https://img.hypeboost.com/products/nike-mercurial-superfly-1-rgn-fg-se-cr7-metallic-gold-star/w300/img01.jpg", | |
| 187 | + "price": 286, | |
| 188 | + "currency": "EUR", | |
| 189 | + "sku": null | |
| 190 | + }, | |
| 191 | + { | |
| 192 | + "id": "56033", | |
| 193 | + "name": "Nike Zoom Mercurial Vapor 16 Elite FG Patta Waves White Black", | |
| 194 | + "brand": "Nike", | |
| 195 | + "category": "", | |
| 196 | + "url": "https://hypeboost.com/en/product/nike-zoom-mercurial-vapor-16-elite-fg-patta-waves-black", | |
| 197 | + "image": "https://img.hypeboost.com/products/nike-zoom-mercurial-vapor-16-elite-fg-patta-waves-black/w300/img01.jpg", | |
| 198 | + "price": 296, | |
| 199 | + "currency": "EUR", | |
| 200 | + "sku": null | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "id": "55535", | |
| 204 | + "name": "Nike Kobe 6 ASG Hollywood 3D (2026) (GS)", | |
| 205 | + "brand": "Nike", | |
| 206 | + "category": "Kobe", | |
| 207 | + "url": "https://hypeboost.com/en/product/nike-kobe-6-asg-hollywood-3d-2026-gs", | |
| 208 | + "image": "https://img.hypeboost.com/products/nike-kobe-6-asg-hollywood-3d-2026-gs/w300/img01.jpg", | |
| 209 | + "price": 149, | |
| 210 | + "currency": "EUR", | |
| 211 | + "sku": null | |
| 212 | + }, | |
| 213 | + { | |
| 214 | + "id": "55466", | |
| 215 | + "name": "Nike Moon Shoe SP Jacquemus Fauna Brown (Women's)", | |
| 216 | + "brand": "Nike", | |
| 217 | + "category": "", | |
| 218 | + "url": "https://hypeboost.com/en/product/nike-moon-shoe-sp-jacquemus-fauna-brown-womens", | |
| 219 | + "image": "https://img.hypeboost.com/products/nike-moon-shoe-sp-jacquemus-fauna-brown-womens/w300/img01.jpg", | |
| 220 | + "price": 256, | |
| 221 | + "currency": "EUR", | |
| 222 | + "sku": null | |
| 223 | + }, | |
| 224 | + { | |
| 225 | + "id": "55376", | |
| 226 | + "name": "Nike Cortez Triple White (Women's)", | |
| 227 | + "brand": "Nike", | |
| 228 | + "category": "", | |
| 229 | + "url": "https://hypeboost.com/en/product/nike-cortez-triple-white-womens", | |
| 230 | + "image": "https://img.hypeboost.com/products/nike-cortez-triple-white-womens/w300/img01.jpg", | |
| 231 | + "price": 130, | |
| 232 | + "currency": "EUR", | |
| 233 | + "sku": null | |
| 234 | + }, | |
| 235 | + { | |
| 236 | + "id": "55181", | |
| 237 | + "name": "Nike Tiempo Ligera Pro FG LE Metallic Red Bronze Rose Gold", | |
| 238 | + "brand": "Nike", | |
| 239 | + "category": "", | |
| 240 | + "url": "https://hypeboost.com/en/product/nike-tiempo-ligera-pro-fg-le-metallic-red-bronze-rose-gold", | |
| 241 | + "image": "https://img.hypeboost.com/products/nike-tiempo-ligera-pro-fg-le-metallic-red-bronze-rose-gold/w300/img01.jpg", | |
| 242 | + "price": 155, | |
| 243 | + "currency": "EUR", | |
| 244 | + "sku": null | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "id": "55030", | |
| 248 | + "name": "Nike Shox TL Metallic Silver", | |
| 249 | + "brand": "Nike", | |
| 250 | + "category": "Shox", | |
| 251 | + "url": "https://hypeboost.com/en/product/nike-shox-tl-metallic-silver", | |
| 252 | + "image": "https://img.hypeboost.com/products/nike-shox-tl-metallic-silver/w300/img01.jpg", | |
| 253 | + "price": 128, | |
| 254 | + "currency": "EUR", | |
| 255 | + "sku": null | |
| 256 | + }, | |
| 257 | + { | |
| 258 | + "id": "54857", | |
| 259 | + "name": "Nike Air Max 90 Premium Skunk", | |
| 260 | + "brand": "Nike", | |
| 261 | + "category": "Air Max", | |
| 262 | + "url": "https://hypeboost.com/en/product/nike-air-max-90-premium-skunk", | |
| 263 | + "image": "https://img.hypeboost.com/products/nike-air-max-90-premium-skunk/w300/img01.jpg", | |
| 264 | + "price": 121, | |
| 265 | + "currency": "EUR", | |
| 266 | + "sku": null | |
| 267 | + }, | |
| 268 | + { | |
| 269 | + "id": "54837", | |
| 270 | + "name": "Nike SB Dunk Low Nardwuar", | |
| 271 | + "brand": "Nike", | |
| 272 | + "category": "SB", | |
| 273 | + "url": "https://hypeboost.com/en/product/nike-sb-dunk-low-nardwuar", | |
| 274 | + "image": "https://img.hypeboost.com/products/nike-sb-dunk-low-nardwuar/w300/img01.jpg", | |
| 275 | + "price": 164, | |
| 276 | + "currency": "EUR", | |
| 277 | + "sku": null | |
| 278 | + }, | |
| 279 | + { | |
| 280 | + "id": "54675", | |
| 281 | + "name": "Nike Ja 3 Zombie", | |
| 282 | + "brand": "Nike", | |
| 283 | + "category": "Ja", | |
| 284 | + "url": "https://hypeboost.com/en/product/nike-ja-3-zombie", | |
| 285 | + "image": "https://img.hypeboost.com/products/nike-ja-3-zombie/w300/img01.jpg", | |
| 286 | + "price": 120, | |
| 287 | + "currency": "EUR", | |
| 288 | + "sku": null | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "id": "54611", | |
| 292 | + "name": "Nike Dunk Low Retro Khaki Team Red Pearl White (W)", | |
| 293 | + "brand": "Nike", | |
| 294 | + "category": "Dunk Low", | |
| 295 | + "url": "https://hypeboost.com/en/product/nike-dunk-low-retro-khaki-team-red-pearl-white-womens", | |
| 296 | + "image": "https://img.hypeboost.com/products/nike-dunk-low-retro-khaki-team-red-pearl-white-womens/w300/img01.jpg", | |
| 297 | + "price": 200, | |
| 298 | + "currency": "EUR", | |
| 299 | + "sku": null | |
| 300 | + }, | |
| 301 | + { | |
| 302 | + "id": "54136", | |
| 303 | + "name": "Nike Air Max 1 Aster Pink (W)", | |
| 304 | + "brand": "Nike", | |
| 305 | + "category": "Air Max", | |
| 306 | + "url": "https://hypeboost.com/en/product/nike-air-max-1-aster-pink-womens", | |
| 307 | + "image": "https://img.hypeboost.com/products/nike-air-max-1-aster-pink-womens/w300/img01.jpg", | |
| 308 | + "price": 107, | |
| 309 | + "currency": "EUR", | |
| 310 | + "sku": null | |
| 311 | + }, | |
| 312 | + { | |
| 313 | + "id": "53462", | |
| 314 | + "name": "Nike Air Max 95 OG Big Bubble Sanded Purple", | |
| 315 | + "brand": "Nike", | |
| 316 | + "category": "Air Max", | |
| 317 | + "url": "https://hypeboost.com/en/product/nike-air-max-95-og-big-bubble-sanded-purple", | |
| 318 | + "image": "https://img.hypeboost.com/products/nike-air-max-95-og-big-bubble-sanded-purple/w300/img01.jpg", | |
| 319 | + "price": 149, | |
| 320 | + "currency": "EUR", | |
| 321 | + "sku": null | |
| 322 | + }, | |
| 323 | + { | |
| 324 | + "id": "52924", | |
| 325 | + "name": "Nike Air Max 95 Corteiz Honey Black", | |
| 326 | + "brand": "Nike", | |
| 327 | + "category": "Air Max", | |
| 328 | + "url": "https://hypeboost.com/en/product/nike-air-max-95-corteiz-honey-black", | |
| 329 | + "image": "https://img.hypeboost.com/products/nike-air-max-95-corteiz-honey-black/w300/img01.jpg", | |
| 330 | + "price": 279, | |
| 331 | + "currency": "EUR", | |
| 332 | + "sku": null | |
| 333 | + }, | |
| 334 | + { | |
| 335 | + "id": "52647", | |
| 336 | + "name": "Nike Killshot 2 LTR PRM Tiempo Pack Black", | |
| 337 | + "brand": "Nike", | |
| 338 | + "category": "", | |
| 339 | + "url": "https://hypeboost.com/en/product/nike-killshot-2-ltr-prm-tiempo-pack-black", | |
| 340 | + "image": "https://img.hypeboost.com/products/nike-killshot-2-ltr-prm-tiempo-pack-black/w300/img01.jpg", | |
| 341 | + "price": 164, | |
| 342 | + "currency": "EUR", | |
| 343 | + "sku": null | |
| 344 | + }, | |
| 345 | + { | |
| 346 | + "id": "52353", | |
| 347 | + "name": "Nike Air Max 180 'Hyper Crimson'", | |
| 348 | + "brand": "Nike", | |
| 349 | + "category": "Air Max", | |
| 350 | + "url": "https://hypeboost.com/en/product/nike-air-max-180-hyper-crimson", | |
| 351 | + "image": "https://img.hypeboost.com/products/nike-air-max-180-hyper-crimson/w300/img01.jpg", | |
| 352 | + "price": 124, | |
| 353 | + "currency": "EUR", | |
| 354 | + "sku": null | |
| 355 | + }, | |
| 356 | + { | |
| 357 | + "id": "52284", | |
| 358 | + "name": "Nike Air Max 90 'Multi-Color Pastel' (W)", | |
| 359 | + "brand": "Nike", | |
| 360 | + "category": "Air Max", | |
| 361 | + "url": "https://hypeboost.com/en/product/nike-air-max-90-multi-color-pastel-womens", | |
| 362 | + "image": "https://img.hypeboost.com/products/nike-air-max-90-multi-color-pastel-womens/w300/img01.jpg", | |
| 363 | + "price": 120, | |
| 364 | + "currency": "EUR", | |
| 365 | + "sku": null | |
| 366 | + }, | |
| 367 | + { | |
| 368 | + "id": "51985", | |
| 369 | + "name": "Nike Air Max 90 Futura 'Summit White Barely Rose' (W)", | |
| 370 | + "brand": "Nike", | |
| 371 | + "category": "Air Max", | |
| 372 | + "url": "https://hypeboost.com/en/product/nike-air-max-90-futura-summit-white-barely-rose-w", | |
| 373 | + "image": "https://img.hypeboost.com/products/nike-air-max-90-futura-summit-white-barely-rose-w/w300/img01.jpg", | |
| 374 | + "price": 129, | |
| 375 | + "currency": "EUR", | |
| 376 | + "sku": null | |
| 377 | + }, | |
| 378 | + { | |
| 379 | + "id": "51948", | |
| 380 | + "name": "Nike Shox TL 'Black University Red'", | |
| 381 | + "brand": "Nike", | |
| 382 | + "category": "Shox", | |
| 383 | + "url": "https://hypeboost.com/en/product/nike-shox-tl-black-university-red", | |
| 384 | + "image": "https://img.hypeboost.com/products/nike-shox-tl-black-university-red/w300/img01.jpg", | |
| 385 | + "price": 135, | |
| 386 | + "currency": "EUR", | |
| 387 | + "sku": null | |
| 388 | + }, | |
| 389 | + { | |
| 390 | + "id": "51691", | |
| 391 | + "name": "Nike P-6000 'Obsidian Summit White'", | |
| 392 | + "brand": "Nike", | |
| 393 | + "category": "P-6000", | |
| 394 | + "url": "https://hypeboost.com/en/product/nike-p-6000-obsidian-summit-white", | |
| 395 | + "image": "https://img.hypeboost.com/products/nike-p-6000-obsidian-summit-white/w300/img01.jpg", | |
| 396 | + "price": 101, | |
| 397 | + "currency": "EUR", | |
| 398 | + "sku": null | |
| 399 | + }, | |
| 400 | + { | |
| 401 | + "id": "51356", | |
| 402 | + "name": "Nike Air Max 90 Premium 'Iron Grey'", | |
| 403 | + "brand": "Nike", | |
| 404 | + "category": "Air Max", | |
| 405 | + "url": "https://hypeboost.com/en/product/nike-air-max-premium-iron-grey", | |
| 406 | + "image": "https://img.hypeboost.com/products/nike-air-max-premium-iron-grey/w300/img01.jpg", | |
| 407 | + "price": 145, | |
| 408 | + "currency": "EUR", | |
| 409 | + "sku": null | |
| 410 | + } | |
| 411 | + ] | |
| 412 | + } | |
| 413 | + }, | |
| 414 | + "expect": { | |
| 415 | + "minCount": 1, | |
| 416 | + "kinds": [ | |
| 417 | + "catalog_item", | |
| 418 | + "listing" | |
| 419 | + ] | |
| 420 | + }, | |
| 421 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 422 | + "capturedAt": "2026-09-07T06:29:21.936Z" | |
| 423 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/laced/air-jordan-1-retro-low-og-sp-travis-scott-velvet-brown.json
+164 −0
@@ -0,0 +1,164 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.laced.com/products/air-jordan-1-retro-low-og-sp-travis-scott-velvet-brown", | |
| 4 | + "externalId": "air-jordan-1-retro-low-og-sp-travis-scott-velvet-brown", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:58.683Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "product_page", | |
| 10 | + "url": "https://www.laced.com/products/air-jordan-1-retro-low-og-sp-travis-scott-velvet-brown", | |
| 11 | + "slug": "air-jordan-1-retro-low-og-sp-travis-scott-velvet-brown", | |
| 12 | + "name": "Air Jordan 1 Retro Low OG SP Travis Scott Velvet Brown", | |
| 13 | + "sku": "DM7866-202", | |
| 14 | + "brand": "Air Jordan", | |
| 15 | + "images": [ | |
| 16 | + "https://images-beta.laced.com/products/81dacabc-5d89-4370-bf29-b1c3613acd04.jpg?width=1200", | |
| 17 | + "https://images-beta.laced.com/products/821b1542-a73c-4e7e-8754-768e6987a122.jpg?width=1200", | |
| 18 | + "https://images-beta.laced.com/products/bd641666-500e-4a1c-b645-1f90bd8e06ba.jpg?width=1200" | |
| 19 | + ], | |
| 20 | + "description": "<p>The Travis Scott x Jordan 1 Low OG \"Velvet Brown\" offers another refined chapter in one of modern sneaker culture’s most consistent collaborations. Launching on 21st December 2024 through Nike SNKRS and select global stockists, this <a href=\"/air-jordan/air-jordan-1\" title=\"Air Jordan 1\">Air Jordan 1</a> release leans into the earthy, dark-toned palette that has become a hallmark of Scott’s Jordan output. With subtle premium cues and that instantly recognisable reverse Swoosh, this pair of Tr", | |
| 21 | + "lowPrice": 404, | |
| 22 | + "highPrice": 689, | |
| 23 | + "currency": "GBP", | |
| 24 | + "sizes": [ | |
| 25 | + { | |
| 26 | + "size": "UK 3 | EU 35.5 | US 3.5", | |
| 27 | + "price": 404, | |
| 28 | + "currency": "GBP", | |
| 29 | + "available": true | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "size": "UK 3.5 | EU 36 | US 4", | |
| 33 | + "price": 563, | |
| 34 | + "currency": "GBP", | |
| 35 | + "available": true | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "size": "UK 4 | EU 36.5 | US 4.5", | |
| 39 | + "price": 511, | |
| 40 | + "currency": "GBP", | |
| 41 | + "available": true | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "size": "UK 4.5 | EU 37.5 | US 5", | |
| 45 | + "price": 568, | |
| 46 | + "currency": "GBP", | |
| 47 | + "available": true | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "size": "UK 5 | EU 38 | US 5.5", | |
| 51 | + "price": 637, | |
| 52 | + "currency": "GBP", | |
| 53 | + "available": true | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + "size": "UK 5.5 | EU 38.5 | US 6", | |
| 57 | + "price": 689, | |
| 58 | + "currency": "GBP", | |
| 59 | + "available": true | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + "size": "UK 6 | EU 39 | US 6.5", | |
| 63 | + "price": 613, | |
| 64 | + "currency": "GBP", | |
| 65 | + "available": true | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + "size": "UK 6 | EU 40 | US 7", | |
| 69 | + "price": 641, | |
| 70 | + "currency": "GBP", | |
| 71 | + "available": true | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + "size": "UK 6.5 | EU 40.5 | US 7.5", | |
| 75 | + "price": 561, | |
| 76 | + "currency": "GBP", | |
| 77 | + "available": true | |
| 78 | + }, | |
| 79 | + { | |
| 80 | + "size": "UK 7 | EU 41 | US 8", | |
| 81 | + "price": 471, | |
| 82 | + "currency": "GBP", | |
| 83 | + "available": true | |
| 84 | + }, | |
| 85 | + { | |
| 86 | + "size": "UK 7.5 | EU 42 | US 8.5", | |
| 87 | + "price": 412, | |
| 88 | + "currency": "GBP", | |
| 89 | + "available": true | |
| 90 | + }, | |
| 91 | + { | |
| 92 | + "size": "UK 8 | EU 42.5 | US 9", | |
| 93 | + "price": 513, | |
| 94 | + "currency": "GBP", | |
| 95 | + "available": true | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "size": "UK 8.5 | EU 43 | US 9.5", | |
| 99 | + "price": 481, | |
| 100 | + "currency": "GBP", | |
| 101 | + "available": true | |
| 102 | + }, | |
| 103 | + { | |
| 104 | + "size": "UK 9 | EU 44 | US 10", | |
| 105 | + "price": 522, | |
| 106 | + "currency": "GBP", | |
| 107 | + "available": true | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + "size": "UK 9.5 | EU 44.5 | US 10.5", | |
| 111 | + "price": 592, | |
| 112 | + "currency": "GBP", | |
| 113 | + "available": true | |
| 114 | + }, | |
| 115 | + { | |
| 116 | + "size": "UK 10 | EU 45 | US 11", | |
| 117 | + "price": 632, | |
| 118 | + "currency": "GBP", | |
| 119 | + "available": true | |
| 120 | + }, | |
| 121 | + { | |
| 122 | + "size": "UK 10.5 | EU 45.5 | US 11.5", | |
| 123 | + "price": 614, | |
| 124 | + "currency": "GBP", | |
| 125 | + "available": true | |
| 126 | + }, | |
| 127 | + { | |
| 128 | + "size": "UK 11 | EU 46 | US 12", | |
| 129 | + "price": 593, | |
| 130 | + "currency": "GBP", | |
| 131 | + "available": true | |
| 132 | + }, | |
| 133 | + { | |
| 134 | + "size": "UK 11.5 | EU 47 | US 12.5", | |
| 135 | + "price": 603, | |
| 136 | + "currency": "GBP", | |
| 137 | + "available": true | |
| 138 | + }, | |
| 139 | + { | |
| 140 | + "size": "UK 12 | EU 47.5 | US 13", | |
| 141 | + "price": 489, | |
| 142 | + "currency": "GBP", | |
| 143 | + "available": true | |
| 144 | + }, | |
| 145 | + { | |
| 146 | + "size": "UK 13 | EU 48.5 | US 14", | |
| 147 | + "price": 550, | |
| 148 | + "currency": "GBP", | |
| 149 | + "available": true | |
| 150 | + } | |
| 151 | + ] | |
| 152 | + } | |
| 153 | + }, | |
| 154 | + "expect": { | |
| 155 | + "minCount": 1, | |
| 156 | + "kinds": [ | |
| 157 | + "catalog_item", | |
| 158 | + "price_observation", | |
| 159 | + "listing" | |
| 160 | + ] | |
| 161 | + }, | |
| 162 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 163 | + "capturedAt": "2026-09-07T06:28:58.705Z" | |
| 164 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/laced/air-jordan-4-retro-black-cat-2025-gs.json
+86 −0
@@ -0,0 +1,86 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.laced.com/products/air-jordan-4-retro-black-cat-2025-gs", | |
| 4 | + "externalId": "air-jordan-4-retro-black-cat-2025-gs", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:57.389Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "product_page", | |
| 10 | + "url": "https://www.laced.com/products/air-jordan-4-retro-black-cat-2025-gs", | |
| 11 | + "slug": "air-jordan-4-retro-black-cat-2025-gs", | |
| 12 | + "name": "Air Jordan 4 Retro Black Cat (2025) GS", | |
| 13 | + "sku": "IB4171-010", | |
| 14 | + "brand": "Air Jordan", | |
| 15 | + "images": [ | |
| 16 | + "https://images-beta.laced.com/products/f2551031-b127-4003-be13-0bfbbe5ea3ae.jpg?width=1200", | |
| 17 | + "https://images-beta.laced.com/products/0e3e0c3c-e8d9-45b9-82ab-9813ab1d62ad.jpg?width=1200", | |
| 18 | + "https://images-beta.laced.com/products/2ad8cd6d-a185-4577-a835-fb9b69b9e1a6.jpg?width=1200" | |
| 19 | + ], | |
| 20 | + "description": "<p>The Air Jordan 4 Retro Black Cat (2025) GS brings the same legendary blackout energy to grade school sizing. The Black Cat story began in 2006, returned in 2020, and officially re-emerged on 28th November 2025 in full family sizing, keeping the nickname-inspired heritage alive for a new generation. As a result, the Jordan 4 Black Cat GS 2025 has quickly become one of the most sought-after youth releases of the year.</p>\r\n<br/>\r\n<p>This GS pair stays true to the formula that made the colourway", | |
| 21 | + "lowPrice": 170, | |
| 22 | + "highPrice": 297, | |
| 23 | + "currency": "GBP", | |
| 24 | + "sizes": [ | |
| 25 | + { | |
| 26 | + "size": "UK 3 | EU 35.5 | US 3.5", | |
| 27 | + "price": 170, | |
| 28 | + "currency": "GBP", | |
| 29 | + "available": true | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "size": "UK 3.5 | EU 36 | US 4", | |
| 33 | + "price": 191, | |
| 34 | + "currency": "GBP", | |
| 35 | + "available": true | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "size": "UK 4 | EU 36.5 | US 4.5", | |
| 39 | + "price": 209, | |
| 40 | + "currency": "GBP", | |
| 41 | + "available": true | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "size": "UK 4.5 | EU 37.5 | US 5", | |
| 45 | + "price": 196, | |
| 46 | + "currency": "GBP", | |
| 47 | + "available": true | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "size": "UK 5 | EU 38 | US 5.5", | |
| 51 | + "price": 234, | |
| 52 | + "currency": "GBP", | |
| 53 | + "available": true | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + "size": "UK 5.5 | EU 38.5 | US 6", | |
| 57 | + "price": 233, | |
| 58 | + "currency": "GBP", | |
| 59 | + "available": true | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + "size": "UK 6 | EU 39 | US 6.5", | |
| 63 | + "price": 281, | |
| 64 | + "currency": "GBP", | |
| 65 | + "available": true | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + "size": "UK 6 | EU 40 | US 7", | |
| 69 | + "price": 297, | |
| 70 | + "currency": "GBP", | |
| 71 | + "available": true | |
| 72 | + } | |
| 73 | + ] | |
| 74 | + } | |
| 75 | + }, | |
| 76 | + "expect": { | |
| 77 | + "minCount": 1, | |
| 78 | + "kinds": [ | |
| 79 | + "catalog_item", | |
| 80 | + "price_observation", | |
| 81 | + "listing" | |
| 82 | + ] | |
| 83 | + }, | |
| 84 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 85 | + "capturedAt": "2026-09-07T06:28:57.408Z" | |
| 86 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/laced/air-jordan-4-retro-black-cat-2025.json
+134 −0
@@ -0,0 +1,134 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.laced.com/products/air-jordan-4-retro-black-cat-2025", | |
| 4 | + "externalId": "air-jordan-4-retro-black-cat-2025", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:55.895Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "product_page", | |
| 10 | + "url": "https://www.laced.com/products/air-jordan-4-retro-black-cat-2025", | |
| 11 | + "slug": "air-jordan-4-retro-black-cat-2025", | |
| 12 | + "name": "Air Jordan 4 Retro Black Cat (2025)", | |
| 13 | + "sku": "FV5029-010", | |
| 14 | + "brand": "Air Jordan", | |
| 15 | + "images": [ | |
| 16 | + "https://images-beta.laced.com/products/07dec8b2-bebf-4702-8471-886f855103a9.jpg?width=1200", | |
| 17 | + "https://images-beta.laced.com/products/3955cd94-5b37-454b-b6ee-6e40824588c8.jpg?width=1200", | |
| 18 | + "https://images-beta.laced.com/products/8748f5a4-82e5-4ba6-800c-460a00e25f6d.jpg?width=1200" | |
| 19 | + ], | |
| 20 | + "description": "<p>The Air Jordan 4 Retro Black Cat (2025) is back to reclaim its place as one of the cleanest stealth classics in the line. Inspired by Michael Jordan’s \"Black Cat\" nickname, the colourway first hit shelves in 2006 before returning in 2020 and again on 28th November 2025 for a major Black Friday restock. This latest release keeps the appeal simple: all-black, all attitude, and endlessly wearable. For collectors who missed earlier drops, the Jordan 4 Black Cat 2025 is a second chance at a modern", | |
| 21 | + "lowPrice": 262, | |
| 22 | + "highPrice": 1331, | |
| 23 | + "currency": "GBP", | |
| 24 | + "sizes": [ | |
| 25 | + { | |
| 26 | + "size": "UK 6 | EU 39 | US 6.5", | |
| 27 | + "price": 514, | |
| 28 | + "currency": "GBP", | |
| 29 | + "available": true | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "size": "UK 6 | EU 40 | US 7", | |
| 33 | + "price": 311, | |
| 34 | + "currency": "GBP", | |
| 35 | + "available": true | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "size": "UK 6.5 | EU 40.5 | US 7.5", | |
| 39 | + "price": 334, | |
| 40 | + "currency": "GBP", | |
| 41 | + "available": true | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "size": "UK 7 | EU 41 | US 8", | |
| 45 | + "price": 287, | |
| 46 | + "currency": "GBP", | |
| 47 | + "available": true | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "size": "UK 7.5 | EU 42 | US 8.5", | |
| 51 | + "price": 290, | |
| 52 | + "currency": "GBP", | |
| 53 | + "available": true | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + "size": "UK 8 | EU 42.5 | US 9", | |
| 57 | + "price": 287, | |
| 58 | + "currency": "GBP", | |
| 59 | + "available": true | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + "size": "UK 8.5 | EU 43 | US 9.5", | |
| 63 | + "price": 279, | |
| 64 | + "currency": "GBP", | |
| 65 | + "available": true | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + "size": "UK 9 | EU 44 | US 10", | |
| 69 | + "price": 297, | |
| 70 | + "currency": "GBP", | |
| 71 | + "available": true | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + "size": "UK 9.5 | EU 44.5 | US 10.5", | |
| 75 | + "price": 282, | |
| 76 | + "currency": "GBP", | |
| 77 | + "available": true | |
| 78 | + }, | |
| 79 | + { | |
| 80 | + "size": "UK 10 | EU 45 | US 11", | |
| 81 | + "price": 294, | |
| 82 | + "currency": "GBP", | |
| 83 | + "available": true | |
| 84 | + }, | |
| 85 | + { | |
| 86 | + "size": "UK 10.5 | EU 45.5 | US 11.5", | |
| 87 | + "price": 278, | |
| 88 | + "currency": "GBP", | |
| 89 | + "available": true | |
| 90 | + }, | |
| 91 | + { | |
| 92 | + "size": "UK 11 | EU 46 | US 12", | |
| 93 | + "price": 294, | |
| 94 | + "currency": "GBP", | |
| 95 | + "available": true | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "size": "UK 11.5 | EU 47 | US 12.5", | |
| 99 | + "price": 262, | |
| 100 | + "currency": "GBP", | |
| 101 | + "available": true | |
| 102 | + }, | |
| 103 | + { | |
| 104 | + "size": "UK 12 | EU 47.5 | US 13", | |
| 105 | + "price": 279, | |
| 106 | + "currency": "GBP", | |
| 107 | + "available": true | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + "size": "UK 12.5 | EU 48 | US 13.5", | |
| 111 | + "price": 1331, | |
| 112 | + "currency": "GBP", | |
| 113 | + "available": true | |
| 114 | + }, | |
| 115 | + { | |
| 116 | + "size": "UK 13 | EU 48.5 | US 14", | |
| 117 | + "price": 279, | |
| 118 | + "currency": "GBP", | |
| 119 | + "available": true | |
| 120 | + } | |
| 121 | + ] | |
| 122 | + } | |
| 123 | + }, | |
| 124 | + "expect": { | |
| 125 | + "minCount": 1, | |
| 126 | + "kinds": [ | |
| 127 | + "catalog_item", | |
| 128 | + "price_observation", | |
| 129 | + "listing" | |
| 130 | + ] | |
| 131 | + }, | |
| 132 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 133 | + "capturedAt": "2026-09-07T06:28:55.951Z" | |
| 134 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/rebag/collection-hermes-1.json
+1625 −0
@@ -0,0 +1,1625 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://shop.rebag.com/collections/hermes/products.json?page=1", | |
| 4 | + "externalId": "collection:hermes:1", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:33.858Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "shopify_page", | |
| 10 | + "url": "https://shop.rebag.com/collections/hermes/products.json?page=1", | |
| 11 | + "seed": "hermes", | |
| 12 | + "page": 1, | |
| 13 | + "products": [ | |
| 14 | + { | |
| 15 | + "id": 8235947557041, | |
| 16 | + "title": "Women's Tablier Dress Jacket Denim", | |
| 17 | + "handle": "apparel-hermes-womens-tablier-dress-jacket-denim-3290952", | |
| 18 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Wear and minor creasing throughout, discoloration on interior armpit, scratches on hardware. Accessories: No Accessories Measurements: Length 40.5\", Bust 35.0\" Designer: Hermes Model: Women's Tablier Dress Jacket Denim Exterior Material: Denim, Leather Exterior Color: Black, Blue Interior Material: Denim Interior Color: Blue Hardware Color: Palladium Brand Code: No Code Item Number: 329095/2", | |
| 19 | + "published_at": "2025-02-15T04:04:08-05:00", | |
| 20 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 21 | + "vendor": "Hermes", | |
| 22 | + "product_type": "Apparel", | |
| 23 | + "tags": [ | |
| 24 | + "2500-to-5000", | |
| 25 | + "500-to-1-500", | |
| 26 | + "557", | |
| 27 | + "557A", | |
| 28 | + "apparel", | |
| 29 | + "apparel-type-Jacket", | |
| 30 | + "B2BDEALABR", | |
| 31 | + "B2BDEALAUG", | |
| 32 | + "B2BDEALDIC", | |
| 33 | + "B2BDEALFEB", | |
| 34 | + "B2BDEALJAN", | |
| 35 | + "B2BDEALJUL", | |
| 36 | + "B2BDEALJUN", | |
| 37 | + "B2BDEALMAR", | |
| 38 | + "B2BDEALMAY", | |
| 39 | + "B2BDEALSEPT", | |
| 40 | + "bc-filter-$1\u201a000 to $2\u201a500", | |
| 41 | + "bc-filter-$1\u201a500to$3\u201a000", | |
| 42 | + "bc-filter-$2\u201a500to$5\u201a000", | |
| 43 | + "bc-filter-$500 to $1\u201a500", | |
| 44 | + "bc-filter-Apparel", | |
| 45 | + "bc-filter-apparel-type-Jacket", | |
| 46 | + "bc-filter-Clearance", | |
| 47 | + "bc-filter-custom-size-apparel-L", | |
| 48 | + "bc-filter-dropship-partners-rebag", | |
| 49 | + "bc-filter-exterior-color-Black", | |
| 50 | + "bc-filter-exterior-color-Blue", | |
| 51 | + "bc-filter-exterior-material-Denim", | |
| 52 | + "bc-filter-exterior-material-Leather", | |
| 53 | + "Bc-filter-FallEssentials", | |
| 54 | + "bc-filter-gender-Women", | |
| 55 | + "bc-filter-General View", | |
| 56 | + "bc-filter-Great", | |
| 57 | + "bc-filter-interior-material-Denim", | |
| 58 | + "bc-filter-Last Call", | |
| 59 | + "bc-filter-NYC - Westfield World Trade Center", | |
| 60 | + "bc-filter-Promo Eligible", | |
| 61 | + "bc-filter-Up to 50% off", | |
| 62 | + "bc-filter-Upto40%off", | |
| 63 | + "black" | |
| 64 | + ], | |
| 65 | + "variants": [ | |
| 66 | + { | |
| 67 | + "id": 45155061465265, | |
| 68 | + "title": "Great | Item # 329095/2 / black", | |
| 69 | + "price": "1360.00", | |
| 70 | + "compare_at_price": null, | |
| 71 | + "available": true, | |
| 72 | + "sku": "329095/2", | |
| 73 | + "option1": "Great | Item # 329095/2", | |
| 74 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 75 | + } | |
| 76 | + ], | |
| 77 | + "images": [ | |
| 78 | + { | |
| 79 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/329095-2_20Hermes_20Women_s_20Tablier_20Dress_20Jacket_20Denim_2D_0002.jpg?v=1739561981" | |
| 80 | + }, | |
| 81 | + { | |
| 82 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/329095-2_20Hermes_20Women_s_20Tablier_20Dress_20Jacket_20Denim_2D_0003.jpg?v=1739561981" | |
| 83 | + } | |
| 84 | + ], | |
| 85 | + "options": [ | |
| 86 | + { | |
| 87 | + "name": "Default Title" | |
| 88 | + }, | |
| 89 | + { | |
| 90 | + "name": "Color" | |
| 91 | + } | |
| 92 | + ] | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + "id": 8368949854385, | |
| 96 | + "title": "Petite Course Bag Epsom", | |
| 97 | + "handle": "handbags-hermes-petite-course-bag-epsom-3478091", | |
| 98 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Excellent. Interior: minor scuffs | Hardware: minor scratches Accessories: Dust Bag Measurements: Height 7\", Width 11\", Depth 2.5\" Designer: Hermes Model: Petite Course Bag Epsom Exterior Material: Leather Exterior Color: Black Interior Material: Leather Interior Color: Black Hardware Color: Palladium Brand Code: B (2023) Item Number: 347809/1", | |
| 99 | + "published_at": "2025-05-24T03:57:01-04:00", | |
| 100 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 101 | + "vendor": "Hermes", | |
| 102 | + "product_type": "Belt bag", | |
| 103 | + "tags": [ | |
| 104 | + "5000-to-10000", | |
| 105 | + "599", | |
| 106 | + "599A", | |
| 107 | + "all-bags", | |
| 108 | + "B2BDEALABR", | |
| 109 | + "B2BDEALAUG", | |
| 110 | + "B2BDEALJUL", | |
| 111 | + "B2BDEALJUN", | |
| 112 | + "B2BDEALMAR", | |
| 113 | + "B2BDEALMAY", | |
| 114 | + "B2BDEALSEPT", | |
| 115 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 116 | + "bc-filter-$3\u201a000 and Up", | |
| 117 | + "bc-filter-Bags", | |
| 118 | + "bc-filter-Belt Bag", | |
| 119 | + "bc-filter-Bloomingdale's Soho NYC", | |
| 120 | + "bc-filter-Clearance", | |
| 121 | + "bc-filter-dropship-partners-rebag", | |
| 122 | + "bc-filter-exterior-color-Black", | |
| 123 | + "bc-filter-exterior-material-Leather", | |
| 124 | + "Bc-filter-Fall Essentials", | |
| 125 | + "bc-filter-General View", | |
| 126 | + "bc-filter-interior-material-Leather", | |
| 127 | + "bc-filter-Last Call", | |
| 128 | + "bc-filter-Pristine", | |
| 129 | + "bc-filter-Promo Eligible", | |
| 130 | + "bc-filter-Up to 50% off", | |
| 131 | + "belt-bag", | |
| 132 | + "bl-soho-nyc", | |
| 133 | + "black", | |
| 134 | + "bloomingdales", | |
| 135 | + "CL22", | |
| 136 | + "dust-bag", | |
| 137 | + "dynamic-feed-24", | |
| 138 | + "exterior-color-black", | |
| 139 | + "exterior-material-leather", | |
| 140 | + "Fall-essentials-coll-2025", | |
| 141 | + "FallEssentialsSept2025", | |
| 142 | + "handbag", | |
| 143 | + "hardware-color-palladium" | |
| 144 | + ], | |
| 145 | + "variants": [ | |
| 146 | + { | |
| 147 | + "id": 45562784841905, | |
| 148 | + "title": "Excellent | Item # 347809/1 / black", | |
| 149 | + "price": "3130.00", | |
| 150 | + "compare_at_price": null, | |
| 151 | + "available": true, | |
| 152 | + "sku": "347809/1", | |
| 153 | + "option1": "Excellent | Item # 347809/1", | |
| 154 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 155 | + } | |
| 156 | + ], | |
| 157 | + "images": [ | |
| 158 | + { | |
| 159 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/347809-1_20Hermes_20Petite_20Course_20Bag_20Epsom_2D_0002.jpg?v=1748018295" | |
| 160 | + }, | |
| 161 | + { | |
| 162 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/347809-1_20Hermes_20Petite_20Course_20Bag_20Epsom_2D_0003.jpg?v=1748018295" | |
| 163 | + } | |
| 164 | + ], | |
| 165 | + "options": [ | |
| 166 | + { | |
| 167 | + "name": "Default Title" | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "name": "Color" | |
| 171 | + } | |
| 172 | + ] | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "id": 8436328169649, | |
| 176 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 177 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3534862", | |
| 178 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: corner wear, creases, scuffs, cracking | Handles/Straps: scuffs, discoloration, cracking, minor creases | Interior: discoloration, scuffs | Hardware: scratches, minor tarnished Accessories: Lock Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue Interior Material: Raw Leather Interior Color: Blue Hardware Color: Palladium Brand Code: C (2018) Item Number: 353486/2", | |
| 179 | + "published_at": "2025-06-17T00:56:48-04:00", | |
| 180 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 181 | + "vendor": "Hermes", | |
| 182 | + "product_type": "Bucket", | |
| 183 | + "tags": [ | |
| 184 | + "1-500-to-3-000", | |
| 185 | + "5000-to-10000", | |
| 186 | + "609", | |
| 187 | + "609A", | |
| 188 | + "all-bags", | |
| 189 | + "B2BDEALABR", | |
| 190 | + "B2BDEALAUG", | |
| 191 | + "B2BDEALJUL", | |
| 192 | + "B2BDEALJUN", | |
| 193 | + "B2BDEALMAR", | |
| 194 | + "B2BDEALMAY", | |
| 195 | + "B2BDEALSEPT", | |
| 196 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 197 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 198 | + "bc-filter-Bags", | |
| 199 | + "bc-filter-Bloomingdale's Boca Raton", | |
| 200 | + "bc-filter-Bucket", | |
| 201 | + "bc-filter-Clearance", | |
| 202 | + "bc-filter-dropship-partners-rebag", | |
| 203 | + "bc-filter-exterior-color-Blue", | |
| 204 | + "bc-filter-exterior-material-Leather", | |
| 205 | + "Bc-filter-Fall Essentials", | |
| 206 | + "bc-filter-General View", | |
| 207 | + "bc-filter-Great", | |
| 208 | + "bc-filter-interior-material-Raw Leather", | |
| 209 | + "bc-filter-Last Call", | |
| 210 | + "bc-filter-Promo Eligible", | |
| 211 | + "bc-filter-Shoulder Bags", | |
| 212 | + "bc-filter-Up to 50% off", | |
| 213 | + "bl-boca-raton", | |
| 214 | + "bloomingdales", | |
| 215 | + "blue", | |
| 216 | + "bucket", | |
| 217 | + "CL22", | |
| 218 | + "dynamic-feed-24", | |
| 219 | + "exterior-color-blue", | |
| 220 | + "exterior-material-leather", | |
| 221 | + "Fall-essentials-coll-2025", | |
| 222 | + "FallEssentialsSept2025", | |
| 223 | + "great" | |
| 224 | + ], | |
| 225 | + "variants": [ | |
| 226 | + { | |
| 227 | + "id": 45697385136305, | |
| 228 | + "title": "Great | Item # 353486/2 / blue", | |
| 229 | + "price": "2730.00", | |
| 230 | + "compare_at_price": null, | |
| 231 | + "available": true, | |
| 232 | + "sku": "353486/2", | |
| 233 | + "option1": "Great | Item # 353486/2", | |
| 234 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 235 | + } | |
| 236 | + ], | |
| 237 | + "images": [ | |
| 238 | + { | |
| 239 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/353486-2_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1750097830" | |
| 240 | + }, | |
| 241 | + { | |
| 242 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/353486-2_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1750097830" | |
| 243 | + } | |
| 244 | + ], | |
| 245 | + "options": [ | |
| 246 | + { | |
| 247 | + "name": "Default Title" | |
| 248 | + }, | |
| 249 | + { | |
| 250 | + "name": "Color" | |
| 251 | + } | |
| 252 | + ] | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "id": 8461682311345, | |
| 256 | + "title": "In-The-Loop Belt Bag Swift", | |
| 257 | + "handle": "handbags-hermes-in-the-loop-belt-bag-swift-3552451", | |
| 258 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Great. Exterior: creases, minor corner wear, minor edge wear, minor scuffs | Handles/Straps: creases, minor scuffs, minor discoloration | Interior: scuffs, minor discoloration | Hardware: minor scratches Accessories: Dust Bag, With Strap Measurements: Height 5\", Width 7\", Depth 1.5\", Strap Drop 10-17\" Designer: Hermes Model: In-The-Loop Belt Bag Swift Exterior Material: Leather Exterior Color: Blue Interior Material: Leather Interior Color: Green Hardware Color: Palladium Brand Code: Y (2020) Item Number: 355245/1", | |
| 259 | + "published_at": "2025-06-21T03:58:41-04:00", | |
| 260 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 261 | + "vendor": "Hermes", | |
| 262 | + "product_type": "Belt bag", | |
| 263 | + "tags": [ | |
| 264 | + "1-500-to-3-000", | |
| 265 | + "2500-to-5000", | |
| 266 | + "611", | |
| 267 | + "611A", | |
| 268 | + "all-bags", | |
| 269 | + "B2BDEALABR", | |
| 270 | + "B2BDEALAUG", | |
| 271 | + "B2BDEALJUL", | |
| 272 | + "B2BDEALJUN", | |
| 273 | + "B2BDEALMAR", | |
| 274 | + "B2BDEALMAY", | |
| 275 | + "B2BDEALSEPT", | |
| 276 | + "bc-filter-$1\u201a000 to $2\u201a500", | |
| 277 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 278 | + "bc-filter-Bags", | |
| 279 | + "bc-filter-Belt Bag", | |
| 280 | + "bc-filter-Bloomingdale's Orlando", | |
| 281 | + "bc-filter-Clearance", | |
| 282 | + "bc-filter-dropship-partners-rebag", | |
| 283 | + "bc-filter-Excellent", | |
| 284 | + "bc-filter-exterior-color-Blue", | |
| 285 | + "bc-filter-exterior-material-Leather", | |
| 286 | + "bc-filter-General View", | |
| 287 | + "bc-filter-interior-material-Leather", | |
| 288 | + "bc-filter-Last Call", | |
| 289 | + "bc-filter-Promo Eligible", | |
| 290 | + "bc-filter-Up to 50% off", | |
| 291 | + "belt-bag", | |
| 292 | + "bl-orlando-fl", | |
| 293 | + "bloomingdales", | |
| 294 | + "blue", | |
| 295 | + "CL22", | |
| 296 | + "dust-bag", | |
| 297 | + "dynamic-feed-24", | |
| 298 | + "excellent", | |
| 299 | + "exterior-color-blue", | |
| 300 | + "exterior-material-leather", | |
| 301 | + "handbag", | |
| 302 | + "hardware-color-palladium", | |
| 303 | + "hermes" | |
| 304 | + ], | |
| 305 | + "variants": [ | |
| 306 | + { | |
| 307 | + "id": 45732411998385, | |
| 308 | + "title": "Great | Item # 355245/1 / blue", | |
| 309 | + "price": "2275.00", | |
| 310 | + "compare_at_price": null, | |
| 311 | + "available": true, | |
| 312 | + "sku": "355245/1", | |
| 313 | + "option1": "Great | Item # 355245/1", | |
| 314 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 315 | + } | |
| 316 | + ], | |
| 317 | + "images": [ | |
| 318 | + { | |
| 319 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/355245-1_20Hermes_20In-The-Loop_20Belt_20Bag_20Swift_2D_0002.jpg?v=1750446091" | |
| 320 | + }, | |
| 321 | + { | |
| 322 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/355245-1_20Hermes_20In-The-Loop_20Belt_20Bag_20Swift_2D_0003.jpg?v=1750446091" | |
| 323 | + } | |
| 324 | + ], | |
| 325 | + "options": [ | |
| 326 | + { | |
| 327 | + "name": "Default Title" | |
| 328 | + }, | |
| 329 | + { | |
| 330 | + "name": "Color" | |
| 331 | + } | |
| 332 | + ] | |
| 333 | + }, | |
| 334 | + { | |
| 335 | + "id": 8619796398257, | |
| 336 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 337 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3550402", | |
| 338 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Good. Exterior: heavy corner wear, scuffs, creases, minor edge wear, minor discoloration | Handles/Straps: scuffs, cracking, discoloration, minor creases | Interior: scuffs, discoloration, cigarette odor, minor stains | Hardware: scratches, tarnished Accessories: No Accessories Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Black, Blue, Brown Interior Material: Raw Leather Interior Color: Blue Hardware Color: Palladium Brand Code: C (2018) Item Number: 355040/2", | |
| 339 | + "published_at": "2025-07-03T03:51:48-04:00", | |
| 340 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 341 | + "vendor": "Hermes", | |
| 342 | + "product_type": "Bucket", | |
| 343 | + "tags": [ | |
| 344 | + "1-500-to-3-000", | |
| 345 | + "2500-to-5000", | |
| 346 | + "616", | |
| 347 | + "616A", | |
| 348 | + "all-bags", | |
| 349 | + "B2BDEALABR", | |
| 350 | + "B2BDEALAUG", | |
| 351 | + "B2BDEALJUL", | |
| 352 | + "B2BDEALJUN", | |
| 353 | + "B2BDEALMAR", | |
| 354 | + "B2BDEALMAY", | |
| 355 | + "B2BDEALSEPT", | |
| 356 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 357 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 358 | + "bc-filter-Bags", | |
| 359 | + "bc-filter-Bucket", | |
| 360 | + "bc-filter-Cabazon - Desert Hills Premium Outlets", | |
| 361 | + "bc-filter-Clearance", | |
| 362 | + "bc-filter-dropship-partners-rebag", | |
| 363 | + "bc-filter-exterior-color-Black", | |
| 364 | + "bc-filter-exterior-color-Blue", | |
| 365 | + "bc-filter-exterior-color-Brown", | |
| 366 | + "bc-filter-exterior-material-Leather", | |
| 367 | + "Bc-filter-Fall Essentials", | |
| 368 | + "bc-filter-General View", | |
| 369 | + "bc-filter-interior-material-Raw Leather", | |
| 370 | + "bc-filter-Last Call", | |
| 371 | + "bc-filter-Promo Eligible", | |
| 372 | + "bc-filter-Shoulder Bags", | |
| 373 | + "bc-filter-Up to 50% off", | |
| 374 | + "bc-filter-Very Good", | |
| 375 | + "black", | |
| 376 | + "bloomingdales", | |
| 377 | + "blue", | |
| 378 | + "brown", | |
| 379 | + "bucket", | |
| 380 | + "CL22", | |
| 381 | + "desert-hills", | |
| 382 | + "dynamic-feed-24", | |
| 383 | + "exterior-color-black" | |
| 384 | + ], | |
| 385 | + "variants": [ | |
| 386 | + { | |
| 387 | + "id": 45913583812785, | |
| 388 | + "title": "Very Good | Item # 355040/2 / black", | |
| 389 | + "price": "2820.00", | |
| 390 | + "compare_at_price": null, | |
| 391 | + "available": true, | |
| 392 | + "sku": "355040/2", | |
| 393 | + "option1": "Very Good | Item # 355040/2", | |
| 394 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 395 | + } | |
| 396 | + ], | |
| 397 | + "images": [ | |
| 398 | + { | |
| 399 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/355040-2_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1751479249" | |
| 400 | + }, | |
| 401 | + { | |
| 402 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/355040-2_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1751479249" | |
| 403 | + } | |
| 404 | + ], | |
| 405 | + "options": [ | |
| 406 | + { | |
| 407 | + "name": "Default Title" | |
| 408 | + }, | |
| 409 | + { | |
| 410 | + "name": "Color" | |
| 411 | + } | |
| 412 | + ] | |
| 413 | + }, | |
| 414 | + { | |
| 415 | + "id": 8775878213809, | |
| 416 | + "title": "Picotin Lock Bag Clemence MM", | |
| 417 | + "handle": "handbags-hermes-picotin-lock-bag-clemence-mm36102310", | |
| 418 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Good. Exterior: touched-up base edges and corners, scuffs, creases, corner wear, minor edge wear | Handles/Straps: creases, cracking, discoloration, minor scuffs | Interior: scuffs, discoloration, light odor | Hardware: scratches, tarnished Accessories: No Accessories Measurements: Handle Drop 7\", Height 9\", Width 8.5\", Depth 7\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Clemence MM Exterior Material: Leather Exterior Color: Orange Interior Material: Raw Leather Interior Color: Orange Hardware Color: Gold Brand Code: A (2017) Item Number: 361023/10", | |
| 419 | + "published_at": "2025-08-16T03:48:24-04:00", | |
| 420 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 421 | + "vendor": "Hermes", | |
| 422 | + "product_type": "Bucket", | |
| 423 | + "tags": [ | |
| 424 | + "1-500-to-3-000", | |
| 425 | + "20%-39%", | |
| 426 | + "20OFFSTORE", | |
| 427 | + "2500-to-5000", | |
| 428 | + "635", | |
| 429 | + "635A", | |
| 430 | + "all-bags", | |
| 431 | + "B2BDEALABR", | |
| 432 | + "B2BDEALAUG", | |
| 433 | + "B2BDEALJUL", | |
| 434 | + "B2BDEALJUN", | |
| 435 | + "B2BDEALMAR", | |
| 436 | + "B2BDEALMAY", | |
| 437 | + "B2BDEALSEPT", | |
| 438 | + "bc-filter-$1\u201a000 to $2\u201a500", | |
| 439 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 440 | + "bc-filter-Bags", | |
| 441 | + "bc-filter-Bucket", | |
| 442 | + "bc-filter-Clearance", | |
| 443 | + "bc-filter-dropship-partners-rebag", | |
| 444 | + "bc-filter-exterior-color-Orange", | |
| 445 | + "bc-filter-exterior-material-Leather", | |
| 446 | + "Bc-filter-Fall Essentials", | |
| 447 | + "bc-filter-General View", | |
| 448 | + "bc-filter-interior-material-Raw Leather", | |
| 449 | + "bc-filter-Last Call", | |
| 450 | + "bc-filter-NYC - Westfield World Trade Center", | |
| 451 | + "bc-filter-Promo Eligible", | |
| 452 | + "bc-filter-Up to 40% off", | |
| 453 | + "bc-filter-Very Good", | |
| 454 | + "bloomingdales", | |
| 455 | + "bucket", | |
| 456 | + "CL22", | |
| 457 | + "dynamic-feed-24", | |
| 458 | + "exterior-color-orange", | |
| 459 | + "exterior-material-leather", | |
| 460 | + "Fall-essentials-coll-2025", | |
| 461 | + "FallEssentialsSept2025", | |
| 462 | + "good", | |
| 463 | + "handbag" | |
| 464 | + ], | |
| 465 | + "variants": [ | |
| 466 | + { | |
| 467 | + "id": 46149809143985, | |
| 468 | + "title": "Very Good | Item # 361023/10 / orange", | |
| 469 | + "price": "2230.00", | |
| 470 | + "compare_at_price": null, | |
| 471 | + "available": true, | |
| 472 | + "sku": "361023/10", | |
| 473 | + "option1": "Very Good | Item # 361023/10", | |
| 474 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 475 | + } | |
| 476 | + ], | |
| 477 | + "images": [ | |
| 478 | + { | |
| 479 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/361023-10_20Hermes_20Picotin_20Lock_20Bag_20Clemence_20MM_2D_0002.jpg?v=1755286273" | |
| 480 | + }, | |
| 481 | + { | |
| 482 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/361023-10_20Hermes_20Picotin_20Lock_20Bag_20Clemence_20MM_2D_0003.jpg?v=1755286272" | |
| 483 | + } | |
| 484 | + ], | |
| 485 | + "options": [ | |
| 486 | + { | |
| 487 | + "name": "Default Title" | |
| 488 | + }, | |
| 489 | + { | |
| 490 | + "name": "Color" | |
| 491 | + } | |
| 492 | + ] | |
| 493 | + }, | |
| 494 | + { | |
| 495 | + "id": 8781885472945, | |
| 496 | + "title": "Women's Colette Loafers Leather", | |
| 497 | + "handle": "shoes-hermes-womens-colette-loafers-leather-3687022", | |
| 498 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Great. Creasing throughout, marks in interior, discoloration, indentations, scuffs, and minor peeling on outer soles, scratches on hardware. Accessories: Dust Bag Measurements: Designer: Hermes Model: Women's Colette Loafers Leather Exterior Material: Leather Exterior Color: Brown Interior Material: Leather Interior Color: Brown Hardware Color: Gold Brand Code: MB 211041Z Item Number: 368702/2", | |
| 499 | + "published_at": "2025-08-21T03:58:58-04:00", | |
| 500 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 501 | + "vendor": "Hermes", | |
| 502 | + "product_type": "Shoes", | |
| 503 | + "tags": [ | |
| 504 | + "100-to-500", | |
| 505 | + "40%-59%", | |
| 506 | + "637", | |
| 507 | + "637A", | |
| 508 | + "B2BDEALABR", | |
| 509 | + "B2BDEALAUG", | |
| 510 | + "B2BDEALJUL", | |
| 511 | + "B2BDEALJUN", | |
| 512 | + "B2BDEALMAR", | |
| 513 | + "B2BDEALMAY", | |
| 514 | + "B2BDEALSEPT", | |
| 515 | + "bags-under-500", | |
| 516 | + "bc-filter-$100 to $500", | |
| 517 | + "bc-filter-Clearance", | |
| 518 | + "bc-filter-custom-size-shoes-5", | |
| 519 | + "bc-filter-dropship-partners-rebag", | |
| 520 | + "bc-filter-Excellent", | |
| 521 | + "bc-filter-exterior-color-Brown", | |
| 522 | + "bc-filter-exterior-material-Leather", | |
| 523 | + "bc-filter-gender-Women", | |
| 524 | + "bc-filter-General View", | |
| 525 | + "bc-filter-interior-material-Leather", | |
| 526 | + "bc-filter-Last Call", | |
| 527 | + "bc-filter-Online", | |
| 528 | + "bc-filter-Promo Eligible", | |
| 529 | + "bc-filter-shoe-style-Loafers", | |
| 530 | + "bc-filter-Shoes", | |
| 531 | + "bc-filter-Under $1\u201a000", | |
| 532 | + "bc-filter-Up to 50% off", | |
| 533 | + "brown", | |
| 534 | + "CL22", | |
| 535 | + "custom-size-shoes-5", | |
| 536 | + "dust-bag", | |
| 537 | + "dynamic-feed-24", | |
| 538 | + "excellent", | |
| 539 | + "exterior-color-brown", | |
| 540 | + "exterior-material-leather", | |
| 541 | + "gender-Women", | |
| 542 | + "hardware-color-gold", | |
| 543 | + "hermes" | |
| 544 | + ], | |
| 545 | + "variants": [ | |
| 546 | + { | |
| 547 | + "id": 46169048875185, | |
| 548 | + "title": "Great | Item # 368702/2 / brown", | |
| 549 | + "price": "495.00", | |
| 550 | + "compare_at_price": null, | |
| 551 | + "available": true, | |
| 552 | + "sku": "368702/2", | |
| 553 | + "option1": "Great | Item # 368702/2", | |
| 554 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 555 | + } | |
| 556 | + ], | |
| 557 | + "images": [ | |
| 558 | + { | |
| 559 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/368702-2_20Hermes_20Womens_20Colette_20Loafers_20Leather_2D_0002.jpg?v=1755708524" | |
| 560 | + }, | |
| 561 | + { | |
| 562 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/368702-2_20Hermes_20Womens_20Colette_20Loafers_20Leather_2D_0003.jpg?v=1755708524" | |
| 563 | + } | |
| 564 | + ], | |
| 565 | + "options": [ | |
| 566 | + { | |
| 567 | + "name": "Default Title" | |
| 568 | + }, | |
| 569 | + { | |
| 570 | + "name": "Color" | |
| 571 | + } | |
| 572 | + ] | |
| 573 | + }, | |
| 574 | + { | |
| 575 | + "id": 8789624291505, | |
| 576 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 577 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3681361", | |
| 578 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Good. Exterior: corner wear, minor scuffs, minor creases | Handles/Straps: heavy cracking, scuffs, creases, minor discoloration | Interior: scuffs, discoloration | Hardware: scratches, minor tarnished Accessories: Keys, Lock Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue, Red Interior Material: Raw Leather Interior Color: Red Hardware Color: Palladium Brand Code: D (2019) Item Number: 368136/1", | |
| 579 | + "published_at": "2025-08-23T03:54:27-04:00", | |
| 580 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 581 | + "vendor": "Hermes", | |
| 582 | + "product_type": "Bucket", | |
| 583 | + "tags": [ | |
| 584 | + "1-500-to-3-000", | |
| 585 | + "2500-to-5000", | |
| 586 | + "638", | |
| 587 | + "638A", | |
| 588 | + "all-bags", | |
| 589 | + "B2BDEALABR", | |
| 590 | + "B2BDEALAUG", | |
| 591 | + "B2BDEALJUL", | |
| 592 | + "B2BDEALJUN", | |
| 593 | + "B2BDEALMAR", | |
| 594 | + "B2BDEALMAY", | |
| 595 | + "B2BDEALSEPT", | |
| 596 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 597 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 598 | + "bc-filter-Bags", | |
| 599 | + "bc-filter-Bloomingdale's Orlando", | |
| 600 | + "bc-filter-Bucket", | |
| 601 | + "bc-filter-Clearance", | |
| 602 | + "bc-filter-dropship-partners-rebag", | |
| 603 | + "bc-filter-exterior-color-Blue", | |
| 604 | + "bc-filter-exterior-color-Red", | |
| 605 | + "bc-filter-exterior-material-Leather", | |
| 606 | + "Bc-filter-Fall Essentials", | |
| 607 | + "bc-filter-General View", | |
| 608 | + "bc-filter-interior-material-Raw Leather", | |
| 609 | + "bc-filter-Last Call", | |
| 610 | + "bc-filter-Promo Eligible", | |
| 611 | + "bc-filter-Shoulder Bags", | |
| 612 | + "bc-filter-Up to 50% off", | |
| 613 | + "bc-filter-Very Good", | |
| 614 | + "bl-orlando-fl", | |
| 615 | + "bloomingdales", | |
| 616 | + "blue", | |
| 617 | + "bucket", | |
| 618 | + "CL22", | |
| 619 | + "dynamic-feed-24", | |
| 620 | + "exterior-color-blue", | |
| 621 | + "exterior-color-red", | |
| 622 | + "exterior-material-leather", | |
| 623 | + "Fall-essentials-coll-2025" | |
| 624 | + ], | |
| 625 | + "variants": [ | |
| 626 | + { | |
| 627 | + "id": 46191896625329, | |
| 628 | + "title": "Very Good | Item # 368136/1 / blue", | |
| 629 | + "price": "2665.00", | |
| 630 | + "compare_at_price": null, | |
| 631 | + "available": true, | |
| 632 | + "sku": "368136/1", | |
| 633 | + "option1": "Very Good | Item # 368136/1", | |
| 634 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 635 | + } | |
| 636 | + ], | |
| 637 | + "images": [ | |
| 638 | + { | |
| 639 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/368136-1_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1755890506" | |
| 640 | + }, | |
| 641 | + { | |
| 642 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/368136-1_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1755890506" | |
| 643 | + } | |
| 644 | + ], | |
| 645 | + "options": [ | |
| 646 | + { | |
| 647 | + "name": "Default Title" | |
| 648 | + }, | |
| 649 | + { | |
| 650 | + "name": "Color" | |
| 651 | + } | |
| 652 | + ] | |
| 653 | + }, | |
| 654 | + { | |
| 655 | + "id": 8799528321201, | |
| 656 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 657 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3729281", | |
| 658 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: corner wear, creases, minor edge wear, minor scuffs, minor cracking | Handles/Straps: discoloration, creases, minor scuffs, minor cracking | Interior: scuffs, minor discoloration | Hardware: scratches, minor tarnished Accessories: Keys, Lock, Dust Bag Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue, Red Interior Material: Raw Leather Interior Color: Blue Hardware Color: Palladium Brand Code: C (2018) Item Number: 372928/1", | |
| 659 | + "published_at": "2025-09-02T03:56:38-04:00", | |
| 660 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 661 | + "vendor": "Hermes", | |
| 662 | + "product_type": "Bucket", | |
| 663 | + "tags": [ | |
| 664 | + "1-500-to-3-000", | |
| 665 | + "5000-to-10000", | |
| 666 | + "642", | |
| 667 | + "642A", | |
| 668 | + "all-bags", | |
| 669 | + "B2BDEALABR", | |
| 670 | + "B2BDEALAUG", | |
| 671 | + "B2BDEALJUL", | |
| 672 | + "B2BDEALJUN", | |
| 673 | + "B2BDEALMAR", | |
| 674 | + "B2BDEALMAY", | |
| 675 | + "B2BDEALSEPT", | |
| 676 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 677 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 678 | + "bc-filter-Bags", | |
| 679 | + "bc-filter-Bloomingdale's Boca Raton", | |
| 680 | + "bc-filter-Bucket", | |
| 681 | + "bc-filter-Clearance", | |
| 682 | + "bc-filter-dropship-partners-rebag", | |
| 683 | + "bc-filter-exterior-color-Blue", | |
| 684 | + "bc-filter-exterior-color-Red", | |
| 685 | + "bc-filter-exterior-material-Leather", | |
| 686 | + "bc-filter-General View", | |
| 687 | + "bc-filter-Great", | |
| 688 | + "bc-filter-interior-material-Raw Leather", | |
| 689 | + "bc-filter-Last Call", | |
| 690 | + "bc-filter-Promo Eligible", | |
| 691 | + "bc-filter-Shoulder Bags", | |
| 692 | + "bc-filter-Up to 50% off", | |
| 693 | + "bl-boca-raton", | |
| 694 | + "bloomingdales", | |
| 695 | + "blue", | |
| 696 | + "bucket", | |
| 697 | + "CL22", | |
| 698 | + "dust-bag", | |
| 699 | + "dynamic-feed-24", | |
| 700 | + "exterior-color-blue", | |
| 701 | + "exterior-color-red", | |
| 702 | + "exterior-material-leather", | |
| 703 | + "great" | |
| 704 | + ], | |
| 705 | + "variants": [ | |
| 706 | + { | |
| 707 | + "id": 46218412654769, | |
| 708 | + "title": "Great | Item # 372928/1 / blue", | |
| 709 | + "price": "2930.00", | |
| 710 | + "compare_at_price": null, | |
| 711 | + "available": true, | |
| 712 | + "sku": "372928/1", | |
| 713 | + "option1": "Great | Item # 372928/1", | |
| 714 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 715 | + } | |
| 716 | + ], | |
| 717 | + "images": [ | |
| 718 | + { | |
| 719 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/372928-1_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002_2fc19044-1a8e-4456-9ea3-b49d47973702.jpg?v=1756748687" | |
| 720 | + }, | |
| 721 | + { | |
| 722 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/372928-1_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1756748687" | |
| 723 | + } | |
| 724 | + ], | |
| 725 | + "options": [ | |
| 726 | + { | |
| 727 | + "name": "Default Title" | |
| 728 | + }, | |
| 729 | + { | |
| 730 | + "name": "Color" | |
| 731 | + } | |
| 732 | + ] | |
| 733 | + }, | |
| 734 | + { | |
| 735 | + "id": 8802281980081, | |
| 736 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 737 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3697463", | |
| 738 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: scuffs, creases, corner wear | Handles/Straps: minor scuffs, minor creases, minor discoloration | Interior: scuffs | Hardware: scratches, minor tarnished Accessories: Lock, Dust Bag, Keys Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue, Red Interior Material: Raw Leather Interior Color: Red Hardware Color: Palladium Brand Code: C (2018) Item Number: 369746/3", | |
| 739 | + "published_at": "2025-09-06T03:52:03-04:00", | |
| 740 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 741 | + "vendor": "Hermes", | |
| 742 | + "product_type": "Bucket", | |
| 743 | + "tags": [ | |
| 744 | + "1-500-to-3-000", | |
| 745 | + "5000-to-10000", | |
| 746 | + "644", | |
| 747 | + "644A", | |
| 748 | + "all-bags", | |
| 749 | + "B2BDEALABR", | |
| 750 | + "B2BDEALAUG", | |
| 751 | + "B2BDEALJUL", | |
| 752 | + "B2BDEALJUN", | |
| 753 | + "B2BDEALMAR", | |
| 754 | + "B2BDEALMAY", | |
| 755 | + "B2BDEALSEPT", | |
| 756 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 757 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 758 | + "bc-filter-Bags", | |
| 759 | + "bc-filter-Bloomingdale's White Plains", | |
| 760 | + "bc-filter-Bucket", | |
| 761 | + "bc-filter-Clearance", | |
| 762 | + "bc-filter-dropship-partners-rebag", | |
| 763 | + "bc-filter-exterior-color-Blue", | |
| 764 | + "bc-filter-exterior-color-Red", | |
| 765 | + "bc-filter-exterior-material-Leather", | |
| 766 | + "bc-filter-General View", | |
| 767 | + "bc-filter-Great", | |
| 768 | + "bc-filter-interior-material-Raw Leather", | |
| 769 | + "bc-filter-Last Call", | |
| 770 | + "bc-filter-Promo Eligible", | |
| 771 | + "bc-filter-Shoulder Bags", | |
| 772 | + "bc-filter-Up to 50% off", | |
| 773 | + "bl-white-plains", | |
| 774 | + "bloomingdales", | |
| 775 | + "blue", | |
| 776 | + "bucket", | |
| 777 | + "CL22", | |
| 778 | + "dust-bag", | |
| 779 | + "dynamic-feed-24", | |
| 780 | + "exterior-color-blue", | |
| 781 | + "exterior-color-red", | |
| 782 | + "exterior-material-leather", | |
| 783 | + "great" | |
| 784 | + ], | |
| 785 | + "variants": [ | |
| 786 | + { | |
| 787 | + "id": 46228185645233, | |
| 788 | + "title": "Great | Item # 369746/3 / blue", | |
| 789 | + "price": "2905.00", | |
| 790 | + "compare_at_price": null, | |
| 791 | + "available": true, | |
| 792 | + "sku": "369746/3", | |
| 793 | + "option1": "Great | Item # 369746/3", | |
| 794 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 795 | + } | |
| 796 | + ], | |
| 797 | + "images": [ | |
| 798 | + { | |
| 799 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/369746-3_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1757098591" | |
| 800 | + }, | |
| 801 | + { | |
| 802 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/369746-3_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1757098591" | |
| 803 | + } | |
| 804 | + ], | |
| 805 | + "options": [ | |
| 806 | + { | |
| 807 | + "name": "Default Title" | |
| 808 | + }, | |
| 809 | + { | |
| 810 | + "name": "Color" | |
| 811 | + } | |
| 812 | + ] | |
| 813 | + }, | |
| 814 | + { | |
| 815 | + "id": 8802284634289, | |
| 816 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 817 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3677833", | |
| 818 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Good. Exterior: corner wear, edge wear, creases, scuffs, cracking | Handles/Straps: creases, scuffs, discoloration | Interior: glitter, discoloration, scuffs, minor stains | Hardware: scratches, minor tarnished Accessories: No Accessories Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue, Red Interior Material: Raw Leather Interior Color: Blue Hardware Color: Palladium Brand Code: C (2018) Item Number: 367783/3", | |
| 819 | + "published_at": "2025-09-06T03:49:58-04:00", | |
| 820 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 821 | + "vendor": "Hermes", | |
| 822 | + "product_type": "Bucket", | |
| 823 | + "tags": [ | |
| 824 | + "1-500-to-3-000", | |
| 825 | + "5000-to-10000", | |
| 826 | + "644", | |
| 827 | + "644A", | |
| 828 | + "all-bags", | |
| 829 | + "B2BDEALABR", | |
| 830 | + "B2BDEALAUG", | |
| 831 | + "B2BDEALJUL", | |
| 832 | + "B2BDEALJUN", | |
| 833 | + "B2BDEALMAR", | |
| 834 | + "B2BDEALMAY", | |
| 835 | + "B2BDEALSEPT", | |
| 836 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 837 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 838 | + "bc-filter-Bags", | |
| 839 | + "bc-filter-Bloomingdale's Fashion Valley", | |
| 840 | + "bc-filter-Bucket", | |
| 841 | + "bc-filter-Clearance", | |
| 842 | + "bc-filter-dropship-partners-rebag", | |
| 843 | + "bc-filter-exterior-color-Blue", | |
| 844 | + "bc-filter-exterior-color-Red", | |
| 845 | + "bc-filter-exterior-material-Leather", | |
| 846 | + "Bc-filter-Fall Essentials", | |
| 847 | + "bc-filter-General View", | |
| 848 | + "bc-filter-interior-material-Raw Leather", | |
| 849 | + "bc-filter-Last Call", | |
| 850 | + "bc-filter-Promo Eligible", | |
| 851 | + "bc-filter-Shoulder Bags", | |
| 852 | + "bc-filter-Up to 50% off", | |
| 853 | + "bc-filter-Very Good", | |
| 854 | + "bl-fashion-valley", | |
| 855 | + "bloomingdales", | |
| 856 | + "blue", | |
| 857 | + "bucket", | |
| 858 | + "CL22", | |
| 859 | + "dynamic-feed-24", | |
| 860 | + "exterior-color-blue", | |
| 861 | + "exterior-color-red", | |
| 862 | + "exterior-material-leather", | |
| 863 | + "Fall-essentials-coll-2025" | |
| 864 | + ], | |
| 865 | + "variants": [ | |
| 866 | + { | |
| 867 | + "id": 46228191903921, | |
| 868 | + "title": "Very Good | Item # 367783/3 / blue", | |
| 869 | + "price": "2660.00", | |
| 870 | + "compare_at_price": null, | |
| 871 | + "available": true, | |
| 872 | + "sku": "367783/3", | |
| 873 | + "option1": "Very Good | Item # 367783/3", | |
| 874 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 875 | + } | |
| 876 | + ], | |
| 877 | + "images": [ | |
| 878 | + { | |
| 879 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/367783-3_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1757098797" | |
| 880 | + }, | |
| 881 | + { | |
| 882 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/367783-3_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1757098797" | |
| 883 | + } | |
| 884 | + ], | |
| 885 | + "options": [ | |
| 886 | + { | |
| 887 | + "name": "Default Title" | |
| 888 | + }, | |
| 889 | + { | |
| 890 | + "name": "Color" | |
| 891 | + } | |
| 892 | + ] | |
| 893 | + }, | |
| 894 | + { | |
| 895 | + "id": 8823691313329, | |
| 896 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 897 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3803604", | |
| 898 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: corner wear, creases, scuffs, cracking | Handles/Straps: discoloration, minor cracking | Interior: discoloration, scuffs, minor stains | Hardware: scratches, minor tarnished Accessories: No Accessories Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue, Neutral, Pink Interior Material: Raw Leather Interior Color: Pink Hardware Color: Palladium Brand Code: C (2018) Item Number: 380360/4", | |
| 899 | + "published_at": "2025-09-30T00:57:09-04:00", | |
| 900 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 901 | + "vendor": "Hermes", | |
| 902 | + "product_type": "Bucket", | |
| 903 | + "tags": [ | |
| 904 | + "5000-to-10000", | |
| 905 | + "654", | |
| 906 | + "654A", | |
| 907 | + "all-bags", | |
| 908 | + "B2BDEALAUG", | |
| 909 | + "B2BDEALJUL", | |
| 910 | + "B2BDEALJUN", | |
| 911 | + "B2BDEALMAY", | |
| 912 | + "B2BDEALSEPT", | |
| 913 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 914 | + "bc-filter-$3\u201a000 and Up", | |
| 915 | + "bc-filter-Bags", | |
| 916 | + "bc-filter-Bucket", | |
| 917 | + "bc-filter-Clearance", | |
| 918 | + "bc-filter-dropship-partners-rebag", | |
| 919 | + "bc-filter-exterior-color-Blue", | |
| 920 | + "bc-filter-exterior-color-Neutral", | |
| 921 | + "bc-filter-exterior-color-Pink", | |
| 922 | + "bc-filter-exterior-material-Leather", | |
| 923 | + "bc-filter-General View", | |
| 924 | + "bc-filter-Great", | |
| 925 | + "bc-filter-interior-material-Raw Leather", | |
| 926 | + "bc-filter-Last Call", | |
| 927 | + "bc-filter-Miami - Brickell City Centre", | |
| 928 | + "bc-filter-Promo Eligible", | |
| 929 | + "bc-filter-Shoulder Bags", | |
| 930 | + "bc-filter-Up to 40% off", | |
| 931 | + "bloomingdales", | |
| 932 | + "blue", | |
| 933 | + "brickell", | |
| 934 | + "bucket", | |
| 935 | + "CL22", | |
| 936 | + "dynamic-feed-24", | |
| 937 | + "exterior-color-blue", | |
| 938 | + "exterior-color-neutral", | |
| 939 | + "exterior-color-pink", | |
| 940 | + "exterior-material-leather", | |
| 941 | + "great", | |
| 942 | + "handbag", | |
| 943 | + "hardware-color-palladium" | |
| 944 | + ], | |
| 945 | + "variants": [ | |
| 946 | + { | |
| 947 | + "id": 46272546930865, | |
| 948 | + "title": "Great | Item # 380360/4 / blue", | |
| 949 | + "price": "3155.00", | |
| 950 | + "compare_at_price": null, | |
| 951 | + "available": true, | |
| 952 | + "sku": "380360/4", | |
| 953 | + "option1": "Great | Item # 380360/4", | |
| 954 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 955 | + } | |
| 956 | + ], | |
| 957 | + "images": [ | |
| 958 | + { | |
| 959 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/380360-4_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1759167341" | |
| 960 | + }, | |
| 961 | + { | |
| 962 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/380360-4_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1759167341" | |
| 963 | + } | |
| 964 | + ], | |
| 965 | + "options": [ | |
| 966 | + { | |
| 967 | + "name": "Default Title" | |
| 968 | + }, | |
| 969 | + { | |
| 970 | + "name": "Color" | |
| 971 | + } | |
| 972 | + ] | |
| 973 | + }, | |
| 974 | + { | |
| 975 | + "id": 8212136951985, | |
| 976 | + "title": "Women's Manteau Duffle Coat Fur and Cotton", | |
| 977 | + "handle": "apparel-hermes-womens-manteau-duffle-coat-fur-and-cotton-313389186", | |
| 978 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Great. Minor wear, matting, and marks throughout, scratches on hardware. Accessories: Garment Bag, Hangers Measurements: Length 27\", Bust 33.0\" Designer: Hermes Model: Women's Manteau Duffle Coat Fur and Cotton Exterior Material: Cotton, Fur Exterior Color: Neutral, White Interior Material: Cotton, Leather Interior Color: Neutral, White Hardware Color: Palladium Brand Code: No Code Item Number: 313389/186", | |
| 979 | + "published_at": "2026-02-26T10:17:28-05:00", | |
| 980 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 981 | + "vendor": "Hermes", | |
| 982 | + "product_type": "Apparel", | |
| 983 | + "tags": [ | |
| 984 | + "1-500-to-3-000", | |
| 985 | + "2500-to-5000", | |
| 986 | + "543", | |
| 987 | + "543A", | |
| 988 | + "apparel", | |
| 989 | + "apparel-type-Coat", | |
| 990 | + "B2BDEALABR", | |
| 991 | + "B2BDEALAUG", | |
| 992 | + "B2BDEALDIC", | |
| 993 | + "B2BDEALJAN", | |
| 994 | + "B2BDEALJUL", | |
| 995 | + "B2BDEALJUN", | |
| 996 | + "B2BDEALMAR", | |
| 997 | + "B2BDEALMAY", | |
| 998 | + "B2BDEALNOV", | |
| 999 | + "B2BDEALOCT", | |
| 1000 | + "B2BDEALSEPT", | |
| 1001 | + "bc-filter-$1\u201a000 to $2\u201a500", | |
| 1002 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 1003 | + "bc-filter-$1\u201a500to$3\u201a000", | |
| 1004 | + "bc-filter-$2\u201a500to$5\u201a000", | |
| 1005 | + "bc-filter-Apparel", | |
| 1006 | + "bc-filter-apparel-type-Coat", | |
| 1007 | + "bc-filter-Clearance", | |
| 1008 | + "bc-filter-custom-size-apparel-XXS", | |
| 1009 | + "bc-filter-dropship-partners-rebag", | |
| 1010 | + "bc-filter-Excellent", | |
| 1011 | + "bc-filter-exterior-color-Neutral", | |
| 1012 | + "bc-filter-exterior-color-White", | |
| 1013 | + "bc-filter-exterior-material-Cotton", | |
| 1014 | + "bc-filter-exterior-material-Fur", | |
| 1015 | + "Bc-filter-FallEssentials", | |
| 1016 | + "bc-filter-gender-Women", | |
| 1017 | + "bc-filter-General View", | |
| 1018 | + "bc-filter-interior-material-Cotton", | |
| 1019 | + "bc-filter-interior-material-Leather", | |
| 1020 | + "bc-filter-Last Call", | |
| 1021 | + "bc-filter-NYC - Westfield World Trade Center", | |
| 1022 | + "bc-filter-Promo Eligible", | |
| 1023 | + "bc-filter-Up to 50% off" | |
| 1024 | + ], | |
| 1025 | + "variants": [ | |
| 1026 | + { | |
| 1027 | + "id": 45063753040049, | |
| 1028 | + "title": "Great | Item # 313389/186 / neutral", | |
| 1029 | + "price": "1530.00", | |
| 1030 | + "compare_at_price": null, | |
| 1031 | + "available": true, | |
| 1032 | + "sku": "313389/186", | |
| 1033 | + "option1": "Great | Item # 313389/186", | |
| 1034 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1035 | + } | |
| 1036 | + ], | |
| 1037 | + "images": [ | |
| 1038 | + { | |
| 1039 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/313389-186_20Hermes_20Women_s_20Manteau_20Duffle_20Coat_20Shearling_20and_20Cotton_2D_0002.jpg?v=1736799540" | |
| 1040 | + }, | |
| 1041 | + { | |
| 1042 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/313389-186_20Hermes_20Women_s_20Manteau_20Duffle_20Coat_20Shearling_20and_20Cotton_2D_0003.jpg?v=1736799540" | |
| 1043 | + } | |
| 1044 | + ], | |
| 1045 | + "options": [ | |
| 1046 | + { | |
| 1047 | + "name": "Default Title" | |
| 1048 | + }, | |
| 1049 | + { | |
| 1050 | + "name": "Color" | |
| 1051 | + } | |
| 1052 | + ] | |
| 1053 | + }, | |
| 1054 | + { | |
| 1055 | + "id": 8827301003441, | |
| 1056 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 1057 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3810703", | |
| 1058 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: cracking, minor scuffs, minor creases, minor corner wear | Handles/Straps: creases, minor scuffs, minor discoloration | Interior: discoloration, minor scuffs | Hardware: scratches, minor tarnished Accessories: Lock, Keys Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Blue, Orange, Red Interior Material: Raw Leather Interior Color: Orange Hardware Color: Palladium Brand Code: C (2018) Item Number: 381070/3", | |
| 1059 | + "published_at": "2025-10-04T03:52:45-04:00", | |
| 1060 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1061 | + "vendor": "Hermes", | |
| 1062 | + "product_type": "Bucket", | |
| 1063 | + "tags": [ | |
| 1064 | + "5000-to-10000", | |
| 1065 | + "656", | |
| 1066 | + "656A", | |
| 1067 | + "all-bags", | |
| 1068 | + "B2BDEALAUG", | |
| 1069 | + "B2BDEALJUL", | |
| 1070 | + "B2BDEALJUN", | |
| 1071 | + "B2BDEALMAY", | |
| 1072 | + "B2BDEALSEPT", | |
| 1073 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 1074 | + "bc-filter-$3\u201a000 and Up", | |
| 1075 | + "bc-filter-Bags", | |
| 1076 | + "bc-filter-Bloomingdale's Fashion Valley", | |
| 1077 | + "bc-filter-Bucket", | |
| 1078 | + "bc-filter-Clearance", | |
| 1079 | + "bc-filter-dropship-partners-rebag", | |
| 1080 | + "bc-filter-exterior-color-Blue", | |
| 1081 | + "bc-filter-exterior-color-Orange", | |
| 1082 | + "bc-filter-exterior-color-Red", | |
| 1083 | + "bc-filter-exterior-material-Leather", | |
| 1084 | + "bc-filter-General View", | |
| 1085 | + "bc-filter-Great", | |
| 1086 | + "bc-filter-interior-material-Raw Leather", | |
| 1087 | + "bc-filter-Last Call", | |
| 1088 | + "bc-filter-Promo Eligible", | |
| 1089 | + "bc-filter-Shoulder Bags", | |
| 1090 | + "bc-filter-Up to 40% off", | |
| 1091 | + "bl-fashion-valley", | |
| 1092 | + "bloomingdales", | |
| 1093 | + "blue", | |
| 1094 | + "bucket", | |
| 1095 | + "CL22", | |
| 1096 | + "dynamic-feed-24", | |
| 1097 | + "exterior-color-blue", | |
| 1098 | + "exterior-color-orange", | |
| 1099 | + "exterior-color-red", | |
| 1100 | + "exterior-material-leather", | |
| 1101 | + "great", | |
| 1102 | + "handbag", | |
| 1103 | + "hardware-color-palladium" | |
| 1104 | + ], | |
| 1105 | + "variants": [ | |
| 1106 | + { | |
| 1107 | + "id": 46282375725233, | |
| 1108 | + "title": "Great | Item # 381070/3 / blue", | |
| 1109 | + "price": "3295.00", | |
| 1110 | + "compare_at_price": null, | |
| 1111 | + "available": true, | |
| 1112 | + "sku": "381070/3", | |
| 1113 | + "option1": "Great | Item # 381070/3", | |
| 1114 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1115 | + } | |
| 1116 | + ], | |
| 1117 | + "images": [ | |
| 1118 | + { | |
| 1119 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/381070-3_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1759517732" | |
| 1120 | + }, | |
| 1121 | + { | |
| 1122 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/381070-3_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1759517732" | |
| 1123 | + } | |
| 1124 | + ], | |
| 1125 | + "options": [ | |
| 1126 | + { | |
| 1127 | + "name": "Default Title" | |
| 1128 | + }, | |
| 1129 | + { | |
| 1130 | + "name": "Color" | |
| 1131 | + } | |
| 1132 | + ] | |
| 1133 | + }, | |
| 1134 | + { | |
| 1135 | + "id": 8829395861681, | |
| 1136 | + "title": "Evelyne Bag Gen III Clemence TPM", | |
| 1137 | + "handle": "handbags-hermes-evelyne-bag-gen-iii-clemence-tpm3799161", | |
| 1138 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: cracking, minor scuffs, minor edge wear | Interior: scuffs, discoloration | Hardware: scratches, tarnished Accessories: Dust Bag, With Strap Measurements: Handle Drop 19.5-27.5\", Height 7\", Width 6.5\", Depth 2\", Strap Drop 23\" Designer: Hermes Model: Evelyne Bag Gen III Clemence TPM Exterior Material: Leather Exterior Color: Black Interior Material: Raw Leather Interior Color: Black Hardware Color: Palladium Brand Code: Y (2020) Item Number: 379916/1", | |
| 1139 | + "published_at": "2026-06-04T16:10:08-04:00", | |
| 1140 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1141 | + "vendor": "Hermes", | |
| 1142 | + "product_type": "Cross body bags", | |
| 1143 | + "tags": [ | |
| 1144 | + "20OFFSTORE", | |
| 1145 | + "2500-to-5000", | |
| 1146 | + "658", | |
| 1147 | + "658A", | |
| 1148 | + "all-bags", | |
| 1149 | + "B2BDEALAUG", | |
| 1150 | + "B2BDEALJUL", | |
| 1151 | + "B2BDEALJUN", | |
| 1152 | + "B2BDEALMAR", | |
| 1153 | + "B2BDEALSEPT", | |
| 1154 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 1155 | + "bc-filter-$3\u201a000 and Up", | |
| 1156 | + "bc-filter-Bags", | |
| 1157 | + "bc-filter-Clearance", | |
| 1158 | + "bc-filter-Cross Body Bags", | |
| 1159 | + "bc-filter-dropship-partners-rebag", | |
| 1160 | + "bc-filter-exterior-color-Black", | |
| 1161 | + "bc-filter-exterior-material-Leather", | |
| 1162 | + "bc-filter-General View", | |
| 1163 | + "bc-filter-Great", | |
| 1164 | + "bc-filter-interior-material-Raw Leather", | |
| 1165 | + "bc-filter-Last Call", | |
| 1166 | + "bc-filter-Miami - Brickell City Centre", | |
| 1167 | + "bc-filter-Promo Eligible", | |
| 1168 | + "bc-filter-Shoulder Bags", | |
| 1169 | + "bc-filter-Up to 30% off", | |
| 1170 | + "black", | |
| 1171 | + "bloomingdales", | |
| 1172 | + "brickell", | |
| 1173 | + "CL22", | |
| 1174 | + "dust-bag", | |
| 1175 | + "dynamic-feed-24", | |
| 1176 | + "exterior-color-black", | |
| 1177 | + "exterior-material-leather", | |
| 1178 | + "great", | |
| 1179 | + "handbag", | |
| 1180 | + "hardware-color-palladium", | |
| 1181 | + "hermes", | |
| 1182 | + "in-stock", | |
| 1183 | + "interior-color-black" | |
| 1184 | + ], | |
| 1185 | + "variants": [ | |
| 1186 | + { | |
| 1187 | + "id": 46288920182961, | |
| 1188 | + "title": "Great | Item # 379916/1 / black", | |
| 1189 | + "price": "3080.00", | |
| 1190 | + "compare_at_price": null, | |
| 1191 | + "available": true, | |
| 1192 | + "sku": "379916/1", | |
| 1193 | + "option1": "Great | Item # 379916/1", | |
| 1194 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1195 | + } | |
| 1196 | + ], | |
| 1197 | + "images": [ | |
| 1198 | + { | |
| 1199 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/379916-1_20Hermes_20Evelyne_20Bag_20Gen_20III_20Clemence_20TPM_2D_0002.jpg?v=1759949171" | |
| 1200 | + }, | |
| 1201 | + { | |
| 1202 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/379916-1_20Hermes_20Evelyne_20Bag_20Gen_20III_20Clemence_20TPM_2D_0003.jpg?v=1759949171" | |
| 1203 | + } | |
| 1204 | + ], | |
| 1205 | + "options": [ | |
| 1206 | + { | |
| 1207 | + "name": "Default Title" | |
| 1208 | + }, | |
| 1209 | + { | |
| 1210 | + "name": "Color" | |
| 1211 | + } | |
| 1212 | + ] | |
| 1213 | + }, | |
| 1214 | + { | |
| 1215 | + "id": 8837533565105, | |
| 1216 | + "title": "Evelyne Bag Gen III Clemence TPM", | |
| 1217 | + "handle": "handbags-hermes-evelyne-bag-gen-iii-clemence-tpm3808563", | |
| 1218 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: creases, cracking, edge wear, minor scuffs | Handles/Straps: minor discoloration | Interior: scuffs, discoloration | Hardware: scratches Accessories: With Strap Measurements: Handle Drop 19.5-27.5\", Height 7\", Width 6.5\", Depth 2\", Strap Drop 23\" Designer: Hermes Model: Evelyne Bag Gen III Clemence TPM Exterior Material: Leather Exterior Color: Green Interior Material: Raw Leather Interior Color: Green Hardware Color: Palladium Brand Code: D (2019) Item Number: 380856/3", | |
| 1219 | + "published_at": "2025-10-11T03:57:04-04:00", | |
| 1220 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1221 | + "vendor": "Hermes", | |
| 1222 | + "product_type": "Cross body bags", | |
| 1223 | + "tags": [ | |
| 1224 | + "1-500-to-3-000", | |
| 1225 | + "20OFFSTORE", | |
| 1226 | + "5000-to-10000", | |
| 1227 | + "659", | |
| 1228 | + "659A", | |
| 1229 | + "all-bags", | |
| 1230 | + "B2BDEALAUG", | |
| 1231 | + "B2BDEALJUL", | |
| 1232 | + "B2BDEALJUN", | |
| 1233 | + "B2BDEALMAR", | |
| 1234 | + "B2BDEALMAY", | |
| 1235 | + "B2BDEALSEPT", | |
| 1236 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 1237 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 1238 | + "bc-filter-Bags", | |
| 1239 | + "bc-filter-Bloomingdale's Fashion Valley", | |
| 1240 | + "bc-filter-Clearance", | |
| 1241 | + "bc-filter-Cross Body Bags", | |
| 1242 | + "bc-filter-dropship-partners-rebag", | |
| 1243 | + "bc-filter-exterior-color-Green", | |
| 1244 | + "bc-filter-exterior-material-Leather", | |
| 1245 | + "bc-filter-General View", | |
| 1246 | + "bc-filter-Great", | |
| 1247 | + "bc-filter-interior-material-Raw Leather", | |
| 1248 | + "bc-filter-Last Call", | |
| 1249 | + "bc-filter-Promo Eligible", | |
| 1250 | + "bc-filter-Shoulder Bags", | |
| 1251 | + "bc-filter-Up to 50% off", | |
| 1252 | + "bl-fashion-valley", | |
| 1253 | + "bloomingdales", | |
| 1254 | + "CL22", | |
| 1255 | + "cross-body-bags", | |
| 1256 | + "dynamic-feed-24", | |
| 1257 | + "exterior-color-green", | |
| 1258 | + "exterior-material-leather", | |
| 1259 | + "great", | |
| 1260 | + "green", | |
| 1261 | + "handbag", | |
| 1262 | + "hardware-color-palladium", | |
| 1263 | + "hermes" | |
| 1264 | + ], | |
| 1265 | + "variants": [ | |
| 1266 | + { | |
| 1267 | + "id": 46299299578033, | |
| 1268 | + "title": "Great | Item # 380856/3 / green", | |
| 1269 | + "price": "2620.00", | |
| 1270 | + "compare_at_price": null, | |
| 1271 | + "available": true, | |
| 1272 | + "sku": "380856/3", | |
| 1273 | + "option1": "Great | Item # 380856/3", | |
| 1274 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1275 | + } | |
| 1276 | + ], | |
| 1277 | + "images": [ | |
| 1278 | + { | |
| 1279 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/380856-3_20Hermes_20Evelyne_20Bag_20Gen_20III_20Clemence_20TPM_2D_0002.jpg?v=1760121549" | |
| 1280 | + }, | |
| 1281 | + { | |
| 1282 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/380856-3_20Hermes_20Evelyne_20Bag_20Gen_20III_20Clemence_20TPM_2D_0003.jpg?v=1760121549" | |
| 1283 | + } | |
| 1284 | + ], | |
| 1285 | + "options": [ | |
| 1286 | + { | |
| 1287 | + "name": "Default Title" | |
| 1288 | + }, | |
| 1289 | + { | |
| 1290 | + "name": "Color" | |
| 1291 | + } | |
| 1292 | + ] | |
| 1293 | + }, | |
| 1294 | + { | |
| 1295 | + "id": 8837534187697, | |
| 1296 | + "title": "Evelyne Bag Gen III Clemence TPM", | |
| 1297 | + "handle": "handbags-hermes-evelyne-bag-gen-iii-clemence-tpm3805661", | |
| 1298 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Good. Exterior: creases, edge wear, corner wear, minor scuffs, minor cracking | Interior: scuffs, stains, discoloration | Hardware: scratches Accessories: Dust Bag, With Strap Measurements: Handle Drop 19.5-27.5\", Height 7\", Width 6.5\", Depth 2\", Strap Drop 23\" Designer: Hermes Model: Evelyne Bag Gen III Clemence TPM Exterior Material: Leather Exterior Color: Gray Interior Material: Raw Leather Interior Color: Gray Hardware Color: Palladium Brand Code: Y (2020) Item Number: 380566/1", | |
| 1299 | + "published_at": "2025-10-11T03:56:29-04:00", | |
| 1300 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1301 | + "vendor": "Hermes", | |
| 1302 | + "product_type": "Cross body bags", | |
| 1303 | + "tags": [ | |
| 1304 | + "1-500-to-3-000", | |
| 1305 | + "20OFFSTORE", | |
| 1306 | + "2500-to-5000", | |
| 1307 | + "659", | |
| 1308 | + "659A", | |
| 1309 | + "all-bags", | |
| 1310 | + "B2BDEALAUG", | |
| 1311 | + "B2BDEALJUL", | |
| 1312 | + "B2BDEALJUN", | |
| 1313 | + "B2BDEALMAR", | |
| 1314 | + "B2BDEALMAY", | |
| 1315 | + "B2BDEALSEPT", | |
| 1316 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 1317 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 1318 | + "bc-filter-Bags", | |
| 1319 | + "bc-filter-Bloomingdale's Fashion Valley", | |
| 1320 | + "bc-filter-Clearance", | |
| 1321 | + "bc-filter-Cross Body Bags", | |
| 1322 | + "bc-filter-dropship-partners-rebag", | |
| 1323 | + "bc-filter-exterior-color-Gray", | |
| 1324 | + "bc-filter-exterior-material-Leather", | |
| 1325 | + "bc-filter-General View", | |
| 1326 | + "bc-filter-interior-material-Raw Leather", | |
| 1327 | + "bc-filter-Last Call", | |
| 1328 | + "bc-filter-Promo Eligible", | |
| 1329 | + "bc-filter-Shoulder Bags", | |
| 1330 | + "bc-filter-Up to 40% off", | |
| 1331 | + "bc-filter-Very Good", | |
| 1332 | + "bl-fashion-valley", | |
| 1333 | + "bloomingdales", | |
| 1334 | + "CL22", | |
| 1335 | + "cross-body-bags", | |
| 1336 | + "dust-bag", | |
| 1337 | + "dynamic-feed-24", | |
| 1338 | + "exterior-color-gray", | |
| 1339 | + "exterior-material-leather", | |
| 1340 | + "good", | |
| 1341 | + "gray", | |
| 1342 | + "handbag", | |
| 1343 | + "hardware-color-palladium" | |
| 1344 | + ], | |
| 1345 | + "variants": [ | |
| 1346 | + { | |
| 1347 | + "id": 46299300266161, | |
| 1348 | + "title": "Very Good | Item # 380566/1 / gray", | |
| 1349 | + "price": "2960.00", | |
| 1350 | + "compare_at_price": null, | |
| 1351 | + "available": true, | |
| 1352 | + "sku": "380566/1", | |
| 1353 | + "option1": "Very Good | Item # 380566/1", | |
| 1354 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1355 | + } | |
| 1356 | + ], | |
| 1357 | + "images": [ | |
| 1358 | + { | |
| 1359 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/380566-1_20Hermes_20Evelyne_20Bag_20Gen_20III_20Clemence_20TPM_2D_0002.jpg?v=1760121612" | |
| 1360 | + }, | |
| 1361 | + { | |
| 1362 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/380566-1_20Hermes_20Evelyne_20Bag_20Gen_20III_20Clemence_20TPM_2D_0003.jpg?v=1760121612" | |
| 1363 | + } | |
| 1364 | + ], | |
| 1365 | + "options": [ | |
| 1366 | + { | |
| 1367 | + "name": "Default Title" | |
| 1368 | + }, | |
| 1369 | + { | |
| 1370 | + "name": "Color" | |
| 1371 | + } | |
| 1372 | + ] | |
| 1373 | + }, | |
| 1374 | + { | |
| 1375 | + "id": 8839399080113, | |
| 1376 | + "title": "Picotin Lock Bag Tressage Epsom MM", | |
| 1377 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-mm3836001", | |
| 1378 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Very good. Exterior: scuffs, creases, corner wear, minor cracking, minor edge wear | Handles/Straps: minor scuffs, minor creases | Interior: scuffs | Hardware: scratches Accessories: Lock, Dust Bag Measurements: Handle Drop 6\", Height 8.5\", Width 8.5\", Depth 7\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom MM Exterior Material: Leather Exterior Color: Blue, Orange, Red Interior Material: Raw Leather Interior Color: Orange Hardware Color: Palladium Brand Code: D (2019) Item Number: 383600/1", | |
| 1379 | + "published_at": "2025-10-14T04:00:14-04:00", | |
| 1380 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1381 | + "vendor": "Hermes", | |
| 1382 | + "product_type": "Totes", | |
| 1383 | + "tags": [ | |
| 1384 | + "1-500-to-3-000", | |
| 1385 | + "2500-to-5000", | |
| 1386 | + "660", | |
| 1387 | + "660A", | |
| 1388 | + "all-bags", | |
| 1389 | + "B2BDEALAUG", | |
| 1390 | + "B2BDEALJUL", | |
| 1391 | + "B2BDEALJUN", | |
| 1392 | + "B2BDEALMAY", | |
| 1393 | + "B2BDEALSEPT", | |
| 1394 | + "bc-filter-$1\u201a500 to $3\u201a000", | |
| 1395 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 1396 | + "bc-filter-Bags", | |
| 1397 | + "bc-filter-Bloomingdale's Short Hills", | |
| 1398 | + "bc-filter-Clearance", | |
| 1399 | + "bc-filter-dropship-partners-rebag", | |
| 1400 | + "bc-filter-exterior-color-Blue", | |
| 1401 | + "bc-filter-exterior-color-Orange", | |
| 1402 | + "bc-filter-exterior-color-Red", | |
| 1403 | + "bc-filter-exterior-material-Leather", | |
| 1404 | + "bc-filter-General View", | |
| 1405 | + "bc-filter-Great", | |
| 1406 | + "bc-filter-interior-material-Raw Leather", | |
| 1407 | + "bc-filter-Last Call", | |
| 1408 | + "bc-filter-Promo Eligible", | |
| 1409 | + "bc-filter-Totes", | |
| 1410 | + "bc-filter-Up to 30% off", | |
| 1411 | + "bl-short-hills", | |
| 1412 | + "bloomingdales", | |
| 1413 | + "blue", | |
| 1414 | + "CL22", | |
| 1415 | + "dust-bag", | |
| 1416 | + "dynamic-feed-24", | |
| 1417 | + "exterior-color-blue", | |
| 1418 | + "exterior-color-orange", | |
| 1419 | + "exterior-color-red", | |
| 1420 | + "exterior-material-leather", | |
| 1421 | + "great", | |
| 1422 | + "handbag", | |
| 1423 | + "hardware-color-palladium" | |
| 1424 | + ], | |
| 1425 | + "variants": [ | |
| 1426 | + { | |
| 1427 | + "id": 46302687822001, | |
| 1428 | + "title": "Great | Item # 383600/1 / blue", | |
| 1429 | + "price": "2875.00", | |
| 1430 | + "compare_at_price": null, | |
| 1431 | + "available": true, | |
| 1432 | + "sku": "383600/1", | |
| 1433 | + "option1": "Great | Item # 383600/1", | |
| 1434 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1435 | + } | |
| 1436 | + ], | |
| 1437 | + "images": [ | |
| 1438 | + { | |
| 1439 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/383600-1_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20MM_2D_0002.jpg?v=1760372355" | |
| 1440 | + }, | |
| 1441 | + { | |
| 1442 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/383600-1_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20MM_2D_0003.jpg?v=1760372355" | |
| 1443 | + } | |
| 1444 | + ], | |
| 1445 | + "options": [ | |
| 1446 | + { | |
| 1447 | + "name": "Default Title" | |
| 1448 | + }, | |
| 1449 | + { | |
| 1450 | + "name": "Color" | |
| 1451 | + } | |
| 1452 | + ] | |
| 1453 | + }, | |
| 1454 | + { | |
| 1455 | + "id": 8839402258609, | |
| 1456 | + "title": "Epure Equestre Earrings Leather with Metal Large", | |
| 1457 | + "handle": "earrings-hermes-epure-equestre-earrings-leather-with-metal-large378677183", | |
| 1458 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Excellent. Light scuffs and scratches throughout. Accessories: No Accessories Measurements: Height 1.6\", Width 1.87\" Designer: Hermes Model: Epure Equestre Earrings Leather with Metal Large Exterior Color: Black, Gold Brand Code: W (2024) Item Number: 378677/183", | |
| 1459 | + "published_at": "2025-10-14T03:58:49-04:00", | |
| 1460 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1461 | + "vendor": "Hermes", | |
| 1462 | + "product_type": "Earrings", | |
| 1463 | + "tags": [ | |
| 1464 | + "1000-to-2500", | |
| 1465 | + "500-to-1-500", | |
| 1466 | + "660", | |
| 1467 | + "660A", | |
| 1468 | + "B2BDEALAUG", | |
| 1469 | + "B2BDEALJUL", | |
| 1470 | + "B2BDEALJUN", | |
| 1471 | + "B2BDEALMAY", | |
| 1472 | + "B2BDEALSEPT", | |
| 1473 | + "bc-filter-$500 to $1\u201a500", | |
| 1474 | + "bc-filter-Bloomingdale's Fashion Valley", | |
| 1475 | + "bc-filter-Clearance", | |
| 1476 | + "bc-filter-dropship-partners-rebag", | |
| 1477 | + "bc-filter-exterior-color-Black", | |
| 1478 | + "bc-filter-exterior-color-Gold", | |
| 1479 | + "bc-filter-exterior-material-Leather", | |
| 1480 | + "bc-filter-exterior-material-Metal", | |
| 1481 | + "bc-filter-General View", | |
| 1482 | + "bc-filter-Jewelry", | |
| 1483 | + "bc-filter-jewelry-type-Earrings", | |
| 1484 | + "bc-filter-Last Call", | |
| 1485 | + "bc-filter-Pristine", | |
| 1486 | + "bc-filter-Promo Eligible", | |
| 1487 | + "bc-filter-Under $1\u201a000", | |
| 1488 | + "bc-filter-Up to 40% off", | |
| 1489 | + "bl-fashion-valley", | |
| 1490 | + "black", | |
| 1491 | + "bloomingdales", | |
| 1492 | + "CL22", | |
| 1493 | + "dynamic-feed-24", | |
| 1494 | + "earrings", | |
| 1495 | + "exterior-color-black", | |
| 1496 | + "exterior-color-gold", | |
| 1497 | + "exterior-material-leather", | |
| 1498 | + "exterior-material-metal", | |
| 1499 | + "gold", | |
| 1500 | + "hermes", | |
| 1501 | + "in-stock", | |
| 1502 | + "item-type-jewelry", | |
| 1503 | + "jewelry" | |
| 1504 | + ], | |
| 1505 | + "variants": [ | |
| 1506 | + { | |
| 1507 | + "id": 46302693195953, | |
| 1508 | + "title": "Excellent | Item # 378677/183 / black", | |
| 1509 | + "price": "910.00", | |
| 1510 | + "compare_at_price": null, | |
| 1511 | + "available": true, | |
| 1512 | + "sku": "378677/183", | |
| 1513 | + "option1": "Excellent | Item # 378677/183", | |
| 1514 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1515 | + } | |
| 1516 | + ], | |
| 1517 | + "images": [ | |
| 1518 | + { | |
| 1519 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/378677-183_20Hermes_20Epure_20Equestre_20Earrings_20Leather_20with_20Metal_20Large_2D_0002.jpg?v=1760372649" | |
| 1520 | + }, | |
| 1521 | + { | |
| 1522 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/378677-183_20Hermes_20Epure_20Equestre_20Earrings_20Leather_20with_20Metal_20Large_2D_0003.jpg?v=1760372649" | |
| 1523 | + } | |
| 1524 | + ], | |
| 1525 | + "options": [ | |
| 1526 | + { | |
| 1527 | + "name": "Default Title" | |
| 1528 | + }, | |
| 1529 | + { | |
| 1530 | + "name": "Color" | |
| 1531 | + } | |
| 1532 | + ] | |
| 1533 | + }, | |
| 1534 | + { | |
| 1535 | + "id": 8840372256945, | |
| 1536 | + "title": "Picotin Lock Bag Tressage Epsom PM", | |
| 1537 | + "handle": "handbags-hermes-picotin-lock-bag-tressage-epsom-pm3762149", | |
| 1538 | + "body_html": "These are professional pictures of the actual item offered by Rebag. Condition: Good. Exterior: scuffs, creases, cracking, corner wear, minor edge wear | Handles/Straps: scuffs, creases, cracking, discoloration | Interior: moderate odor, scuffs, discoloration | Hardware: scratches, tarnished Accessories: Lock, Dust Bag, Keys Measurements: Handle Drop 5\", Height 7\", Width 7\", Depth 5\", Strap Drop None\" Designer: Hermes Model: Picotin Lock Bag Tressage Epsom PM Exterior Material: Leather Exterior Color: Black, Blue, Brown Interior Material: Raw Leather Interior Color: Blue Hardware Color: Palladium Brand Code: C (2018) Item Number: 376214/9", | |
| 1539 | + "published_at": "2025-10-16T03:44:02-04:00", | |
| 1540 | + "updated_at": "2026-09-07T02:28:33-04:00", | |
| 1541 | + "vendor": "Hermes", | |
| 1542 | + "product_type": "Bucket", | |
| 1543 | + "tags": [ | |
| 1544 | + "5000-to-10000", | |
| 1545 | + "661", | |
| 1546 | + "661A", | |
| 1547 | + "all-bags", | |
| 1548 | + "B2BDEALAUG", | |
| 1549 | + "B2BDEALJUL", | |
| 1550 | + "B2BDEALJUN", | |
| 1551 | + "B2BDEALMAY", | |
| 1552 | + "B2BDEALSEPT", | |
| 1553 | + "bc-filter-$2\u201a500 to $5\u201a000", | |
| 1554 | + "bc-filter-$3\u201a000 and Up", | |
| 1555 | + "bc-filter-Bags", | |
| 1556 | + "bc-filter-Bloomingdale's Orlando", | |
| 1557 | + "bc-filter-Bucket", | |
| 1558 | + "bc-filter-Clearance", | |
| 1559 | + "bc-filter-dropship-partners-rebag", | |
| 1560 | + "bc-filter-exterior-color-Black", | |
| 1561 | + "bc-filter-exterior-color-Blue", | |
| 1562 | + "bc-filter-exterior-color-Brown", | |
| 1563 | + "bc-filter-exterior-material-Leather", | |
| 1564 | + "bc-filter-General View", | |
| 1565 | + "bc-filter-interior-material-Raw Leather", | |
| 1566 | + "bc-filter-Last Call", | |
| 1567 | + "bc-filter-Promo Eligible", | |
| 1568 | + "bc-filter-Shoulder Bags", | |
| 1569 | + "bc-filter-Up to 40% off", | |
| 1570 | + "bc-filter-Very Good", | |
| 1571 | + "bl-orlando-fl", | |
| 1572 | + "black", | |
| 1573 | + "bloomingdales", | |
| 1574 | + "blue", | |
| 1575 | + "brown", | |
| 1576 | + "bucket", | |
| 1577 | + "CL22", | |
| 1578 | + "dust-bag", | |
| 1579 | + "dynamic-feed-24", | |
| 1580 | + "exterior-color-black", | |
| 1581 | + "exterior-color-blue", | |
| 1582 | + "exterior-color-brown", | |
| 1583 | + "exterior-material-leather" | |
| 1584 | + ], | |
| 1585 | + "variants": [ | |
| 1586 | + { | |
| 1587 | + "id": 46307253977265, | |
| 1588 | + "title": "Very Good | Item # 376214/9 / black", | |
| 1589 | + "price": "3255.00", | |
| 1590 | + "compare_at_price": null, | |
| 1591 | + "available": true, | |
| 1592 | + "sku": "376214/9", | |
| 1593 | + "option1": "Very Good | Item # 376214/9", | |
| 1594 | + "updated_at": "2026-09-07T02:28:33-04:00" | |
| 1595 | + } | |
| 1596 | + ], | |
| 1597 | + "images": [ | |
| 1598 | + { | |
| 1599 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/376214-9_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0002.jpg?v=1760557744" | |
| 1600 | + }, | |
| 1601 | + { | |
| 1602 | + "src": "https://cdn.shopify.com/s/files/1/0384/0161/files/376214-9_20Hermes_20Picotin_20Lock_20Bag_20Tressage_20Epsom_20PM_2D_0003.jpg?v=1760557744" | |
| 1603 | + } | |
| 1604 | + ], | |
| 1605 | + "options": [ | |
| 1606 | + { | |
| 1607 | + "name": "Default Title" | |
| 1608 | + }, | |
| 1609 | + { | |
| 1610 | + "name": "Color" | |
| 1611 | + } | |
| 1612 | + ] | |
| 1613 | + } | |
| 1614 | + ] | |
| 1615 | + } | |
| 1616 | + }, | |
| 1617 | + "expect": { | |
| 1618 | + "minCount": 1, | |
| 1619 | + "kinds": [ | |
| 1620 | + "listing" | |
| 1621 | + ] | |
| 1622 | + }, | |
| 1623 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts; trimmed to 25 products for size", | |
| 1624 | + "capturedAt": "2026-09-07T06:28:33.871Z" | |
| 1625 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/subdial/sd092678.json
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://subdial.com/listing/grone-moment-meter-sd092678", | |
| 4 | + "externalId": "SD092678", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:45.383Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "listing_page", | |
| 10 | + "url": "https://subdial.com/listing/grone-moment-meter-sd092678", | |
| 11 | + "name": "Grone Moment Meter 2026 Original Box & Papers", | |
| 12 | + "brand": "Grone", | |
| 13 | + "mpn": "Moment Meter", | |
| 14 | + "sku": "SD092678", | |
| 15 | + "price": 4450, | |
| 16 | + "currency": "GBP", | |
| 17 | + "availability": "SoldOut", | |
| 18 | + "images": [ | |
| 19 | + "https://api.subdial.com/vault/kkU27edn/s-TGiOZ1LEKL5etO84zIXN_whPA/Kv3GRg../SD092678%252FSD092678_GRONE_THUMBNAIL_39MM_ORDER1.webp", | |
| 20 | + "https://api.subdial.com/vault/kkU27edn/hGL6VwWoMYJArWDNpPM7is1Nfp8/3zDfQQ../SD092678%252FSD092678_GRONE_LIFESTYLE__ORDER2.webp", | |
| 21 | + "https://api.subdial.com/vault/kkU27edn/0Zbq5ocMyj4yMxMVHzcvb8udd_Q/QbuFuA../SD092678%252FSD092678_GRONE_FULL_LENGTH_39MM_ORDER3.webp" | |
| 22 | + ], | |
| 23 | + "description": "Grone Moment Meter 2026 Original Box & Papers · Ref. Moment Meter · 2026 · £4,450. Inspected, authenticated and sold by Subdial.", | |
| 24 | + "specs": { | |
| 25 | + "papers": "2026", | |
| 26 | + "reference": "Moment Meter", | |
| 27 | + "year": "2026", | |
| 28 | + "movement": "Manual Wind", | |
| 29 | + "case material": "Stainless Steel", | |
| 30 | + "condition": "Very Good" | |
| 31 | + } | |
| 32 | + } | |
| 33 | + }, | |
| 34 | + "expect": { | |
| 35 | + "minCount": 1, | |
| 36 | + "kinds": [ | |
| 37 | + "listing" | |
| 38 | + ] | |
| 39 | + }, | |
| 40 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 41 | + "capturedAt": "2026-09-07T06:28:45.395Z" | |
| 42 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/subdial/sd092916.json
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://subdial.com/listing/cartier-santos-dumont-lacquer-stainless-steel-black-sd092916", | |
| 4 | + "externalId": "SD092916", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:43.776Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "listing_page", | |
| 10 | + "url": "https://subdial.com/listing/cartier-santos-dumont-lacquer-stainless-steel-black-sd092916", | |
| 11 | + "name": "Cartier Santos Dumont Lacquer WSSA0046 2023 Original Box & Papers", | |
| 12 | + "brand": "Cartier", | |
| 13 | + "mpn": "WSSA0046", | |
| 14 | + "sku": "SD092916", | |
| 15 | + "price": 5650, | |
| 16 | + "currency": "GBP", | |
| 17 | + "availability": "InStock", | |
| 18 | + "images": [ | |
| 19 | + "https://api.subdial.com/vault/kkU27edn/neyLFrIebas-658fbTd6N5_7K2g/GQtd7w../SD092916%252FSD092916_CARTIER_THUMBNAIL_43.5MM_ORDER1.webp", | |
| 20 | + "https://api.subdial.com/vault/kkU27edn/1hPzwbyJQQdjS_zrJ46SNdyUg5U/Ropa1A../SD092916%252FSD092916_CARTIER_LIFESTYLE_001_ORDER2.webp", | |
| 21 | + "https://api.subdial.com/vault/kkU27edn/AjgVrCUtVCKhoiNeRdxwEzBJu8I/hxFTwQ../SD092916%252FSD092916_CARTIER_FULL_LENGTH_43.5MM_ORDER3.webp" | |
| 22 | + ], | |
| 23 | + "description": "Cartier Santos Dumont Lacquer WSSA0046 2023 Original Box & Papers · Ref. WSSA0046 · 2023 · £5,650. Inspected, authenticated and sold by Subdial.", | |
| 24 | + "specs": { | |
| 25 | + "papers": "2023", | |
| 26 | + "reference": "WSSA0046", | |
| 27 | + "year": "2023", | |
| 28 | + "movement": "Manual Wind", | |
| 29 | + "case material": "Stainless Steel", | |
| 30 | + "condition": "Excellent" | |
| 31 | + } | |
| 32 | + } | |
| 33 | + }, | |
| 34 | + "expect": { | |
| 35 | + "minCount": 1, | |
| 36 | + "kinds": [ | |
| 37 | + "listing" | |
| 38 | + ] | |
| 39 | + }, | |
| 40 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 41 | + "capturedAt": "2026-09-07T06:28:43.786Z" | |
| 42 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/subdial/sd093236.json
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://subdial.com/listing/jaeger-lecoultre-reverso-tribute-enamel-sd093236", | |
| 4 | + "externalId": "SD093236", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "api", | |
| 7 | + "fetchedAt": "2026-09-07T06:28:42.113Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "listing_page", | |
| 10 | + "url": "https://subdial.com/listing/jaeger-lecoultre-reverso-tribute-enamel-sd093236", | |
| 11 | + "name": "Jaeger-LeCoultre Reverso Tribute Enamel Xu Beihong Q39334C1 2018 Original Box & Papers", | |
| 12 | + "brand": "Jaeger-LeCoultre", | |
| 13 | + "mpn": "Q39334C1", | |
| 14 | + "sku": "SD093236", | |
| 15 | + "price": 75000, | |
| 16 | + "currency": "GBP", | |
| 17 | + "availability": "InStock", | |
| 18 | + "images": [ | |
| 19 | + "https://api.subdial.com/vault/kkU27edn/3Die8gjQuACKtsvRlD0TLxzIPyo/aJa7fA../SD093236%252FSD093236_JAEGER-LECOULTRE_THUMBNAIL_27.4MM_ORDER1.webp", | |
| 20 | + "https://api.subdial.com/vault/kkU27edn/48uq6HzSeZ3czBwD49IIo7pPX_o/qDkTvQ../SD093236%252FSD093236_JAEGER-LECOULTRE_LIFESTYLE_001_ORDER2.webp", | |
| 21 | + "https://api.subdial.com/vault/kkU27edn/jTGNCTYUtLt2kcK-CSgau9h1Ugw/1WsKxw../SD093236%252FSD093236_JAEGER-LECOULTRE_FULL_LENGTH_27.4MM_ORDER3.webp" | |
| 22 | + ], | |
| 23 | + "description": "Jaeger-LeCoultre Reverso Tribute Enamel Xu Beihong Q39334C1 2018 Original Box & Papers · Ref. Q39334C1 · 2018 · £75,000. Inspected, authenticated and sold by…", | |
| 24 | + "specs": { | |
| 25 | + "papers": "2018", | |
| 26 | + "reference": "Q39334C1", | |
| 27 | + "year": "2018", | |
| 28 | + "movement": "Manual Wind", | |
| 29 | + "case material": "White Gold", | |
| 30 | + "condition": "Very Good" | |
| 31 | + } | |
| 32 | + } | |
| 33 | + }, | |
| 34 | + "expect": { | |
| 35 | + "minCount": 1, | |
| 36 | + "kinds": [ | |
| 37 | + "listing" | |
| 38 | + ] | |
| 39 | + }, | |
| 40 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 41 | + "capturedAt": "2026-09-07T06:28:42.143Z" | |
| 42 | +} | |
| \ No newline at end of file | ||
added
data/fixtures/watchfinder/model-rolex-daytona-1.json
+614 −0
@@ -0,0 +1,614 @@ | ||
| 1 | +{ | |
| 2 | + "raw": { | |
| 3 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona", | |
| 4 | + "externalId": "model:rolex/daytona:1", | |
| 5 | + "kind": "listing", | |
| 6 | + "engine": "firecrawl", | |
| 7 | + "fetchedAt": "2026-09-07T06:29:23.081Z", | |
| 8 | + "payload": { | |
| 9 | + "kind": "model_page", | |
| 10 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona", | |
| 11 | + "seed": "rolex/daytona", | |
| 12 | + "page": 1, | |
| 13 | + "total": null, | |
| 14 | + "cards": [ | |
| 15 | + { | |
| 16 | + "sku": "440071", | |
| 17 | + "brand": "Rolex", | |
| 18 | + "series": "Daytona", | |
| 19 | + "model": "116503", | |
| 20 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116503/440071", | |
| 21 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116503-440071-260902-142247525.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 22 | + "name": "Rolex Daytona 116503", | |
| 23 | + "box": true, | |
| 24 | + "papers": true, | |
| 25 | + "year": 2020, | |
| 26 | + "price": 16950, | |
| 27 | + "currency": "GBP" | |
| 28 | + }, | |
| 29 | + { | |
| 30 | + "sku": "437096", | |
| 31 | + "brand": "Rolex", | |
| 32 | + "series": "Daytona", | |
| 33 | + "model": "116520", | |
| 34 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116520/437096", | |
| 35 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116520-437096-260828-154835036.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 36 | + "name": "Rolex Daytona 116520", | |
| 37 | + "box": true, | |
| 38 | + "papers": true, | |
| 39 | + "year": 2002, | |
| 40 | + "price": 17500, | |
| 41 | + "currency": "GBP" | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "sku": "437564", | |
| 45 | + "brand": "Rolex", | |
| 46 | + "series": "Daytona", | |
| 47 | + "model": "116519", | |
| 48 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/437564", | |
| 49 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-437564-260820-104702589.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 50 | + "name": "Rolex Daytona 116519", | |
| 51 | + "box": false, | |
| 52 | + "papers": true, | |
| 53 | + "year": 2005, | |
| 54 | + "price": 21000, | |
| 55 | + "currency": "GBP" | |
| 56 | + }, | |
| 57 | + { | |
| 58 | + "sku": "435588", | |
| 59 | + "brand": "Rolex", | |
| 60 | + "series": "Daytona", | |
| 61 | + "model": "116520", | |
| 62 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116520/435588", | |
| 63 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116520-435588-260828-145145478.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 64 | + "name": "Rolex Daytona 116520", | |
| 65 | + "box": true, | |
| 66 | + "papers": true, | |
| 67 | + "year": 2015, | |
| 68 | + "price": 17950, | |
| 69 | + "currency": "GBP" | |
| 70 | + }, | |
| 71 | + { | |
| 72 | + "sku": "435587", | |
| 73 | + "brand": "Rolex", | |
| 74 | + "series": "Daytona", | |
| 75 | + "model": "116523", | |
| 76 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/435587", | |
| 77 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-435587-260813-125505392.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 78 | + "name": "Rolex Daytona 116523", | |
| 79 | + "box": true, | |
| 80 | + "papers": true, | |
| 81 | + "year": 2014, | |
| 82 | + "price": 17150, | |
| 83 | + "currency": "GBP" | |
| 84 | + }, | |
| 85 | + { | |
| 86 | + "sku": "432776", | |
| 87 | + "brand": "Rolex", | |
| 88 | + "series": "Daytona", | |
| 89 | + "model": "116519", | |
| 90 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/432776", | |
| 91 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-432776-260723-162615210.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 92 | + "name": "Rolex Daytona 116519", | |
| 93 | + "box": true, | |
| 94 | + "papers": true, | |
| 95 | + "year": 2016, | |
| 96 | + "price": 26950, | |
| 97 | + "currency": "GBP" | |
| 98 | + }, | |
| 99 | + { | |
| 100 | + "sku": "434486", | |
| 101 | + "brand": "Rolex", | |
| 102 | + "series": "Daytona", | |
| 103 | + "model": "116523", | |
| 104 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/434486", | |
| 105 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-434486-260729-093059101.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 106 | + "name": "Rolex Daytona 116523", | |
| 107 | + "box": true, | |
| 108 | + "papers": true, | |
| 109 | + "year": 2009, | |
| 110 | + "price": 15360, | |
| 111 | + "currency": "GBP" | |
| 112 | + }, | |
| 113 | + { | |
| 114 | + "sku": "417963", | |
| 115 | + "brand": "Rolex", | |
| 116 | + "series": "Daytona", | |
| 117 | + "model": "116500 LN", | |
| 118 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116500-ln/417963", | |
| 119 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116500LN-417963-260707-085014266.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 120 | + "name": "Rolex Daytona 116500 LN", | |
| 121 | + "box": false, | |
| 122 | + "papers": true, | |
| 123 | + "year": 2019, | |
| 124 | + "price": 20250, | |
| 125 | + "currency": "GBP" | |
| 126 | + }, | |
| 127 | + { | |
| 128 | + "sku": "433035", | |
| 129 | + "brand": "Rolex", | |
| 130 | + "series": "Daytona", | |
| 131 | + "model": "16520", | |
| 132 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/16520/433035", | |
| 133 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-16520-433035-260722-191402800.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 134 | + "name": "Rolex Daytona 16520", | |
| 135 | + "box": false, | |
| 136 | + "papers": false, | |
| 137 | + "year": 1993, | |
| 138 | + "price": 22950, | |
| 139 | + "currency": "GBP" | |
| 140 | + }, | |
| 141 | + { | |
| 142 | + "sku": "421726", | |
| 143 | + "brand": "Rolex", | |
| 144 | + "series": "Daytona", | |
| 145 | + "model": "116503", | |
| 146 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116503/421726", | |
| 147 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116503-421726-260629-204146807.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 148 | + "name": "Rolex Daytona 116503", | |
| 149 | + "box": true, | |
| 150 | + "papers": true, | |
| 151 | + "year": 2017, | |
| 152 | + "price": 18750, | |
| 153 | + "currency": "GBP" | |
| 154 | + }, | |
| 155 | + { | |
| 156 | + "sku": "431375", | |
| 157 | + "brand": "Rolex", | |
| 158 | + "series": "Daytona", | |
| 159 | + "model": "116523", | |
| 160 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/431375", | |
| 161 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-431375-260710-085702074.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 162 | + "name": "Rolex Daytona 116523", | |
| 163 | + "box": true, | |
| 164 | + "papers": false, | |
| 165 | + "year": 2002, | |
| 166 | + "price": 17250, | |
| 167 | + "currency": "GBP" | |
| 168 | + }, | |
| 169 | + { | |
| 170 | + "sku": "429867", | |
| 171 | + "brand": "Rolex", | |
| 172 | + "series": "Daytona", | |
| 173 | + "model": "116523", | |
| 174 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/429867", | |
| 175 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-429867-260717-074806566.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 176 | + "name": "Rolex Daytona 116523", | |
| 177 | + "box": true, | |
| 178 | + "papers": true, | |
| 179 | + "year": 2004, | |
| 180 | + "price": 15950, | |
| 181 | + "currency": "GBP" | |
| 182 | + }, | |
| 183 | + { | |
| 184 | + "sku": "426741", | |
| 185 | + "brand": "Rolex", | |
| 186 | + "series": "Daytona", | |
| 187 | + "model": "116523", | |
| 188 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/426741", | |
| 189 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-426741-260617-200431001.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 190 | + "name": "Rolex Daytona 116523", | |
| 191 | + "box": false, | |
| 192 | + "papers": true, | |
| 193 | + "year": 2008, | |
| 194 | + "price": 18425, | |
| 195 | + "currency": "GBP" | |
| 196 | + }, | |
| 197 | + { | |
| 198 | + "sku": "420232", | |
| 199 | + "brand": "Rolex", | |
| 200 | + "series": "Daytona", | |
| 201 | + "model": "116523", | |
| 202 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/420232", | |
| 203 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-420232-260819-132638348.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 204 | + "name": "Rolex Daytona 116523", | |
| 205 | + "box": false, | |
| 206 | + "papers": true, | |
| 207 | + "year": 2006, | |
| 208 | + "price": 18900, | |
| 209 | + "currency": "GBP" | |
| 210 | + }, | |
| 211 | + { | |
| 212 | + "sku": "424353", | |
| 213 | + "brand": "Rolex", | |
| 214 | + "series": "Daytona", | |
| 215 | + "model": "116523", | |
| 216 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/424353", | |
| 217 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-424353-260708-103951377.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 218 | + "name": "Rolex Daytona 116523", | |
| 219 | + "box": true, | |
| 220 | + "papers": true, | |
| 221 | + "year": 2016, | |
| 222 | + "price": 17265, | |
| 223 | + "currency": "GBP" | |
| 224 | + }, | |
| 225 | + { | |
| 226 | + "sku": "216599", | |
| 227 | + "brand": "Rolex", | |
| 228 | + "series": "Daytona", | |
| 229 | + "model": "116505", | |
| 230 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116505/216599", | |
| 231 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116505-216599-260707-072401598.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 232 | + "name": "Rolex Daytona 116505", | |
| 233 | + "box": false, | |
| 234 | + "papers": true, | |
| 235 | + "year": 2012, | |
| 236 | + "price": 37950, | |
| 237 | + "currency": "GBP" | |
| 238 | + }, | |
| 239 | + { | |
| 240 | + "sku": "421404", | |
| 241 | + "brand": "Rolex", | |
| 242 | + "series": "Daytona", | |
| 243 | + "model": "116518", | |
| 244 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116518-ln/421404", | |
| 245 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116518-421404-260703-171651984.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 246 | + "name": "Rolex Daytona 116518", | |
| 247 | + "box": true, | |
| 248 | + "papers": true, | |
| 249 | + "year": 2006, | |
| 250 | + "price": 24795, | |
| 251 | + "currency": "GBP" | |
| 252 | + }, | |
| 253 | + { | |
| 254 | + "sku": "424370", | |
| 255 | + "brand": "Rolex", | |
| 256 | + "series": "Daytona", | |
| 257 | + "model": "116519", | |
| 258 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/424370", | |
| 259 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-424370-1-260818-110847177.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 260 | + "name": "Rolex Daytona 116519", | |
| 261 | + "box": true, | |
| 262 | + "papers": true, | |
| 263 | + "year": 2014, | |
| 264 | + "price": 24950, | |
| 265 | + "currency": "GBP" | |
| 266 | + }, | |
| 267 | + { | |
| 268 | + "sku": "404363", | |
| 269 | + "brand": "Rolex", | |
| 270 | + "series": "Daytona", | |
| 271 | + "model": "116523", | |
| 272 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/404363", | |
| 273 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-404363-260609-074921565.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 274 | + "name": "Rolex Daytona 116523", | |
| 275 | + "box": true, | |
| 276 | + "papers": true, | |
| 277 | + "year": 2009, | |
| 278 | + "price": 14575, | |
| 279 | + "currency": "GBP" | |
| 280 | + }, | |
| 281 | + { | |
| 282 | + "sku": "427971", | |
| 283 | + "brand": "Rolex", | |
| 284 | + "series": "Daytona", | |
| 285 | + "model": "116509", | |
| 286 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116509/427971", | |
| 287 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116509-427971-260628-054737497.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 288 | + "name": "Rolex Daytona 116509", | |
| 289 | + "box": true, | |
| 290 | + "papers": true, | |
| 291 | + "year": 2008, | |
| 292 | + "price": 26575, | |
| 293 | + "currency": "GBP" | |
| 294 | + }, | |
| 295 | + { | |
| 296 | + "sku": "394635", | |
| 297 | + "brand": "Rolex", | |
| 298 | + "series": "Daytona", | |
| 299 | + "model": "116519", | |
| 300 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/394635", | |
| 301 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-394635-260701-113643309.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 302 | + "name": "Rolex Daytona 116519", | |
| 303 | + "box": true, | |
| 304 | + "papers": true, | |
| 305 | + "year": 2001, | |
| 306 | + "price": 22950, | |
| 307 | + "currency": "GBP" | |
| 308 | + }, | |
| 309 | + { | |
| 310 | + "sku": "420703", | |
| 311 | + "brand": "Rolex", | |
| 312 | + "series": "Daytona", | |
| 313 | + "model": "16523", | |
| 314 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/16523/420703", | |
| 315 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-16523-420703-260824-080122231.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 316 | + "name": "Rolex Daytona 16523", | |
| 317 | + "box": false, | |
| 318 | + "papers": false, | |
| 319 | + "year": 1990, | |
| 320 | + "price": 13000, | |
| 321 | + "currency": "GBP" | |
| 322 | + }, | |
| 323 | + { | |
| 324 | + "sku": "427525", | |
| 325 | + "brand": "Rolex", | |
| 326 | + "series": "Daytona", | |
| 327 | + "model": "116519", | |
| 328 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/427525", | |
| 329 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-427525-1-260618-105333317.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 330 | + "name": "Rolex Daytona 116519", | |
| 331 | + "box": true, | |
| 332 | + "papers": true, | |
| 333 | + "year": 2001, | |
| 334 | + "price": 22950, | |
| 335 | + "currency": "GBP" | |
| 336 | + }, | |
| 337 | + { | |
| 338 | + "sku": "424179", | |
| 339 | + "brand": "Rolex", | |
| 340 | + "series": "Daytona", | |
| 341 | + "model": "116519", | |
| 342 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/424179", | |
| 343 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-424179-260706-081427991.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 344 | + "name": "Rolex Daytona 116519", | |
| 345 | + "box": true, | |
| 346 | + "papers": true, | |
| 347 | + "year": 2002, | |
| 348 | + "price": 23500, | |
| 349 | + "currency": "GBP" | |
| 350 | + }, | |
| 351 | + { | |
| 352 | + "sku": "337841", | |
| 353 | + "brand": "Rolex", | |
| 354 | + "series": "Daytona", | |
| 355 | + "model": "116509", | |
| 356 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116509/337841", | |
| 357 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116509-337841-250227-114559.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 358 | + "name": "Rolex Daytona 116509", | |
| 359 | + "box": true, | |
| 360 | + "papers": true, | |
| 361 | + "year": 2005, | |
| 362 | + "price": 29750, | |
| 363 | + "currency": "GBP" | |
| 364 | + }, | |
| 365 | + { | |
| 366 | + "sku": "423255", | |
| 367 | + "brand": "Rolex", | |
| 368 | + "series": "Daytona", | |
| 369 | + "model": "116515 LN", | |
| 370 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116515-ln/423255", | |
| 371 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116515LN-423255-260602-150013611.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 372 | + "name": "Rolex Daytona 116515 LN", | |
| 373 | + "box": true, | |
| 374 | + "papers": false, | |
| 375 | + "year": 2015, | |
| 376 | + "price": 24950, | |
| 377 | + "currency": "GBP" | |
| 378 | + }, | |
| 379 | + { | |
| 380 | + "sku": "412609", | |
| 381 | + "brand": "Rolex", | |
| 382 | + "series": "Daytona", | |
| 383 | + "model": "116523", | |
| 384 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/412609", | |
| 385 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-412609-260526-141012207.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 386 | + "name": "Rolex Daytona 116523", | |
| 387 | + "box": true, | |
| 388 | + "papers": true, | |
| 389 | + "year": 2002, | |
| 390 | + "price": 14950, | |
| 391 | + "currency": "GBP" | |
| 392 | + }, | |
| 393 | + { | |
| 394 | + "sku": "421204", | |
| 395 | + "brand": "Rolex", | |
| 396 | + "series": "Daytona", | |
| 397 | + "model": "116509", | |
| 398 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116509/421204", | |
| 399 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116509-421204-1-260601-120606216.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 400 | + "name": "Rolex Daytona 116509", | |
| 401 | + "box": true, | |
| 402 | + "papers": false, | |
| 403 | + "year": 2009, | |
| 404 | + "price": 24500, | |
| 405 | + "currency": "GBP" | |
| 406 | + }, | |
| 407 | + { | |
| 408 | + "sku": "423822", | |
| 409 | + "brand": "Rolex", | |
| 410 | + "series": "Daytona", | |
| 411 | + "model": "16523", | |
| 412 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/16523/423822", | |
| 413 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-16523-423822-260529-101912029.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 414 | + "name": "Rolex Daytona 16523", | |
| 415 | + "box": false, | |
| 416 | + "papers": false, | |
| 417 | + "year": 1997, | |
| 418 | + "price": 14000, | |
| 419 | + "currency": "GBP" | |
| 420 | + }, | |
| 421 | + { | |
| 422 | + "sku": "422046", | |
| 423 | + "brand": "Rolex", | |
| 424 | + "series": "Daytona", | |
| 425 | + "model": "116523", | |
| 426 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/422046", | |
| 427 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-422046-260521-145331824.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 428 | + "name": "Rolex Daytona 116523", | |
| 429 | + "box": true, | |
| 430 | + "papers": true, | |
| 431 | + "year": 2017, | |
| 432 | + "price": 16250, | |
| 433 | + "currency": "GBP" | |
| 434 | + }, | |
| 435 | + { | |
| 436 | + "sku": "420700", | |
| 437 | + "brand": "Rolex", | |
| 438 | + "series": "Daytona", | |
| 439 | + "model": "116503", | |
| 440 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116503/420700", | |
| 441 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116503-420700-260520-150422496.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 442 | + "name": "Rolex Daytona 116503", | |
| 443 | + "box": false, | |
| 444 | + "papers": false, | |
| 445 | + "year": 2016, | |
| 446 | + "price": 18500, | |
| 447 | + "currency": "GBP" | |
| 448 | + }, | |
| 449 | + { | |
| 450 | + "sku": "418609", | |
| 451 | + "brand": "Rolex", | |
| 452 | + "series": "Daytona", | |
| 453 | + "model": "116523", | |
| 454 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/418609", | |
| 455 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-418609-260518-200712854.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 456 | + "name": "Rolex Daytona 116523", | |
| 457 | + "box": true, | |
| 458 | + "papers": true, | |
| 459 | + "year": 2007, | |
| 460 | + "price": 19500, | |
| 461 | + "currency": "GBP" | |
| 462 | + }, | |
| 463 | + { | |
| 464 | + "sku": "416066", | |
| 465 | + "brand": "Rolex", | |
| 466 | + "series": "Daytona", | |
| 467 | + "model": "116523", | |
| 468 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116523/416066", | |
| 469 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116523-416066-260421-085959376.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 470 | + "name": "Rolex Daytona 116523", | |
| 471 | + "box": true, | |
| 472 | + "papers": true, | |
| 473 | + "year": 2016, | |
| 474 | + "price": 15950, | |
| 475 | + "currency": "GBP" | |
| 476 | + }, | |
| 477 | + { | |
| 478 | + "sku": "405135", | |
| 479 | + "brand": "Rolex", | |
| 480 | + "series": "Daytona", | |
| 481 | + "model": "116519", | |
| 482 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/405135", | |
| 483 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-405135-260319-182733244.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 484 | + "name": "Rolex Daytona 116519", | |
| 485 | + "box": true, | |
| 486 | + "papers": true, | |
| 487 | + "year": 2012, | |
| 488 | + "price": 24500, | |
| 489 | + "currency": "GBP" | |
| 490 | + }, | |
| 491 | + { | |
| 492 | + "sku": "411076", | |
| 493 | + "brand": "Rolex", | |
| 494 | + "series": "Daytona", | |
| 495 | + "model": "116518", | |
| 496 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116518-ln/411076", | |
| 497 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116518-411076-260316-163459193.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 498 | + "name": "Rolex Daytona 116518", | |
| 499 | + "box": false, | |
| 500 | + "papers": true, | |
| 501 | + "year": 2011, | |
| 502 | + "price": 29950, | |
| 503 | + "currency": "GBP" | |
| 504 | + }, | |
| 505 | + { | |
| 506 | + "sku": "410184", | |
| 507 | + "brand": "Rolex", | |
| 508 | + "series": "Daytona", | |
| 509 | + "model": "116518", | |
| 510 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116518-ln/410184", | |
| 511 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116518-410184-260401-095847428.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 512 | + "name": "Rolex Daytona 116518", | |
| 513 | + "box": true, | |
| 514 | + "papers": true, | |
| 515 | + "year": 2014, | |
| 516 | + "price": 25500, | |
| 517 | + "currency": "GBP" | |
| 518 | + }, | |
| 519 | + { | |
| 520 | + "sku": "409319", | |
| 521 | + "brand": "Rolex", | |
| 522 | + "series": "Daytona", | |
| 523 | + "model": "116518", | |
| 524 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116518-ln/409319", | |
| 525 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116518-409319-260401-090212728.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 526 | + "name": "Rolex Daytona 116518", | |
| 527 | + "box": true, | |
| 528 | + "papers": true, | |
| 529 | + "year": 2009, | |
| 530 | + "price": 24950, | |
| 531 | + "currency": "GBP" | |
| 532 | + }, | |
| 533 | + { | |
| 534 | + "sku": "400738", | |
| 535 | + "brand": "Rolex", | |
| 536 | + "series": "Daytona", | |
| 537 | + "model": "116518", | |
| 538 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116518-ln/400738", | |
| 539 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116518-400738-260129-101536382.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 540 | + "name": "Rolex Daytona 116518", | |
| 541 | + "box": true, | |
| 542 | + "papers": true, | |
| 543 | + "year": 2006, | |
| 544 | + "price": 27350, | |
| 545 | + "currency": "GBP" | |
| 546 | + }, | |
| 547 | + { | |
| 548 | + "sku": "405325", | |
| 549 | + "brand": "Rolex", | |
| 550 | + "series": "Daytona", | |
| 551 | + "model": "116518", | |
| 552 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116518-ln/405325", | |
| 553 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116518-405325-260306-125658442.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 554 | + "name": "Rolex Daytona 116518", | |
| 555 | + "box": false, | |
| 556 | + "papers": true, | |
| 557 | + "year": 2009, | |
| 558 | + "price": 23950, | |
| 559 | + "currency": "GBP" | |
| 560 | + }, | |
| 561 | + { | |
| 562 | + "sku": "405297", | |
| 563 | + "brand": "Rolex", | |
| 564 | + "series": "Daytona", | |
| 565 | + "model": "116509", | |
| 566 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116509/405297", | |
| 567 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116509-405297-260218-161330268.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 568 | + "name": "Rolex Daytona 116509", | |
| 569 | + "box": false, | |
| 570 | + "papers": true, | |
| 571 | + "year": 2010, | |
| 572 | + "price": 29950, | |
| 573 | + "currency": "GBP" | |
| 574 | + }, | |
| 575 | + { | |
| 576 | + "sku": "371822", | |
| 577 | + "brand": "Rolex", | |
| 578 | + "series": "Daytona", | |
| 579 | + "model": "116519", | |
| 580 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116519/371822", | |
| 581 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116519-371822-250901-111546351.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 582 | + "name": "Rolex Daytona 116519", | |
| 583 | + "box": true, | |
| 584 | + "papers": true, | |
| 585 | + "year": 2010, | |
| 586 | + "price": 21415, | |
| 587 | + "currency": "GBP" | |
| 588 | + }, | |
| 589 | + { | |
| 590 | + "sku": "396201", | |
| 591 | + "brand": "Rolex", | |
| 592 | + "series": "Daytona", | |
| 593 | + "model": "116509", | |
| 594 | + "url": "https://www.watchfinder.co.uk/watches/rolex/daytona/116509/396201", | |
| 595 | + "image": "https://www.watchfinder.co.uk/media/catalog/product/W/a/Watch-1-Rolex-Daytona-116509-396201-260311-161600361.jpg?quality=80&bg-color=255,255,255&height=360&width=280&fit=crop", | |
| 596 | + "name": "Rolex Daytona 116509", | |
| 597 | + "box": false, | |
| 598 | + "papers": true, | |
| 599 | + "year": 2012, | |
| 600 | + "price": 29950, | |
| 601 | + "currency": "GBP" | |
| 602 | + } | |
| 603 | + ] | |
| 604 | + } | |
| 605 | + }, | |
| 606 | + "expect": { | |
| 607 | + "minCount": 1, | |
| 608 | + "kinds": [ | |
| 609 | + "listing" | |
| 610 | + ] | |
| 611 | + }, | |
| 612 | + "note": "Captured live by connectors/api/_luxury-lib/capture.ts", | |
| 613 | + "capturedAt": "2026-09-07T06:29:23.128Z" | |
| 614 | +} | |
| \ No newline at end of file | ||
modified
packages/connectors/src/engines/firecrawl.ts
+3 −2
@@ -54,7 +54,7 @@ export function createFirecrawlEngine(opts: FirecrawlEngineOptions) { | ||
| 54 | 54 | async function scrape(url: string, o: FetchOptions = {}): Promise<ExtractionResult> { |
| 55 | 55 | const started = Date.now(); |
| 56 | 56 | const timeoutMs = o.timeoutMs ?? opts.defaultTimeoutMs ?? 60_000; |
| 57 | − const formats: unknown[] = ['markdown', 'html']; | |
| 57 | + const formats: unknown[] = ['markdown', 'html', 'rawHtml']; | |
| 58 | 58 | if (o.jsonSchema || o.jsonPrompt) formats.push({ type: 'json', ...(o.jsonSchema ? { schema: o.jsonSchema } : {}), ...(o.jsonPrompt ? { prompt: o.jsonPrompt } : {}) }); |
| 59 | 59 | const body: Record<string, unknown> = { |
| 60 | 60 | url, |
@@ -76,7 +76,8 @@ export function createFirecrawlEngine(opts: FirecrawlEngineOptions) { | ||
| 76 | 76 | url, |
| 77 | 77 | finalUrl: d?.metadata?.url ?? d?.metadata?.sourceURL ?? null, |
| 78 | 78 | httpStatus: status, |
| 79 | − html: d?.html ?? d?.rawHtml ?? null, | |
| 79 | + // rawHtml keeps <script> blocks (__NEXT_DATA__, JSON-LD); the cleaned html is the fallback. | |
| 80 | + html: d?.rawHtml ?? d?.html ?? null, | |
| 80 | 81 | markdown: d?.markdown ?? null, |
| 81 | 82 | json: d?.json ?? null, |
| 82 | 83 | qualityScore: 0, |
| 83 | 84 | |