> ## 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.

# StoreRouter TypeScript SDK — async client API reference

> Install the TypeScript SDK for StoreRouter, set your API key, and search or look up products across active crawled catalogs with full ESM type safety in Node.js.

The StoreRouter TypeScript SDK gives you a fully typed, async client for the StoreRouter commerce API. Install it from npm, configure your `OCTO_API_KEY`, and you can search products by keyword and facet, find products similar to a source product, and look up any product by URL — all with complete TypeScript interfaces so your editor catches mistakes before they reach production.

## Installation

<Steps>
  <Step title="Install the package">
    Requires Node.js 20 or later. The SDK ships as ESM only.

    ```bash theme={null}
    npm install @octogen-ai/sdk
    ```
  </Step>

  <Step title="Set your API key">
    The client reads your key from the `OCTO_API_KEY` environment variable automatically.

    ```bash theme={null}
    export OCTO_API_KEY=octo_live_...
    ```

    You can also pass `apiKey:` in the constructor options if you prefer to manage secrets yourself (see [Authentication](#authentication) below).
  </Step>
</Steps>

## Authentication

`OctogenClient` resolves your API key in this order:

1. The `apiKey` option passed to the constructor, if provided.
2. The `OCTO_API_KEY` environment variable (`process.env.OCTO_API_KEY`).

If neither is set, the constructor throws `MissingAPIKeyError` immediately — before any network request is made. Every request then sends the key as `Authorization: Bearer <api-key>`.

```typescript theme={null}
import { OctogenClient } from "@octogen-ai/sdk";

// From environment variable (recommended)
const client = new OctogenClient();

// Explicit key
const client = new OctogenClient({ apiKey: "octo_live_..." });
```

## Client options

`OctogenClient` accepts an optional `OctogenClientOptions` object:

| Option | Type | Default | Description |
| - | - | - | - |
| `apiKey` | `string` | `process.env.OCTO_API_KEY` | Your Platform API key. |
| `baseUrl` | `string` | `https://api.octogen.ai/v1` | Override the API base URL (useful for testing). |
| `timeoutMs` | `number` | `30000` | Request timeout in milliseconds. |
| `fetch` | `FetchLike` | `globalThis.fetch` | Custom fetch function — useful for edge runtimes or test mocks. |

```typescript theme={null}
const client = new OctogenClient({
  timeoutMs: 10_000,
  baseUrl: "https://api.octogen.ai/v1",
});
```

## Methods

### `lookupProduct`

```typescript theme={null}
async lookupProduct(
  url: string,
  options?: LookupProductOptions
): Promise<MerchantProductUrlLookupResponse>
```

Resolves a product page URL from StoreRouter's index or with on-demand product resolution. Set `resolutionMode: "index_only"` when your integration requires indexed identity and catalog context.

```typescript theme={null}
const result = await client.lookupProduct(
  "https://example.com/products/blue-linen-dress",
  { resolutionMode: "auto" }
);
if (result.source === "indexed") {
  console.log(result.catalogKey, result.product.uuid);
} else {
  console.log(result.product.title, result.product.currency);
}
```

**Returns:** `Promise<MerchantProductUrlLookupResponse>`

| Field | Type | Description |
| - | - | - |
| `source` | `"indexed" \| "on_demand"` | Source that produced the result. |
| `catalogKey` | `string \| null` | Indexed catalog, or `null` on demand. |
| `catalogDisplayName` | `string \| null` | Indexed catalog name, or `null` on demand. |
| `product` | `MerchantProductView` | Full or partial-safe product view. |
| `resolution` | `ProductResolutionMetadata \| null` | On-demand completeness and evidence. |

***

### `searchProducts`

```typescript theme={null}
async searchProducts(params: SearchProductsParams): Promise<MerchantProductListPage>
```

Searches products across all authorized catalogs by default. Pass `catalog` only when you want to restrict search to one catalog.

**`SearchProductsParams` fields:**

| Field | Type | Default | Description |
| - | - | - | - |
| `catalog` | `string` | — | Optional known active crawled catalog key. Omit to search all active crawled catalogs. |
| `q` | `string` | — | Keyword search query. |
| `facets` | `Facet[]` | — | List of facet filters to apply. See [Faceted search](#faceted-search). |
| `priceMin` | `number` | — | Minimum price filter (inclusive). |
| `priceMax` | `number` | — | Maximum price filter (inclusive). |
| `cursor` | `string` | — | Pagination cursor from a previous response's `nextCursor`. |
| `limit` | `number` | `50` | Number of results per page. Must be an integer between 1 and 100. |

**Returns:** `Promise<MerchantProductListPage>`

| Field | Type | Description |
| - | - | - |
| `items` | `MerchantProductListItem[]` | Products on this page. |
| `nextCursor` | `string \| null` | Pass as `cursor` in your next call to get the next page. `null` means no more results. |

Each `MerchantProductListItem` includes `uuid`, `catalogKey`, `productUrl`, `title`, `brand`, `currentPrice`, `originalPrice`, `imageUrl`, `images`, `rating`, `isActive`, optional match scores, and `updatedAt`.

***

### `moreLikeThisProducts`

```typescript theme={null}
async moreLikeThisProducts(params: MoreLikeThisProductsParams): Promise<MoreLikeThisProductsResponse>
```

Finds products similar to a source product URL or UUID. Omit `catalog` to search all active crawled catalogs, or pass a catalog key to keep the results within one catalog.

```typescript theme={null}
const page = await client.moreLikeThisProducts({
  source: { url: "https://warrenlotas.com/products/black-hoodie" },
  catalog: "warrenlotas",
  pricePreference: "any",
  limit: 12,
});
```

**`MoreLikeThisProductsParams` fields:**

| Field | Type | Default | Description |
| - | - | - | - |
| `source` | `{ url?: string; uuid?: string }` | — | Source product identifier. Exactly one of `url` or `uuid` is required. |
| `catalog` | `string` | — | Optional known active crawled catalog key. Omit to search all active crawled catalogs. |
| `includeFacets` | `Facet[]` | — | Extra include facets appended after server-generated audience facets. |
| `excludeFacets` | `Facet[]` | — | Facets to exclude from similar-product results. |
| `pricePreference` | `"lower" \| "any" \| "higher"` | `"any"` | Relative price behavior compared with the source product. |
| `cursor` | `string` | — | Pagination cursor from a previous response's `nextCursor`. |
| `limit` | `number` | `12` | Number of results per page. Must be an integer between 1 and 100. |
| `debug` | `boolean` | `false` | Include the public `effectiveQuery` used for retrieval. |

**Returns:** `Promise<MoreLikeThisProductsResponse>`

| Field | Type | Description |
| - | - | - |
| `source` | `MoreLikeThisSourceResponse` | Source product identity. |
| `items` | `MerchantProductListItem[]` | Similar products on this page. |
| `nextCursor` | `string \| null` | Pass as `cursor` in your next call to get the next page. |
| `effectiveQuery` | `object \| null` | Present only when `debug` is `true`. |

## Complete example

The following example searches all active crawled catalogs for women's linen dresses and prints each result with its brand and price:

```typescript theme={null}
import { inspect } from "node:util";
import { OctogenAPIError, OctogenClient } from "@octogen-ai/sdk";

async function main(): Promise<void> {
  try {
    const client = new OctogenClient();
    const results = await client.searchProducts({
      limit: 5,
      q: "women's linen summer dresses",
    });

    console.log("Catalog scope: all active crawled catalogs");
    for (const product of results.items) {
      const brand = product.brand?.name ?? "Unknown brand";
      const price =
        product.currentPrice === null || product.currentPrice === undefined
          ? "Price unavailable"
          : `$${product.currentPrice.toFixed(2)}`;
      const title = product.title ?? "Untitled product";
      console.log(`- ${title} | ${brand} | ${price}`);
      console.log(`  ${product.productUrl}`);
    }
  } catch (error) {
    if (error instanceof OctogenAPIError) {
      console.error(`StoreRouter API error: status=${String(error.statusCode)}`);
      console.error(inspect(error.detail, { depth: null }));
    }
    throw error;
  }
}

await main();
```

## Faceted search

Use `Facet` objects to filter by brand, gender, color, category, and other attributes. Pass them in the `facets` field of `SearchProductsParams`.

```typescript theme={null}
import { FacetName, OctogenClient } from "@octogen-ai/sdk";

const client = new OctogenClient();

const results = await client.searchProducts({
  q: "summer dress",
  facets: [
    { name: FacetName.GENDER, values: ["female"] },
    { name: FacetName.COLOR_FAMILY, values: ["Blue", "Green"] },
  ],
  priceMax: 150,
  limit: 20,
});

for (const product of results.items) {
  console.log(product.title, product.productUrl);
}
```

<Tip>
  `FacetName` is a const object with string values for all built-in facet fields: `BRAND_NAME`, `GENDER`, `AGE_GROUPS`, `COLOR`, `COLOR_FAMILY`, `IS_ACTIVEWEAR`, `PRODUCT_TYPE`, `CATEGORY_PATH_DEPTH_0` through `CATEGORY_PATH_DEPTH_6`, and more. You can also pass any custom attribute facet name as a plain string.
</Tip>

## Pagination

When `nextCursor` is not `null` in a response, pass it as `cursor` in your next call to retrieve the following page:

```typescript theme={null}
let cursor: string | undefined;

while (true) {
  const page = await client.searchProducts({
    q: "linen dress",
    cursor,
    limit: 50,
  });

  for (const product of page.items) {
    console.log(product.title);
  }

  if (page.nextCursor == null) break;
  cursor = page.nextCursor;
}
```

## Error handling

All SDK errors extend `OctogenError`. HTTP errors extend `OctogenAPIError`, which exposes `statusCode`, `detail`, and `response` properties.

<AccordionGroup>
  <Accordion title="OctogenAuthenticationError — 401">
    Thrown when your API key is missing, malformed, or has been revoked. Check that `OCTO_API_KEY` is set correctly and that the key is still active in StoreRouter Platform.
  </Accordion>

  <Accordion title="OctogenForbiddenError — 403">
    Thrown when a valid key attempts an action it is not authorized for — for example, accessing a catalog that has not been granted to your organization.
  </Accordion>

  <Accordion title="OctogenNotFoundError — 404">
    Thrown when a catalog or product URL cannot be found. For `lookupProduct`, verify the URL belongs to a catalog your key can access.
  </Accordion>

  <Accordion title="OctogenValidationError — 422">
    Thrown when the API rejects a request due to invalid parameters. The `detail` property contains the validation error list from the API.
  </Accordion>

  <Accordion title="OctogenConnectionError">
    Thrown when the SDK cannot reach the API — for example, due to a network timeout or DNS failure. Does not have a `statusCode`.
  </Accordion>
</AccordionGroup>

Catch the base `OctogenAPIError` to handle all HTTP errors in one place:

```typescript theme={null}
import {
  OctogenAPIError,
  OctogenAuthenticationError,
  OctogenConnectionError,
} from "@octogen-ai/sdk";

try {
  const results = await client.searchProducts({ q: "dress" });
} catch (error) {
  if (error instanceof OctogenAuthenticationError) {
    console.error("Check your OCTO_API_KEY.");
  } else if (error instanceof OctogenAPIError) {
    console.error(`API error ${String(error.statusCode)}:`, error.detail);
  } else if (error instanceof OctogenConnectionError) {
    console.error("Network error:", error.message);
  } else {
    throw error;
  }
}
```

<Warning>
  `MissingAPIKeyError` is thrown by the constructor, not by a network call. It will surface at client creation time if neither `apiKey` nor `OCTO_API_KEY` is present.
</Warning>


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