> ## Documentation Index
> Fetch the complete documentation index at: https://www.octogen.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /products/lookup — resolve a product URL

> POST /products/lookup resolves a product URL from StoreRouter's index or with bounded on-demand product resolution.

`POST /v1/products/lookup` resolves one product page URL and returns a source-aware `MerchantProductUrlLookupResponse`. The default `auto` mode checks StoreRouter's product index first, then uses on-demand product resolution if the URL is not indexed and that capability is available.

## Request

```http theme={null}
POST https://api.octogen.ai/v1/products/lookup
Authorization: Bearer <your-platform-api-key>
Content-Type: application/json
```

### Body parameters

<ParamField body="url" type="string" required>
  Product page URL to resolve. The URL does not need to belong to a StoreRouter catalog unless `resolutionMode` is `index_only`.
</ParamField>

<ParamField body="resolutionMode" type="string" default="auto">
  Source selection policy. `auto` checks the index and then resolves on demand after a miss. `index_only` never performs outbound work. `on_demand_only` skips the index. Allowed values: `auto`, `index_only`, `on_demand_only`.
</ParamField>

<ParamField body="onDemandCachePolicy" type="string" default="prefer_cache">
  Cache behavior when the request can enter the on-demand path. `prefer_cache` reuses a recent result when available. `refresh` requests a fresh resolution but does not bypass safety checks or rate limits. Allowed values: `prefer_cache`, `refresh`. This field does not apply to `index_only`.
</ParamField>

### Example

```bash theme={null}
curl -sS https://api.octogen.ai/v1/products/lookup \
  -H "Authorization: Bearer $OCTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/products/blue-linen-dress",
    "resolutionMode": "auto",
    "onDemandCachePolicy": "prefer_cache"
  }'
```

## Response

<ResponseField name="requestId" type="string | null">
  Request identifier for tracing an on-demand resolution. It may be `null` for an indexed hit.
</ResponseField>

<ResponseField name="source" type="&#x22;indexed&#x22; | &#x22;on_demand&#x22;" required>
  Source that produced the successful result.
</ResponseField>

<ResponseField name="catalogKey" type="string | null">
  Catalog key for an indexed result. `null` for an on-demand result.
</ResponseField>

<ResponseField name="catalogDisplayName" type="string | null">
  Human-readable catalog name for an indexed result. `null` for an on-demand result.
</ResponseField>

<ResponseField name="sourceBaseUrl" type="string | null">
  Catalog source base URL for an indexed result. `null` for an on-demand result or when not configured.
</ResponseField>

<ResponseField name="product" type="MerchantProductView" required>
  Product detail view. `productUrl` is always present. For on-demand results, `uuid`, `catalogKey`, and `isActive` are `null`; unsupported fields are `null`, omitted, or empty according to their existing schema defaults.

  <Expandable title="Identity and core fields">
    <ResponseField name="uuid" type="string | null" required>
      Stable indexed identifier. `null` for an on-demand result.
    </ResponseField>

    <ResponseField name="catalogKey" type="string | null">
      Supplying catalog for an indexed product. `null` for an on-demand result.
    </ResponseField>

    <ResponseField name="productUrl" type="string" required>
      Product page URL.
    </ResponseField>

    <ResponseField name="title" type="string | null">
      Product display name.
    </ResponseField>

    <ResponseField name="brand" type="BrandView | null">
      Brand information when available.
    </ResponseField>

    <ResponseField name="currentPrice" type="number | null">
      Current selling price.
    </ResponseField>

    <ResponseField name="originalPrice" type="number | null">
      Pre-discount price.
    </ResponseField>

    <ResponseField name="currency" type="string | null">
      Currency code when available. This is especially important for results without catalog currency context.
    </ResponseField>

    <ResponseField name="imageUrl" type="string | null">
      Primary product image URL.
    </ResponseField>

    <ResponseField name="images" type="string[]">
      Product image URLs.
    </ResponseField>

    <ResponseField name="isActive" type="boolean | null">
      Indexed lifecycle state. `null` for an on-demand result.
    </ResponseField>

    <ResponseField name="updatedAt" type="datetime | null">
      Last indexed update timestamp when available.
    </ResponseField>
  </Expandable>

  <Expandable title="Extended fields">
    <ResponseField name="description" type="string | null">Product description.</ResponseField>
    <ResponseField name="inStock" type="boolean | null">Current stock evidence.</ResponseField>
    <ResponseField name="categories" type="CategoryView[]">Product categories.</ResponseField>
    <ResponseField name="sizes" type="string[]">Available size labels.</ResponseField>
    <ResponseField name="colors" type="ColorView[]">Available colors.</ResponseField>
    <ResponseField name="tags" type="string[]">Product tags.</ResponseField>
    <ResponseField name="variants" type="MerchantVariantView[]">SKU-level variants.</ResponseField>
    <ResponseField name="details" type="ProductDetailsView">Materials, fit, dimensions, and patterns.</ResponseField>
    <ResponseField name="audience" type="AudienceView | null">Gender and age-group data.</ResponseField>
    <ResponseField name="identifiers" type="IdentifiersView">Merchant and global identifiers.</ResponseField>
    <ResponseField name="breadcrumbs" type="BreadcrumbView[]">Merchant navigation path.</ResponseField>
    <ResponseField name="promotions" type="PromotionView[]">Active promotions.</ResponseField>
    <ResponseField name="reviews" type="ReviewView[]">Customer reviews.</ResponseField>
    <ResponseField name="videos" type="VideoView[]">Product videos.</ResponseField>
    <ResponseField name="enrichment" type="ProductEnrichment | null">StoreRouter enrichment for indexed products when available.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="requestedUrl" type="string | null">
  The URL you submitted, echoed back on every successful result.
