{
  "openapi": "3.1.0",
  "info": {
    "title": "ASINSpotlight Scraping API",
    "summary": "Real-time Amazon product, search, seller, and storefront data via REST.",
    "description": "The ASINSpotlight Scraping API returns structured Amazon data on demand: product details, keyword search results, full seller offer panels, seller storefront profiles, and arbitrary Amazon URL scrapes. Single API key, query-param style, JSON in/JSON out, 20 supported marketplaces.\n\n**Display language.** Every endpoint accepts an optional `language` parameter: `en` (default) scrapes the English render of the page, `native` scrapes the marketplace's own language (currently supported: `it`, `fr`, `de`, `es`, with more marketplaces rolling out). Normalized fields (condition slugs, prices, dates, units) stay canonical either way; free-text fields follow the requested language.\n\n**Deals and promotions.** Product data and search result entries carry a `promotion` object: the deal badge Amazon renders only while a promotion is live (normalized type, verbatim label, `amzn1.deal.*` id, and the exact UTC end time), plus the struck-through `list_price` and EU-only `lowest_price_30_days` reference prices with savings. `null` means the product is selling at its normal price, so one request answers whether a discount is running right now.\n\n**Source of truth.** This spec is hand-maintained against the live NestJS controller and DTOs. Response field shapes are verified against shipping responses; if any field here diverges from the live JSON, treat the live response as authoritative and report the doc bug.\n\n**Status codes reflect our service health, not what's on Amazon.** A scrape that succeeds but finds the entity missing (invalid ASIN/seller, removed listing) is **not** an error: it returns `200` with `found: false` and `data: null`. Reserve `4xx` for caller mistakes (bad key, bad params, quota) and `5xx` for our failures (parser error, transport). Always branch on the `found` boolean, not on the HTTP status, to tell \"found\" from \"not found\".\n\n**Quota awareness.** Every successful response carries `meta.usage.requests_remaining`. Use it to make quota-aware loops; don't poll a separate endpoint to find out how much credit you have left.",
    "version": "1.5.0",
    "contact": {
      "name": "ASINSpotlight Support",
      "email": "support@asinspotlight.com",
      "url": "https://www.asinspotlight.com/api"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.asinspotlight.com/api"
    }
  },
  "servers": [
    {
      "url": "https://api.asinspotlight.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Product",
      "description": "Product detail page (PDP) data — title, price, reviews, BSR, brand, images, more."
    },
    {
      "name": "Search",
      "description": "Keyword search result pages with paginated entries."
    },
    {
      "name": "Offers",
      "description": "Full Buy Box and seller offer panel for a product."
    },
    {
      "name": "Seller",
      "description": "Seller storefront profile — name, feedback rating, and Detailed Seller Information (legal business name and address)."
    },
    {
      "name": "Scrape",
      "description": "Submit an arbitrary Amazon URL; the API auto-detects the page type."
    }
  ],
  "paths": {
    "/product": {
      "get": {
        "tags": ["Product"],
        "summary": "Get product details by ASIN",
        "description": "Fetches the live product detail page for a given ASIN and marketplace. Returns 60+ fields including pricing, Buy Box owner, reviews, BSR, brand, category, and images. Sub-second response time on typical product pages.\n\nIf the ASIN doesn't exist (or the listing was removed), the response is `200` with `found: false` and `data: null` — not a `404`.",
        "operationId": "getProduct",
        "parameters": [
          {
            "$ref": "#/components/parameters/Asin"
          },
          {
            "$ref": "#/components/parameters/Marketplace"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "description": "Product detail data, or a not-found result (`found: false`, `data: null`) if the ASIN isn't on Amazon.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResponse"
                },
                "examples": {
                  "found": {
                    "summary": "Product found",
                    "value": {
                      "success": true,
                      "found": true,
                      "page_type": "product",
                      "data": {
                        "asin": "B0B3ZD8QXJ",
                        "title": "Soundcore by Anker Q20i Hybrid Active Noise Cancelling Headphones",
                        "buybox": true,
                        "bb_price": 39.99,
                        "rating": 4.6,
                        "reviews": 58800,
                        "bsr": 12,
                        "in_stock": true,
                        "is_prime": true,
                        "promotion": {
                          "deal": {
                            "kind": "limited_time",
                            "label": "Limited time deal",
                            "deal_id": "amzn1.deal.b12c9bd2",
                            "ends_at": "2026-08-16T04:00:00Z",
                            "ends_on": "2026-08-16",
                            "ends_in_days": 15,
                            "ends_on_text": null
                          },
                          "list_price": {"amount": 49.99, "label": "List Price:", "savings_amount": 10.0, "savings_percent": 20.0},
                          "lowest_price_30_days": null
                        },
                        "brand": {"name": "Soundcore", "url": "/stores/Soundcore/page/..."},
                        "category": {"name": "Over-Ear Headphones", "url": "/b?node=172541"},
                        "image_url": "https://m.media-amazon.com/images/I/61abc...jpg",
                        "sellers_all": 1,
                        "bought_past_month": 20000
                      },
                      "meta": {
                        "marketplace": "us",
                        "language": "en",
                        "timing_ms": 1842,
                        "request_url": "https://api.asinspotlight.com/v1/product?asin=B0B3ZD8QXJ&marketplace=us",
                        "timestamp": "2026-05-03T10:14:22.318Z",
                        "request_id": "550e8400-e29b-41d4-a716-446655440000",
                        "usage": {"requests_consumed": 1, "requests_remaining": 49999}
                      }
                    }
                  },
                  "notFound": {
                    "summary": "ASIN not on Amazon (still billable)",
                    "value": {
                      "success": true,
                      "found": false,
                      "page_type": "product",
                      "data": null,
                      "meta": {
                        "marketplace": "us",
                        "language": "en",
                        "timing_ms": 1610,
                        "request_url": "https://api.asinspotlight.com/v1/product?asin=B000000000&marketplace=us",
                        "timestamp": "2026-05-03T10:14:22.318Z",
                        "request_id": "550e8400-e29b-41d4-a716-446655440000",
                        "usage": {"requests_consumed": 1, "requests_remaining": 49998}
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {"$ref": "#/components/responses/ValidationError"},
          "401": {"$ref": "#/components/responses/AuthError"},
          "429": {"$ref": "#/components/responses/RateLimitOrQuotaError"},
          "500": {"$ref": "#/components/responses/InternalError"},
          "503": {"$ref": "#/components/responses/ServiceUnavailableError"}
        }
      }
    },
    "/search": {
      "get": {
        "tags": ["Search"],
        "summary": "Search Amazon by keyword",
        "description": "Submits a keyword query against the chosen marketplace and returns the search results page. Each entry already includes ASIN, title, price, rating, review count, monthly demand, and stock — enough to shortlist without a follow-up `/product` call.\n\nPagination: pass `page=N` to walk deeper pages. The response's `data.current_page` and `data.last_page_number` tell you where you are and how many pages Amazon exposes for that query. Amazon serves one of two result-page layouts, chosen per query, and they pair with different pagination walls: up to 48 entries per page stopping at 7 pages, or up to 16 entries per page stopping at 20. Read `shallow_parts.length` and `data.last_page_number` rather than assuming fixed values, and don't page past `last_page_number` (`total_results_count` reports the whole match set, not what Amazon will paginate through). Each paginated request counts as one credit.",
        "operationId": "searchKeyword",
        "parameters": [
          {
            "name": "keyword",
            "in": "query",
            "required": true,
            "description": "Search keyword. URL-encode multi-word queries (e.g. `wireless+headphones` or `wireless%20headphones`).",
            "schema": {"type": "string", "minLength": 1},
            "example": "wireless headphones"
          },
          {
            "$ref": "#/components/parameters/Marketplace"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number to retrieve (1-based). Amazon usually caps a keyword search at 20 pages regardless of `total_results_count`. Read `data.last_page_number` for the real ceiling on a given query.",
            "schema": {"type": "integer", "minimum": 1, "default": 1},
            "example": 1
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "description": "Keyword search results page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                },
                "example": {
                  "success": true,
                  "found": true,
                  "page_type": "search",
                  "data": {
                    "title": "wireless headphones",
                    "current_page": 1,
                    "last_page_number": 20,
                    "total_results_count": 10000,
                    "shallow_parts": [
                      {
                        "asin": "B0CQXMXJC5",
                        "title": "Soundcore Q20i Headphones",
                        "index_on_page": 1,
                        "page_number": 1,
                        "price": 39.99,
                        "shipping": 0,
                        "is_prime": true,
                        "rating": 4.6,
                        "reviews": 58800,
                        "bought_past_month": 20000,
                        "in_stock": true,
                        "coupon": null,
                        "image_url": "https://m.media-amazon.com/images/I/...",
                        "promotion": {
                          "deal": {
                            "kind": "limited_time",
                            "label": "Limited time deal",
                            "deal_id": null,
                            "ends_at": null,
                            "ends_on": null,
                            "ends_in_days": null,
                            "ends_on_text": null
                          },
                          "list_price": {"amount": 49.99, "label": "List:", "savings_amount": 10.0, "savings_percent": 20.0},
                          "lowest_price_30_days": null
                        },
                        "has_variation_swatches": true,
                        "advertised_variation_count": 4,
                        "sampled_variation_asins": ["B0CQXMABC1", "B0CQXMABC2", "B0CQXMABC3"]
                      }
                    ]
                  },
                  "meta": {
                    "marketplace": "us",
                    "language": "en",
                    "timing_ms": 2104,
                    "request_url": "https://api.asinspotlight.com/v1/search?keyword=wireless%20headphones&marketplace=us",
                    "timestamp": "2026-05-03T10:14:22.318Z",
                    "request_id": "550e8400-e29b-41d4-a716-446655440000",
                    "usage": {"requests_consumed": 1, "requests_remaining": 49998}
                  }
                }
              }
            }
          },
          "400": {"$ref": "#/components/responses/ValidationError"},
          "401": {"$ref": "#/components/responses/AuthError"},
          "429": {"$ref": "#/components/responses/RateLimitOrQuotaError"},
          "500": {"$ref": "#/components/responses/InternalError"},
          "503": {"$ref": "#/components/responses/ServiceUnavailableError"}
        }
      }
    },
    "/offers": {
      "get": {
        "tags": ["Offers"],
        "summary": "Get every seller offer for a product",
        "description": "Returns the full Buy Box panel and All Offers Display for a product: every seller currently listing the ASIN, with their price, shipping cost, condition, fulfillment method (FBA/FBM/Amazon), seller rating, stock, and delivery promises. Updated in real time, not from a cached database.\n\nEach offer is labeled with both a normalized `condition` (e.g. `used_like_new`) and the raw `condition_text` (e.g. `Used - Like New`). Use the optional `condition` query parameter to return only offers of a given condition or used tier.\n\nThe featured (Buy Box) offer is flagged with `is_pinned`, so you do not need to infer it from price. Each offer also carries a `delivery` array with the promised dates, whether delivery is free, and how many days out the promise is.\n\nPagination: Amazon's offer panel holds 10 offers per page, and page 1 additionally carries the pinned Buy Box offer, so a full first page returns up to 11 entries. Pass `page=N` to walk the rest. Every response reports `total_pages` and a 0-based `page_index`, so `page=2` returns `page_index: 1`. Read `total_pages` from the page 1 response and loop up to it: page 1 counts the offers beside the featured one, which matches how the pages actually fill, while page 2 and later count the featured offer too and can therefore report one page more than exists. Requesting a page past the end does not return an empty list: Amazon clamps it and re-serves the last page, and `page_index` echoes the page you asked for, so a loop trusting the higher number silently duplicates offers and pays for the extra request. Pages after the first never contain a pinned offer, and `fba_count` / `fbm_count` / `sold_by_amazon` describe the requested page rather than the whole listing. Each paginated request counts as one credit.\n\nWorked example: ASIN `B004YAVF8I` on `us` has 15 offers. Page 1 returns 11 (pinned + 10), page 2 returns the remaining 4, and `/v1/product` reports `sellers_all: 15` for the same ASIN.",
        "operationId": "getOffers",
        "parameters": [
          {
            "$ref": "#/components/parameters/Asin"
          },
          {
            "$ref": "#/components/parameters/Marketplace"
          },
          {
            "$ref": "#/components/parameters/Condition"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page of the offer panel to retrieve (1-based). Page 1 returns the pinned Buy Box offer plus up to 10 more; each later page returns up to 10. Read `data.total_pages` from the page 1 response for the real ceiling.",
            "schema": {"type": "integer", "minimum": 1, "default": 1},
            "example": 1
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "description": "Seller offer panel",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OffersResponse"
                },
                "example": {
                  "success": true,
                  "found": true,
                  "page_type": "offers",
                  "data": {
                    "sold_by_amazon": true,
                    "fba_count": 3,
                    "fbm_count": 2,
                    "product_sellers_info": [
                      {
                        "name": "Amazon.com",
                        "price": 29.99,
                        "shipping_price": 0,
                        "is_fba": true,
                        "is_amazon": true,
                        "stock_qty": 100,
                        "rating_percent": 95,
                        "reviews_count": 50000,
                        "condition": "new",
                        "condition_text": "New",
                        "seller_id": null,
                        "domain": "com",
                        "is_just_launched": false,
                        "is_pinned": true,
                        "delivery": [
                          {
                            "kind": "standard",
                            "days_min": 8,
                            "days_max": 8,
                            "date_min": "2026-07-25",
                            "date_max": "2026-07-25",
                            "is_free": true,
                            "text": "FREE delivery Saturday, July 25."
                          },
                          {
                            "kind": "fastest",
                            "days_min": 5,
                            "days_max": 7,
                            "date_min": "2026-07-22",
                            "date_max": "2026-07-24",
                            "is_free": null,
                            "text": "Or fastest delivery July 22 - 24."
                          }
                        ]
                      },
                      {
                        "name": "ThriftBooks",
                        "price": 18.5,
                        "shipping_price": 3.99,
                        "is_fba": false,
                        "is_amazon": false,
                        "stock_qty": 5,
                        "rating_percent": 97,
                        "reviews_count": 4200,
                        "condition": "used_like_new",
                        "condition_text": "Used - Like New",
                        "seller_id": "A3XYZ123456",
                        "domain": "com",
                        "is_just_launched": false,
                        "is_pinned": false,
                        "delivery": [
                          {
                            "kind": "standard",
                            "days_min": 6,
                            "days_max": 11,
                            "date_min": "2026-07-23",
                            "date_max": "2026-07-28",
                            "is_free": false,
                            "text": "$3.99 delivery July 23 - 28."
                          }
                        ]
                      }
                    ],
                    "page_index": 0,
                    "total_pages": 2
                  },
                  "meta": {
                    "marketplace": "us",
                    "language": "en",
                    "timing_ms": 2417,
                    "request_url": "https://api.asinspotlight.com/v1/offers?asin=B0B3ZD8QXJ&marketplace=us",
                    "timestamp": "2026-05-03T10:14:22.318Z",
                    "request_id": "550e8400-e29b-41d4-a716-446655440000",
                    "usage": {"requests_consumed": 1, "requests_remaining": 49997}
                  }
                }
              }
            }
          },
          "400": {"$ref": "#/components/responses/ValidationError"},
          "401": {"$ref": "#/components/responses/AuthError"},
          "429": {"$ref": "#/components/responses/RateLimitOrQuotaError"},
          "500": {"$ref": "#/components/responses/InternalError"},
          "503": {"$ref": "#/components/responses/ServiceUnavailableError"}
        }
      }
    },
    "/seller": {
      "get": {
        "tags": ["Seller"],
        "summary": "Get a seller storefront profile by seller ID",
        "description": "Fetches a seller's storefront profile page (`/sp?seller=…`) and returns the seller name, feedback rating and positive-feedback percentage over the last 12 months, total rating count, the storefront link, and the **Detailed Seller Information** block (legal business name and address) when Amazon exposes it.\n\nIf the seller ID is invalid — Amazon redirects unknown sellers to a generic help page — the response is `200` with `found: false` and `data: null`, not a `404`.",
        "operationId": "getSeller",
        "parameters": [
          {
            "$ref": "#/components/parameters/SellerId"
          },
          {
            "$ref": "#/components/parameters/Marketplace"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "description": "Seller storefront profile, or a not-found result (`found: false`, `data: null`) if the seller ID isn't a valid storefront.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SellerResponse"
                },
                "examples": {
                  "found": {
                    "summary": "Seller found",
                    "value": {
                      "success": true,
                      "found": true,
                      "page_type": "seller",
                      "data": {
                        "seller_id": "A1PXYTJNWCR133",
                        "name": "Lovinio",
                        "storefront_url": "/s?ie=UTF8&marketplaceID=ATVPDKIKX0DER&me=A1PXYTJNWCR133",
                        "rating": 3.4,
                        "positive_percent": 59,
                        "ratings_count": 217,
                        "business_name": "Lovinio Inc",
                        "business_address": ["4652 Eagle Falls Pl", "Tampa", "FL", "33619", "US"]
                      },
                      "meta": {
                        "marketplace": "us",
                        "language": "en",
                        "timing_ms": 1312,
                        "request_url": "https://api.asinspotlight.com/v1/seller?sellerId=A1PXYTJNWCR133&marketplace=us",
                        "timestamp": "2026-05-03T10:14:22.318Z",
                        "request_id": "550e8400-e29b-41d4-a716-446655440000",
                        "usage": {"requests_consumed": 1, "requests_remaining": 49996}
                      }
                    }
                  },
                  "notFound": {
                    "summary": "Seller ID not a valid storefront (still billable)",
                    "value": {
                      "success": true,
                      "found": false,
                      "page_type": "seller",
                      "data": null,
                      "meta": {
                        "marketplace": "us",
                        "language": "en",
                        "timing_ms": 1902,
                        "request_url": "https://api.asinspotlight.com/v1/seller?sellerId=A0000000000000&marketplace=us",
                        "timestamp": "2026-05-03T10:14:22.318Z",
                        "request_id": "550e8400-e29b-41d4-a716-446655440000",
                        "usage": {"requests_consumed": 1, "requests_remaining": 49995}
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {"$ref": "#/components/responses/ValidationError"},
          "401": {"$ref": "#/components/responses/AuthError"},
          "429": {"$ref": "#/components/responses/RateLimitOrQuotaError"},
          "500": {"$ref": "#/components/responses/InternalError"},
          "503": {"$ref": "#/components/responses/ServiceUnavailableError"}
        }
      }
    },
    "/scrape": {
      "post": {
        "tags": ["Scrape"],
        "summary": "Scrape an arbitrary Amazon URL",
        "description": "Submit any Amazon URL — product page, search results, offers, bestsellers, category, storefront, seller profile, reviews. The page type is auto-detected from the URL pattern; the response shape matches the detected type. Use this when you need a page format that doesn't fit a typed endpoint (deal pages, marketplace-specific layouts, search variants).",
        "operationId": "scrapeUrl",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeRequest"
              },
              "example": {
                "url": "https://www.amazon.com/gp/bestsellers/electronics",
                "marketplace": "us"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Auto-detected page data. The `data` field shape matches the detected `page_type`; a not-found page returns `found: false` and `data: null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScrapeResponse"
                }
              }
            }
          },
          "400": {"$ref": "#/components/responses/ValidationError"},
          "401": {"$ref": "#/components/responses/AuthError"},
          "429": {"$ref": "#/components/responses/RateLimitOrQuotaError"},
          "500": {"$ref": "#/components/responses/InternalError"},
          "503": {"$ref": "#/components/responses/ServiceUnavailableError"}
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your API key. Generate one at https://board.asinspotlight.com/dashboard/api"
      }
    },
    "parameters": {
      "Asin": {
        "name": "asin",
        "in": "query",
        "required": true,
        "description": "Amazon Standard Identification Number — 10-character alphanumeric ID for a product.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Z0-9]{10}$"
        },
        "example": "B0B3ZD8QXJ"
      },
      "SellerId": {
        "name": "sellerId",
        "in": "query",
        "required": true,
        "description": "Amazon seller (merchant) ID — the `A…` token from a seller storefront URL (`/sp?seller=<sellerId>` or `…&me=<sellerId>`).",
        "schema": {
          "type": "string",
          "pattern": "^[A-Z0-9]{10,}$"
        },
        "example": "A1PXYTJNWCR133"
      },
      "Marketplace": {
        "name": "marketplace",
        "in": "query",
        "required": false,
        "description": "Two-letter marketplace code. Defaults to `us`. Picks which Amazon storefront to query.",
        "schema": {
          "$ref": "#/components/schemas/Marketplace"
        },
        "example": "us"
      },
      "Condition": {
        "name": "condition",
        "in": "query",
        "required": false,
        "description": "Filter offers by condition. Defaults to `all` (every condition). `used` returns any used tier. The filter is applied server-side against each offer's normalized `condition`, so the response contains only offers of the requested condition and `fba_count`/`fbm_count`/`sold_by_amazon` reflect the filtered set.",
        "schema": {
          "$ref": "#/components/schemas/OfferConditionFilter"
        },
        "example": "used"
      },
      "Language": {
        "name": "language",
        "in": "query",
        "required": false,
        "description": "Display language of the scraped page. Defaults to `en` (English render). Set `native` to fetch and parse the page in the marketplace's own language: free-text fields (title, category and brand names, `condition_text`, delivery text) come back in that language, while normalized fields (condition slugs, prices, dates, units) stay canonical. Native mode is currently supported for: `it`, `fr`, `de`, `es`. Requesting `native` on any other marketplace returns HTTP 400 with error code `NATIVE_LANGUAGE_NOT_SUPPORTED` and is not billed. More marketplaces are rolling out.",
        "schema": {
          "$ref": "#/components/schemas/Language"
        },
        "example": "native"
      }
    },
    "schemas": {
      "Marketplace": {
        "type": "string",
        "default": "us",
        "enum": [
          "us", "uk", "de", "fr", "it", "es", "ca", "au", "jp", "in",
          "mx", "br", "tr", "sa", "ae", "sg", "nl", "pl", "se", "be"
        ],
        "description": "Supported marketplaces:\n- `us` — amazon.com (United States)\n- `uk` — amazon.co.uk (United Kingdom)\n- `de` — amazon.de (Germany)\n- `fr` — amazon.fr (France)\n- `it` — amazon.it (Italy)\n- `es` — amazon.es (Spain)\n- `ca` — amazon.ca (Canada)\n- `au` — amazon.com.au (Australia)\n- `jp` — amazon.co.jp (Japan)\n- `in` — amazon.in (India)\n- `mx` — amazon.com.mx (Mexico)\n- `br` — amazon.com.br (Brazil)\n- `tr` — amazon.com.tr (Turkey)\n- `sa` — amazon.sa (Saudi Arabia)\n- `ae` — amazon.ae (UAE)\n- `sg` — amazon.sg (Singapore)\n- `nl` — amazon.nl (Netherlands)\n- `pl` — amazon.pl (Poland)\n- `se` — amazon.se (Sweden)\n- `be` — amazon.com.be (Belgium)"
      },
      "Language": {
        "type": "string",
        "default": "en",
        "enum": ["en", "native"],
        "description": "Display language for the scraped page:\n- `en`: the English render of the page (default)\n- `native`: the marketplace's own language (e.g. Italian on `it`). Only available on marketplaces with validated native parsing; others return `NATIVE_LANGUAGE_NOT_SUPPORTED`."
      },
      "OfferConditionFilter": {
        "type": "string",
        "default": "all",
        "enum": ["all", "new", "used", "used_like_new", "used_very_good", "used_good", "used_acceptable", "collectible"],
        "description": "Condition filter accepted by the `condition` query parameter:\n- `all` — every condition (default)\n- `new` — New offers only\n- `used` — any used tier (Like New / Very Good / Good / Acceptable)\n- `used_like_new`, `used_very_good`, `used_good`, `used_acceptable` — a specific used tier\n- `collectible` — Collectible offers"
      },
      "OfferCondition": {
        "type": "string",
        "enum": ["new", "used_like_new", "used_very_good", "used_good", "used_acceptable", "collectible"],
        "description": "Normalized, locale-independent condition of a single offer. `null` when Amazon's heading was absent or not recognized (e.g. Renewed/Refurbished, or some non-English wordings); the raw heading is then available in `condition_text`."
      },
      "ResponseMeta": {
        "type": "object",
        "description": "Per-request metadata. Always present on successful responses.",
        "required": ["marketplace", "language", "timing_ms", "request_url", "timestamp", "request_id", "usage"],
        "properties": {
          "marketplace": {
            "type": "string",
            "description": "The marketplace code that was queried.",
            "example": "us"
          },
          "language": {
            "type": "string",
            "enum": ["en", "native"],
            "description": "The display language the page was scraped in.",
            "example": "en"
          },
          "timing_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "End-to-end request time in milliseconds, measured from receipt to response.",
            "example": 1842
          },
          "request_url": {
            "type": "string",
            "format": "uri",
            "description": "The API request URL you called, echoed back for logging and correlation.",
            "example": "https://api.asinspotlight.com/v1/product?asin=B0B3ZD8QXJ&marketplace=us"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp when the response was generated.",
            "example": "2026-05-03T10:14:22.318Z"
          },
          "request_id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique request ID. Include it when contacting support.",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "usage": {
            "$ref": "#/components/schemas/UsageMeta"
          }
        }
      },
      "UsageMeta": {
        "type": "object",
        "description": "Per-request quota accounting. Use `requests_remaining` to drive quota-aware loops without polling a separate endpoint.",
        "required": ["requests_consumed", "requests_remaining"],
        "properties": {
          "requests_consumed": {
            "type": "integer",
            "minimum": 0,
            "description": "Credits charged for this request. `1` for any successful scrape (HTTP 200) — including not-found results (`found: false`), since the page was still fetched and parsed. `0` for failures, captchas, and auth/quota rejections.",
            "example": 1
          },
          "requests_remaining": {
            "type": "integer",
            "minimum": 0,
            "description": "Credits remaining across all currently active request packages for your API key.",
            "example": 49999
          }
        }
      },
      "Found": {
        "type": "boolean",
        "description": "Whether the requested entity was found on Amazon. When `false`, `data` is `null` — the scrape still succeeded and is billable. Branch on this rather than the HTTP status to distinguish \"found\" from \"not found\"."
      },
      "DealKind": {
        "type": "string",
        "enum": ["limited_time", "lightning", "prime_exclusive"],
        "description": "Normalized, locale-independent deal type, classified from the badge wording. `null` when the wording names no mechanic: countdown badges (\"Ends in\"), seasonal event badges (\"Black Friday Deal\", \"Prime Day Deal\"), and locales the classifier doesn't recognize. Nothing is lost in that case: the verbatim wording is always available in `label`, and the badge's presence, not its type, is what says the promotion is live."
      },
      "DealBadge": {
        "type": "object",
        "description": "The deal badge Amazon renders above the price while the featured offer is on an active promotion. Presence is the signal: Amazon only renders the badge while the deal is running, so a non-null badge means the promotion is live at fetch time. That makes \"has the promotion ended?\" answerable from a single request instead of a price time series.",
        "properties": {
          "kind": {
            "description": "Normalized deal type; `null` when the badge wording isn't recognized (see `label`).",
            "oneOf": [
              {"$ref": "#/components/schemas/DealKind"},
              {"type": "null"}
            ]
          },
          "label": {"type": "string", "description": "Verbatim badge text as rendered, in the page's language.", "example": "Limited time deal"},
          "deal_id": {"type": ["string", "null"], "description": "Amazon's own promotion identifier, stable for the lifetime of one deal and shared across the ASINs it covers. Compare it across scrapes to tell a continuing deal from a new one at a similar price. `null` on search result entries.", "example": "amzn1.deal.b12c9bd2"},
          "ends_at": {"type": ["string", "null"], "format": "date-time", "description": "The exact moment the deal expires, ISO 8601 in UTC. Read from Amazon's machine-readable timestamp, so it needs no locale to interpret; prefer it for anything time-sensitive. On search result entries present only for countdown badges, which state their own target.", "example": "2026-08-16T04:00:00Z"},
          "ends_on": {"type": ["string", "null"], "format": "date", "description": "The calendar day the deal ends: the UTC day of `ends_at`, or the day named in Amazon's deal-terms popover when the page carries no timestamp. A day-granularity summary, not a purchase deadline.", "example": "2026-08-16"},
          "ends_in_days": {"type": ["integer", "null"], "description": "Whole days from the scrape date to `ends_on`; `0` means the deal ends today. Always `null` on search result entries (derive it from `ends_at` there).", "example": 15},
          "ends_on_text": {"type": ["string", "null"], "description": "Amazon's own end-date sentence, verbatim and localized. Absent on storefronts that render no deal-terms popover (amazon.com is one).", "example": "Questa offerta termina il 26 luglio 2026"}
        }
      },
      "ReferencePrice": {
        "type": "object",
        "description": "A struck-through \"was\" price Amazon displays the current price against.",
        "properties": {
          "amount": {"type": "number", "format": "double", "description": "The struck-through price.", "example": 199.99},
          "label": {"type": ["string", "null"], "description": "Verbatim label Amazon rendered (\"List Price:\", \"Typical:\", \"Prezzo consigliato:\"). `null` when the struck price carries no label.", "example": "List Price:"},
          "savings_amount": {"type": ["number", "null"], "format": "double", "description": "`amount` minus the current price. Never negative: a reference at or below the current price is not a discount, so the field is `null` rather than zero or negative.", "example": 50.99},
          "savings_percent": {"type": ["number", "null"], "format": "double", "description": "The discount as a positive whole percent. Amazon's own rendered figure when it shows one (that is what the shopper sees), otherwise derived from the two prices and rounded the way Amazon rounds.", "example": 25}
        }
      },
      "PromotionInfo": {
        "type": "object",
        "description": "Everything the page says about the current price being a discount: the deal badge Amazon renders only while a promotion is live, and the struck-through reference prices the discount is measured against. The three fields are independent: an everyday markdown can be struck through with no badge at all, and a badged deal need not carry a reference price. Clip-to-apply coupons are a separate mechanism and are not part of this object.",
        "properties": {
          "deal": {
            "description": "The active deal, when Amazon renders a deal badge. `null` means no deal is running on the offer at fetch time.",
            "oneOf": [
              {"$ref": "#/components/schemas/DealBadge"},
              {"type": "null"}
            ]
          },
          "list_price": {
            "description": "The headline struck-through price: usually the manufacturer's list price, but Amazon fills the same slot with a typical, median, or former price when it has no list price to show (\"Typical:\", \"Prezzo mediano:\", \"Ancien prix :\"). The `label` wording is the only thing that states which one it is.",
            "oneOf": [
              {"$ref": "#/components/schemas/ReferencePrice"},
              {"type": "null"}
            ]
          },
          "lowest_price_30_days": {
            "description": "The lowest price of the last 30 days, which EU storefronts must display alongside a discount (Omnibus Directive). Absent outside the EU.",
            "oneOf": [
              {"$ref": "#/components/schemas/ReferencePrice"},
              {"type": "null"}
            ]
          }
        }
      },
      "ProductData": {
        "type": "object",
        "description": "Detail-page data for a single ASIN. Field set is documented to the typical case; absent fields can occur on listings missing that data on the page.",
        "properties": {
          "asin": {"type": "string", "example": "B0B3ZD8QXJ"},
          "title": {"type": "string", "example": "Soundcore Q20i Headphones"},
          "buybox": {"type": "boolean", "description": "Whether a Buy Box is currently displayed.", "example": true},
          "bb_price": {"type": "number", "format": "double", "description": "Buy Box price.", "example": 39.99},
          "rating": {"type": "number", "format": "double", "minimum": 0, "maximum": 5, "example": 4.6},
          "reviews": {"type": "integer", "minimum": 0, "description": "Total review count.", "example": 58800},
          "bsr": {"type": "integer", "minimum": 1, "description": "Best Sellers Rank within the primary category.", "example": 12},
          "in_stock": {"type": "boolean", "example": true},
          "is_prime": {"type": "boolean", "example": true},
          "promotion": {
            "description": "Whether the featured offer is on an active deal, and the struck-through reference prices the discount is measured against. `null` when the page shows neither a deal badge nor a struck-through price, i.e. the product is selling at its normal price.",
            "oneOf": [
              {"$ref": "#/components/schemas/PromotionInfo"},
              {"type": "null"}
            ]
          },
          "brand": {
            "type": "object",
            "properties": {
              "name": {"type": "string", "example": "Soundcore"},
              "url": {"type": "string", "description": "Relative URL to the brand storefront.", "example": "/stores/Soundcore"}
            }
          },
          "category": {
            "type": "object",
            "properties": {
              "name": {"type": "string", "example": "Over-Ear Headphones"},
              "url": {"type": "string", "description": "Relative URL to the category browse node.", "example": "/b?node=172541"}
            }
          },
          "image_url": {"type": "string", "format": "uri", "example": "https://m.media-amazon.com/images/I/61abc.jpg"},
          "sellers_all": {"type": "integer", "minimum": 0, "description": "Total seller count for this listing.", "example": 5},
          "bought_past_month": {"type": "integer", "minimum": 0, "description": "Amazon's `Xk+ bought in past month` indicator. Coerced from labels like `2K+` to integer.", "example": 20000}
        }
      },
      "ProductResponse": {
        "type": "object",
        "required": ["success", "found", "page_type", "data", "meta"],
        "properties": {
          "success": {"type": "boolean", "const": true},
          "found": {"$ref": "#/components/schemas/Found"},
          "page_type": {"type": "string", "const": "product"},
          "data": {
            "description": "Product data, or `null` when `found` is `false`.",
            "anyOf": [
              {"$ref": "#/components/schemas/ProductData"},
              {"type": "null"}
            ]
          },
          "meta": {"$ref": "#/components/schemas/ResponseMeta"}
        }
      },
      "SearchResultEntry": {
        "type": "object",
        "description": "One organic search result entry. Sponsored placements are filtered out.",
        "properties": {
          "asin": {"type": "string", "example": "B0CQXMXJC5"},
          "title": {"type": "string", "example": "Soundcore Q20i Headphones"},
          "index_on_page": {"type": "integer", "minimum": 1, "description": "1-based position of this entry within the page, in Amazon's own result order.", "example": 1},
          "page_number": {"type": "integer", "minimum": 1, "description": "The page this entry came from. Echoes `data.current_page`, so merged pages stay ordered.", "example": 1},
          "price": {"type": "number", "format": "double", "example": 39.99},
          "shipping": {"type": ["number", "null"], "format": "double", "description": "Shipping cost quoted on the tile, in the marketplace's local currency. `0` means free shipping.", "example": 0},
          "is_prime": {"type": ["boolean", "null"], "description": "`true` when the tile carries the Prime badge, `null` when it does not.", "example": true},
          "rating": {"type": "number", "format": "double", "minimum": 0, "maximum": 5, "example": 4.6},
          "reviews": {"type": "integer", "minimum": 0, "example": 58800},
          "bought_past_month": {"type": "integer", "minimum": 0, "example": 20000},
          "in_stock": {"type": "boolean", "example": true},
          "coupon": {
            "description": "Clip-to-apply coupon advertised on the tile, a separate mechanism from `promotion`, carrying the same meaning here as on `/product`: `value` is what the coupon takes off, never the price left after it. A `percent` coupon carries the percentage, a `currency` coupon the amount in the marketplace's local currency. Not reflected in `price`. `null` on most listings.",
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "unit": {"type": "string", "enum": ["currency", "percent"], "example": "currency"},
                  "value": {"type": "number", "format": "double", "example": 5}
                }
              },
              {"type": "null"}
            ]
          },
          "image_url": {"type": "string", "format": "uri", "example": "https://m.media-amazon.com/images/I/..."},
          "promotion": {
            "description": "Deal badge and struck-through reference price carried by the result tile: the shallow half of what `/product` reports. No `deal_id` or `ends_on_text`, `ends_at` only for countdown badges, `ends_in_days` always `null`. `null` when the tile shows neither a badge nor a struck-through price.",
            "oneOf": [
              {"$ref": "#/components/schemas/PromotionInfo"},
              {"type": "null"}
            ]
          },
          "has_variation_swatches": {"type": "boolean", "description": "`true` when the result tile shows a color/size swatch row. A positive-only signal that the product is one child of a variation family; `false` means no swatch row was rendered, not that the product has no variations.", "example": true},
          "advertised_variation_count": {"type": ["integer", "null"], "description": "The family size the tile advertises: visible swatches plus the `+N more` overflow. `null` when the tile has no swatch row.", "example": 4},
          "sampled_variation_asins": {"type": "array", "items": {"type": "string"}, "description": "Sibling child ASINs Amazon rendered as swatches. A partial set (typically 6 or fewer, excluding the item's own ASIN), not the full family.", "example": ["B0CQXMABC1", "B0CQXMABC2", "B0CQXMABC3"]}
        }
      },
      "SearchData": {
        "type": "object",
        "properties": {
          "title": {"type": "string", "description": "The keyword that was searched.", "example": "wireless headphones"},
          "current_page": {"type": "integer", "minimum": 1, "example": 1},
          "last_page_number": {"type": "integer", "minimum": 1, "example": 20},
          "total_results_count": {"type": "integer", "minimum": 0, "example": 10000},
          "shallow_parts": {
            "type": "array",
            "description": "Search result entries on the current page.",
            "items": {"$ref": "#/components/schemas/SearchResultEntry"}
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": ["success", "found", "page_type", "data", "meta"],
        "properties": {
          "success": {"type": "boolean", "const": true},
          "found": {"$ref": "#/components/schemas/Found"},
          "page_type": {"type": "string", "const": "search"},
          "data": {
            "description": "Search data, or `null` when `found` is `false`.",
            "anyOf": [
              {"$ref": "#/components/schemas/SearchData"},
              {"type": "null"}
            ]
          },
          "meta": {"$ref": "#/components/schemas/ResponseMeta"}
        }
      },
      "OfferEntry": {
        "type": "object",
        "description": "One seller offer on a product listing.",
        "properties": {
          "name": {"type": "string", "example": "Amazon.com"},
          "price": {"type": "number", "format": "double", "example": 29.99},
          "shipping_price": {"type": "number", "format": "double", "minimum": 0, "example": 0},
          "is_fba": {"type": "boolean", "description": "Fulfilled by Amazon.", "example": true},
          "is_amazon": {"type": "boolean", "description": "Sold by Amazon directly (vs. third-party seller).", "example": true},
          "stock_qty": {"type": "integer", "minimum": 0, "description": "Stock quantity if exposed by Amazon; may be 0 or absent if hidden.", "example": 100},
          "rating_percent": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Seller positive-feedback percentage.", "example": 95},
          "reviews_count": {"type": "integer", "minimum": 0, "description": "Seller's total review count.", "example": 50000},
          "condition": {
            "description": "Normalized offer condition, or `null` when not recognized (see `condition_text`).",
            "oneOf": [
              {"$ref": "#/components/schemas/OfferCondition"},
              {"type": "null"}
            ]
          },
          "condition_text": {"type": ["string", "null"], "description": "Verbatim, whitespace-collapsed condition heading shown by Amazon (locale-dependent), e.g. `New`, `Used - Like New`, `Nuevo`. `null` when Amazon shows no heading.", "example": "Used - Like New"},
          "seller_id": {"type": ["string", "null"], "description": "Amazon seller (merchant) ID; `null` for offers sold directly by Amazon.", "example": null},
          "domain": {"type": "string", "description": "Amazon TLD the offer was scraped from.", "example": "com"},
          "is_just_launched": {"type": "boolean", "description": "Seller is newly launched (no established feedback history yet).", "example": false},
          "is_pinned": {"type": "boolean", "description": "`true` for the featured (Buy Box) offer. At most one offer per response carries this flag. Prefer it over inferring the Buy Box winner from price: position in the array is **not** a substitute, and a response with no pinned offer means Amazon rendered no featured offer for this ASIN. Note that a `condition` filter can remove the pinned offer from the response.", "example": true},
          "delivery": {
            "type": "array",
            "description": "Delivery promises shown on the offer, newest-first by slot: the primary (`standard`) slot, then the secondary (`fastest`) slot when Amazon shows one. Empty when the offer displays no delivery block (for example, a grocery offer on an address-less session).",
            "items": {"$ref": "#/components/schemas/DeliveryEstimate"}
          }
        }
      },
      "DeliveryEstimate": {
        "type": "object",
        "description": "One delivery promise parsed from an offer. `date_*` and `days_*` describe the same promise: `date_*` is stable, while `days_*` is relative to the moment the request was made and goes stale. Key stored records on `date_*`.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": ["standard", "fastest"],
            "description": "Which slot the promise came from. `standard` is the primary promise. `fastest` is the secondary slot, which Amazon also uses for Prime upsells (\"Or Prime members get FREE delivery Friday\"), so treat it as \"the other option shown\" rather than a guaranteed faster one.",
            "example": "standard"
          },
          "days_min": {"type": ["integer", "null"], "minimum": 0, "description": "Whole days until the earliest promised date, counted from the request date. `0` means delivery today. `null` means the promise could not be resolved to a date (unrecognized or non-English wording); it is never a sentinel value, so treat `null` as unknown rather than as zero.", "example": 8},
          "days_max": {"type": ["integer", "null"], "minimum": 0, "description": "Whole days until the latest promised date. Equal to `days_min` for a single-date promise. `null` under the same conditions as `days_min`.", "example": 8},
          "date_min": {"type": ["string", "null"], "format": "date", "description": "Earliest promised delivery date (`YYYY-MM-DD`), or `null` when unresolved. Amazon does not render a year, so it is inferred from the request date.", "example": "2026-07-25"},
          "date_max": {"type": ["string", "null"], "format": "date", "description": "Latest promised delivery date (`YYYY-MM-DD`), equal to `date_min` for a single-date promise, or `null` when unresolved.", "example": "2026-07-25"},
          "is_free": {"type": ["boolean", "null"], "description": "`true` when the promise is free delivery, `false` when a delivery charge is named, `null` when Amazon states no cost. This is the reliable free-shipping signal: `shipping_price` reports `0` both for free delivery and for an unparsed cost.", "example": true},
          "text": {"type": "string", "description": "The promise exactly as Amazon rendered it. Always populated, even when the date could not be resolved. It can embed a live countdown (\"Order within 4 hrs 18 mins\"), so do not use it as a cache key or compare it between requests.", "example": "FREE delivery Saturday, July 25."}
        }
      },
      "OffersData": {
        "type": "object",
        "properties": {
          "sold_by_amazon": {"type": "boolean", "example": true},
          "fba_count": {"type": "integer", "minimum": 0, "description": "Count of FBA offers on the requested page, not across the whole listing. Sum it yourself when paginating.", "example": 3},
          "fbm_count": {"type": "integer", "minimum": 0, "description": "Count of FBM (Fulfilled by Merchant) offers on the requested page, not across the whole listing. Sum it yourself when paginating.", "example": 2},
          "product_sellers_info": {
            "type": "array",
            "items": {"$ref": "#/components/schemas/OfferEntry"}
          },
          "page_index": {"type": "integer", "minimum": 0, "description": "0-based index of the returned page, so a `page=2` request answers with `1`.", "example": 0},
          "total_pages": {"type": "integer", "minimum": 0, "description": "Number of offer pages Amazon exposes for this ASIN. Greater than 1 means the seller list is partial and the rest is reachable via `page`. Read this from the page 1 response: page 2 and later also count the featured offer and can report one page more than exists, and a page past the end is re-served as a copy of the last page rather than an empty list, so a loop trusting the higher number duplicates offers and pays for the extra request.", "example": 2}
        }
      },
      "OffersResponse": {
        "type": "object",
        "required": ["success", "found", "page_type", "data", "meta"],
        "properties": {
          "success": {"type": "boolean", "const": true},
          "found": {"$ref": "#/components/schemas/Found"},
          "page_type": {"type": "string", "const": "offers"},
          "data": {
            "description": "Offers data, or `null` when `found` is `false`.",
            "anyOf": [
              {"$ref": "#/components/schemas/OffersData"},
              {"type": "null"}
            ]
          },
          "meta": {"$ref": "#/components/schemas/ResponseMeta"}
        }
      },
      "StorefrontInfoData": {
        "type": "object",
        "description": "Seller storefront profile. Fields sourced from the Detailed Seller Information block may be absent when Amazon doesn't publish them for that seller.",
        "properties": {
          "seller_id": {"type": ["string", "null"], "description": "Amazon seller (merchant) ID.", "example": "A1PXYTJNWCR133"},
          "name": {"type": "string", "description": "Seller storefront display name.", "example": "Lovinio"},
          "storefront_url": {"type": ["string", "null"], "description": "Relative URL to the seller's product storefront.", "example": "/s?ie=UTF8&marketplaceID=ATVPDKIKX0DER&me=A1PXYTJNWCR133"},
          "rating": {"type": "number", "format": "double", "minimum": 0, "maximum": 5, "description": "Average feedback rating over the last 12 months. `0` when no rating is shown.", "example": 3.4},
          "positive_percent": {"type": ["number", "null"], "minimum": 0, "maximum": 100, "description": "Positive-feedback percentage over the last 12 months.", "example": 59},
          "ratings_count": {"type": ["integer", "null"], "minimum": 0, "description": "Total number of feedback ratings over the last 12 months.", "example": 217},
          "business_name": {"type": ["string", "null"], "description": "Legal business name from Detailed Seller Information.", "example": "Lovinio Inc"},
          "business_address": {
            "type": "array",
            "description": "Business address lines from Detailed Seller Information, in display order. Empty when not published.",
            "items": {"type": "string"},
            "example": ["4652 Eagle Falls Pl", "Tampa", "FL", "33619", "US"]
          }
        }
      },
      "SellerResponse": {
        "type": "object",
        "required": ["success", "found", "page_type", "data", "meta"],
        "properties": {
          "success": {"type": "boolean", "const": true},
          "found": {"$ref": "#/components/schemas/Found"},
          "page_type": {"type": "string", "const": "seller"},
          "data": {
            "description": "Seller storefront data, or `null` when `found` is `false`.",
            "anyOf": [
              {"$ref": "#/components/schemas/StorefrontInfoData"},
              {"type": "null"}
            ]
          },
          "meta": {"$ref": "#/components/schemas/ResponseMeta"}
        }
      },
      "ScrapeRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Full Amazon URL to scrape. The page type is auto-detected from the URL pattern.",
            "example": "https://www.amazon.com/s?k=laptop+stand"
          },
          "marketplace": {
            "$ref": "#/components/schemas/Marketplace"
          },
          "language": {
            "$ref": "#/components/schemas/Language"
          }
        }
      },
      "ScrapeResponse": {
        "type": "object",
        "required": ["success", "found", "page_type", "data", "meta"],
        "properties": {
          "success": {"type": "boolean", "const": true},
          "found": {"$ref": "#/components/schemas/Found"},
          "page_type": {
            "type": "string",
            "enum": ["product", "search", "offers", "category", "bestseller", "storefront", "seller", "reviews"],
            "description": "Detected page type. Determines the shape of `data`."
          },
          "data": {
            "description": "Page data; shape depends on `page_type`. For `product`, matches ProductData; for `search`, matches SearchData; for `offers`, matches OffersData; for `seller`, matches StorefrontInfoData. Other types return a structured payload tailored to the page. `null` when `found` is `false`.",
            "oneOf": [
              {"$ref": "#/components/schemas/ProductData"},
              {"$ref": "#/components/schemas/SearchData"},
              {"$ref": "#/components/schemas/OffersData"},
              {"$ref": "#/components/schemas/StorefrontInfoData"},
              {"type": "object", "description": "Free-form payload for category, bestseller, storefront, or reviews pages."},
              {"type": "null"}
            ]
          },
          "meta": {"$ref": "#/components/schemas/ResponseMeta"}
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": ["success", "error"],
        "properties": {
          "success": {"type": "boolean", "const": false},
          "meta": {
            "type": "object",
            "description": "Present on quota/rate-limit errors so you can read remaining credits even when a request is rejected.",
            "properties": {
              "usage": {"$ref": "#/components/schemas/UsageMeta"}
            }
          },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code. Stable across releases.",
                "enum": [
                  "MISSING_API_KEY",
                  "INVALID_API_KEY",
                  "RATE_LIMIT_EXCEEDED",
                  "MONTHLY_QUOTA_EXCEEDED",
                  "NATIVE_LANGUAGE_NOT_SUPPORTED",
                  "SCRAPE_FAILED",
                  "CAPTCHA_DETECTED",
                  "API_USAGE_UNAVAILABLE",
                  "INTERNAL_ERROR"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable description. Subject to wording change; do not match on this string."
              }
            }
          }
        }
      }
    },
    "responses": {
      "ValidationError": {
        "description": "Request rejected before scraping. Never billed. Two shapes: parameter validation failures (missing required parameter, invalid marketplace or language code, unrecognized field) come from the framework's validation pipe and do not follow the standard `ErrorEnvelope`; requesting `language=native` on a marketplace without native support returns a standard `ErrorEnvelope` with code `NATIVE_LANGUAGE_NOT_SUPPORTED`.",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "type": "object",
                  "description": "Framework validation-pipe shape.",
                  "properties": {
                    "statusCode": {"type": "integer", "example": 400},
                    "message": {
                      "oneOf": [
                        {"type": "string"},
                        {"type": "array", "items": {"type": "string"}}
                      ],
                      "example": ["asin should not be empty", "marketplace must be one of the following values: us, uk, de, ..."]
                    },
                    "error": {"type": "string", "example": "Bad Request"}
                  }
                },
                {"$ref": "#/components/schemas/ErrorEnvelope"}
              ]
            },
            "examples": {
              "validation": {
                "summary": "Parameter validation failed (framework shape)",
                "value": {
                  "statusCode": 400,
                  "message": ["language must be one of the following values: en, native"],
                  "error": "Bad Request"
                }
              },
              "nativeLanguageNotSupported": {
                "summary": "NATIVE_LANGUAGE_NOT_SUPPORTED — native parsing not yet available for this marketplace",
                "value": {
                  "success": false,
                  "meta": {"usage": {"requests_consumed": 0, "requests_remaining": 49996}},
                  "error": {"code": "NATIVE_LANGUAGE_NOT_SUPPORTED", "message": "language=native is not yet available for marketplace \"jp\". Currently supported: it, fr, de, es."}
                }
              }
            }
          }
        }
      },
      "AuthError": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/ErrorEnvelope"},
            "examples": {
              "missing": {
                "summary": "MISSING_API_KEY",
                "value": {
                  "success": false,
                  "error": {"code": "MISSING_API_KEY", "message": "Provide your API key in the x-api-key header."}
                }
              },
              "invalid": {
                "summary": "INVALID_API_KEY",
                "value": {
                  "success": false,
                  "error": {"code": "INVALID_API_KEY", "message": "The provided API key is not valid."}
                }
              }
            }
          }
        }
      },
      "RateLimitOrQuotaError": {
        "description": "Either too many parallel in-flight requests for your API key, or you have no active request credits left.",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/ErrorEnvelope"},
            "examples": {
              "rateLimit": {
                "summary": "RATE_LIMIT_EXCEEDED — too many concurrent requests",
                "value": {
                  "success": false,
                  "meta": {"usage": {"requests_consumed": 0, "requests_remaining": 49996}},
                  "error": {"code": "RATE_LIMIT_EXCEEDED", "message": "Too many parallel requests."}
                }
              },
              "quota": {
                "summary": "MONTHLY_QUOTA_EXCEEDED — no active credits",
                "value": {
                  "success": false,
                  "meta": {"usage": {"requests_consumed": 0, "requests_remaining": 0}},
                  "error": {"code": "MONTHLY_QUOTA_EXCEEDED", "message": "No active API request credits are available."}
                }
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Our service failed to return data: either the parser reached the page but couldn't extract structured data (`SCRAPE_FAILED` — page format change, partial render, malformed HTML), or an internal/transport failure to the parser worker (`INTERNAL_ERROR`). Not billed. Note: a missing entity is **not** an error — it returns `200` with `found: false`.",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/ErrorEnvelope"},
            "examples": {
              "scrapeFailed": {
                "summary": "SCRAPE_FAILED — page reached but not parseable",
                "value": {
                  "success": false,
                  "error": {"code": "SCRAPE_FAILED", "message": "Failed to parse Amazon page."}
                }
              },
              "internal": {
                "summary": "INTERNAL_ERROR — transport/internal failure",
                "value": {
                  "success": false,
                  "error": {"code": "INTERNAL_ERROR", "message": "Request to parser service failed. Please try again."}
                }
              }
            }
          }
        }
      },
      "ServiceUnavailableError": {
        "description": "Amazon returned a captcha page, or the usage tracking layer is temporarily unavailable. Retry after a short delay.",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/ErrorEnvelope"},
            "examples": {
              "captcha": {
                "summary": "CAPTCHA_DETECTED",
                "value": {
                  "success": false,
                  "error": {"code": "CAPTCHA_DETECTED", "message": "Amazon returned a captcha page. Please retry."}
                }
              },
              "usageUnavailable": {
                "summary": "API_USAGE_UNAVAILABLE",
                "value": {
                  "success": false,
                  "error": {"code": "API_USAGE_UNAVAILABLE", "message": "API usage tracking is temporarily unavailable."}
                }
              }
            }
          }
        }
      }
    }
  }
}