</ResponseField>

<ResponseField name="resolvedUrl" type="string | null">
  Final URL after redirects. Populated for on-demand results when available;
  use it for on-demand follow-up lookups.
</ResponseField>

<ResponseField name="normalizedUrl" type="string | null">
  The matched product's URL as normalized by StoreRouter (HTTPS-forced, `www.`
  stripped, tracking parameters removed, query sorted). Populated for indexed
  results. This is the stable URL: submit it on a follow-up lookup and it
  deterministically re-resolves the same product. Prefer storing it over the
  URL you originally submitted.
</ResponseField>

<ResponseField name="canonicalUrl" type="string | null">
  The canonical URL the product page itself declares (JSON-LD `url`,
  `og:url`, or `link rel="canonical"`). Populated only for on-demand results;
  when the page declares none, the resolver currently falls back to the final
  fetched URL, so a non-null value is not proof of a declaration. `null` for
  indexed results. For follow-up lookups use `normalizedUrl` (indexed) or
  `resolvedUrl` (on-demand), not this field.
</ResponseField>

<ResponseField name="resolution" type="ProductResolutionMetadata | null">
  On-demand resolution metadata. `null` for indexed results.

  <Expandable title="ProductResolutionMetadata properties">
    <ResponseField name="completeness" type="&#x22;complete&#x22; | &#x22;partial&#x22;" required>
      Whether all expected shallow product fields were available.
    </ResponseField>

    <ResponseField name="method" type="&#x22;json_ld&#x22; | &#x22;open_graph&#x22; | &#x22;html_meta&#x22; | &#x22;resolved_url&#x22;" required>
      Strongest page evidence used for the result.
    </ResponseField>

    <ResponseField name="rendered" type="boolean">
      `false` for the current direct-HTML path.
    </ResponseField>

    <ResponseField name="missingFields" type="string[]">
      Expected fields that were not available.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="cacheStatus" type="&#x22;hit&#x22; | &#x22;miss&#x22; | &#x22;refresh&#x22; | null">
  Cache outcome for an on-demand result. `null` for an indexed result.
</ResponseField>

<ResponseField name="warnings" type="string[]">
  Non-fatal warnings about the result.
</ResponseField>

### Indexed example

```json theme={null}
{
  "source": "indexed",
  "catalogKey": "warrenlotas",
  "catalogDisplayName": "Warren Lotas",
  "sourceBaseUrl": "https://warrenlotas.com",
  "requestedUrl": "https://warrenlotas.com/products/black-hoodie",
  "normalizedUrl": "https://warrenlotas.com/products/black-hoodie",
  "canonicalUrl": null,
  "product": {
    "uuid": "prod_01HX...",
    "catalogKey": "warrenlotas",
    "productUrl": "https://warrenlotas.com/products/black-hoodie",
    "title": "Black Hoodie",
    "currentPrice": 180,
    "currency": "USD",
    "isActive": true
  },
  "resolution": null,
  "cacheStatus": null,
  "warnings": []
}
```

### On-demand example

```json theme={null}
{
  "requestId": "req_01J...",
  "source": "on_demand",
  "catalogKey": null,
  "catalogDisplayName": null,
  "sourceBaseUrl": null,
  "product": {
    "uuid": null,
    "catalogKey": null,
    "productUrl": "https://example.com/products/blue-linen-dress",
    "title": "Blue Linen Dress",
    "currentPrice": 128,
    "currency": "USD",
    "isActive": null
  },
  "requestedUrl": "https://example.com/products/blue-linen-dress",
  "resolvedUrl": "https://example.com/products/blue-linen-dress",
  "normalizedUrl": null,
  "canonicalUrl": "https://example.com/products/blue-linen-dress",
  "resolution": {
    "completeness": "partial",
    "method": "json_ld",
    "rendered": false,
    "missingFields": ["inStock"]
  },
  "cacheStatus": "miss",
  "warnings": []
}
```

## Errors

| Status | `detail` | Meaning |
| - | - | - |
| `404` | `product_not_found` | The selected sources did not return a useful product. |
| `422` | `invalid_product_url` | The URL is malformed or unsupported. |
| `422` | `unsafe_url` | The URL failed outbound safety checks. |
| `429` | `rate_limit_exceeded` or `product_lookup_rate_limited` | A service-wide lookup budget is exhausted. Honor `Retry-After`. |
| `502` | `product_lookup_upstream_failed` | The merchant page failed upstream. |
| `503` | `product_lookup_fallback_unavailable` | On-demand resolution is temporarily unavailable. |
| `504` | `product_lookup_timed_out` | The bounded lookup deadline expired. |

See [Error Handling](/docs/guides/error-handling) and [Rate Limits](/docs/guides/rate-limits).

## SDK equivalents

<CodeGroup>
  ```python Python theme={null}
  result = await client.lookup_product(
      "https://example.com/products/blue-linen-dress",
      resolution_mode="auto",
      on_demand_cache_policy="prefer_cache",
  )

  if result.source == "indexed":
      print(result.catalog_key, result.product.uuid)
  else:
      print(result.product.title, result.resolution)
  ```

  ```typescript TypeScript theme={null}
  const result = await client.lookupProduct(
    "https://example.com/products/blue-linen-dress",
    { resolutionMode: "auto", onDemandCachePolicy: "prefer_cache" }
  );

  if (result.source === "indexed") {
    console.log(result.catalogKey, result.product.uuid);
  } else {
    console.log(result.product.title, result.resolution);
  }
  ```
</CodeGroup>

On-demand results are ephemeral and do not create an indexed product UUID. If your integration requires indexed identity and catalog context, set `resolutionMode` to `index_only`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.