storerouter 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,391 @@
1
+ Metadata-Version: 2.4
2
+ Name: storerouter
3
+ Version: 0.1.0
4
+ Summary: Python SDK for StoreRouter: look up any product URL as structured JSON, search products across stores, and discover brands and stores
5
+ Keywords: storerouter,ecommerce,product-data,product-search,product-lookup,web-scraping,structured-data,shopping,ai-agents,sdk
6
+ Author: StoreRouter (Octogen AI)
7
+ License-Expression: Apache-2.0
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Internet :: WWW/HTTP
16
+ Classifier: Topic :: Office/Business
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: httpx>=0.28.1
20
+ Requires-Dist: octogen-ai-sdk>=0.17.0,<1.0.0
21
+ Requires-Dist: pydantic>=2.0
22
+ Requires-Python: >=3.11
23
+ Project-URL: Homepage, https://storerouter.ai
24
+ Project-URL: Documentation, https://storerouter.ai/docs
25
+ Project-URL: Source, https://github.com/octogen-ai/octogen-dev/tree/main/sdks/storerouter-python
26
+ Project-URL: Issues, https://github.com/octogen-ai/octogen-dev/issues
27
+ Project-URL: Changelog, https://github.com/octogen-ai/octogen-dev/blob/main/sdks/storerouter-python/CHANGELOG.md
28
+ Description-Content-Type: text/markdown
29
+
30
+ # StoreRouter Python SDK
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/storerouter)](https://pypi.org/project/storerouter/)
33
+ [![Python](https://img.shields.io/pypi/pyversions/storerouter)](https://pypi.org/project/storerouter/)
34
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/octogen-ai/octogen-dev/blob/main/LICENSE)
35
+
36
+ `storerouter` is the official Python SDK for [StoreRouter](https://storerouter.ai),
37
+ an API that returns **structured e-commerce product data**. Use it to:
38
+
39
+ - turn a product page URL into normalized JSON (title, brand, price, currency,
40
+ stock, variants, images, identifiers, taxonomy category and attributes);
41
+ - search products across thousands of stores, or only the stores you name;
42
+ - discover stores and brands.
43
+
44
+ Every result uses **one product schema**, whatever store it came from, and
45
+ every response is a typed Pydantic model. The package has sync and async
46
+ clients, and it retries 429 and 5xx responses for you.
47
+
48
+ ```bash
49
+ pip install storerouter
50
+ ```
51
+
52
+ ## Quickstart
53
+
54
+ Get an API key at [dev.storerouter.ai](https://dev.storerouter.ai) and export it:
55
+
56
+ ```bash
57
+ export STOREROUTER_API_KEY="sk_..."
58
+ ```
59
+
60
+ ```python
61
+ from storerouter import StoreRouter
62
+
63
+ client = StoreRouter() # reads STOREROUTER_API_KEY
64
+
65
+ result = client.lookup_product(
66
+ "https://www.allbirds.com/products/mens-runner-nz-jersey-light-grey-syrah"
67
+ )
68
+ product = result.product
69
+ print(product.title, product.current_price, product.currency, product.in_stock)
70
+ # Men's Runner NZ Jersey - Light Grey/Syrah (Blizzard Sole) 110.0 USD True
71
+ ```
72
+
73
+ ## Look up a product URL
74
+
75
+ `lookup_product` takes the URL of any product page. It returns the indexed
76
+ product when StoreRouter already has it, and otherwise extracts the page on
77
+ demand. `result.source` says which: `"indexed"` or `"on_demand"`.
78
+
79
+ ```python
80
+ result = client.lookup_product(
81
+ "https://www.allbirds.com/products/mens-runner-nz-jersey-light-grey-syrah"
82
+ )
83
+ print(result.product.model_dump_json(by_alias=True, exclude_none=True, indent=2))
84
+ ```
85
+
86
+ This is real output, shortened. It shows one of 19 variants and some of the
87
+ enrichment attributes. It leaves out the description, the long brand
88
+ description, the image list, the store's own attributes and the request ID on
89
+ image URLs.
90
+
91
+ ```json
92
+ {
93
+ "uuid": "2b7a52cc-93e4-545a-999c-ed6f41f54ea6",
94
+ "catalogKey": "allbirds",
95
+ "productUrl": "https://www.allbirds.com/products/mens-runner-nz-jersey-light-grey-syrah",
96
+ "title": "Men's Runner NZ Jersey - Light Grey/Syrah (Blizzard Sole)",
97
+ "brand": { "name": "Allbirds", "slug": "allbirds", "url": "www.allbirds.com" },
98
+ "currentPrice": 110.0,
99
+ "originalPrice": 110.0,
100
+ "currency": "USD",
101
+ "inStock": true,
102
+ "availability": "InStock",
103
+ "primaryImage": {
104
+ "url": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12548_26Q1_Runner-NZ-Jersey-Light-Grey-Syrah-Bizzard-Sole_PDP_LEFT.png?v=1774551568",
105
+ "cdnUrl": "https://cdn.storerouter.ai/91b22b320476b49b1d658a81b4f2aad6/6afbe5a521bcb04aa95fbb111487a33d.webp",
106
+ "width": 4000,
107
+ "height": 4000,
108
+ "mimeType": "image/webp"
109
+ },
110
+ "sizes": [
111
+ "8",
112
+ "8.5",
113
+ "9",
114
+ "9.5",
115
+ "10",
116
+ "10.5",
117
+ "11",
118
+ "11.5",
119
+ "12",
120
+ "12.5",
121
+ "13",
122
+ "13.5",
123
+ "14"
124
+ ],
125
+ "colors": [{ "label": "Light Grey/Syrah (Blizzard Sole)" }],
126
+ "categories": [{ "name": "Shoes" }],
127
+ "variants": [
128
+ {
129
+ "sku": "mens-runner-nz-jersey-light-grey-syrah",
130
+ "productUrl": "https://www.allbirds.com/products/mens-runner-nz-jersey-light-grey-syrah",
131
+ "color": { "label": "Light Grey/Syrah (Blizzard Sole)" },
132
+ "inStock": true,
133
+ "availability": "InStock",
134
+ "currentPrice": 110.0,
135
+ "currency": "USD"
136
+ }
137
+ ],
138
+ "identifiers": { "productId": "7258388856912", "productGroupId": "7258388856912" },
139
+ "details": { "materials": ["cotton"], "fit": [], "patterns": [] },
140
+ "audience": { "genders": ["male"], "ageGroups": ["adult"] },
141
+ "enrichment": {
142
+ "type": "sneakers",
143
+ "category_path": ["Apparel & Accessories", "Shoes", "Sneakers"],
144
+ "color_family": ["Gray", "Red", "White"],
145
+ "gender": "male",
146
+ "age_groups": ["adult"],
147
+ "attributes": {
148
+ "Activity Type": ["Running"],
149
+ "Closure type": ["Lace-Up"],
150
+ "Footwear material": ["Cotton"],
151
+ "Sneaker style": ["Athletic"],
152
+ "Shoe Type (Silhouette/Style)": ["Low-Top"]
153
+ },
154
+ "summary": "Men's light grey jersey textile sneakers featuring contrasting red laces and a clean white sole."
155
+ },
156
+ "languageCode": "en-US",
157
+ "updatedAt": "2026-11-06T10:34:25.665193Z"
158
+ }
159
+ ```
160
+
161
+ Want Markdown for an LLM prompt instead of JSON? Call
162
+ `client.lookup_product_markdown(url)`.
163
+
164
+ ### Look up many URLs at once
165
+
166
+ `lookup_products` runs lookups concurrently (8 at a time by default) and
167
+ returns one `LookupResult` per URL, in the same order. A URL that fails does
168
+ not stop the batch.
169
+
170
+ ```python
171
+ results = client.lookup_products([
172
+ "https://www.allbirds.com/products/mens-runner-nz-jersey-light-grey-syrah",
173
+ "https://www.jcrew.com/p/mens/categories/clothing/shirts/linen/short-sleeve-camp-collar-shirt-in-irish-linen/BG997",
174
+ "https://www.example.com/not-a-product",
175
+ ])
176
+ for r in results:
177
+ if r.ok:
178
+ print("ok ", r.product.title, r.product.current_price)
179
+ else:
180
+ print("fail", r.url, type(r.error).__name__, r.error)
181
+ ```
182
+
183
+ ```text
184
+ ok Men's Runner NZ Jersey - Light Grey/Syrah (Blizzard Sole) 110.0
185
+ ok Short-sleeve camp-collar shirt in Irish linen 87.99
186
+ fail https://www.example.com/not-a-product OctogenNotFoundError product_not_found
187
+ ```
188
+
189
+ ## Search products
190
+
191
+ `search_products` takes a natural-language query. By default StoreRouter
192
+ understands it: it rewrites the query for retrieval and turns implied
193
+ constraints into filters. You can narrow the search with:
194
+
195
+ - `stores`: up to 20 store domains;
196
+ - `facets`: filters such as `color_family`, `gender`, `brand_name`,
197
+ `category_path.depth_N` and taxonomy attributes;
198
+ - `price_min` and `price_max`.
199
+
200
+ With `relax=True`, a query that finds nothing is retried with its filters
201
+ dropped in stages. Only the facets you mark `relaxable=True` can be dropped.
202
+
203
+ ```python
204
+ from storerouter import SearchFacet
205
+
206
+ page = client.search_products(
207
+ q="linen shirt",
208
+ stores=["jcrew.com"],
209
+ facets=[SearchFacet(name="color_family", values=["Blue"], relaxable=True)],
210
+ relax=True,
211
+ limit=3,
212
+ )
213
+ for item in page.items:
214
+ print(f"{item.title} | {item.current_price} {item.currency} | {item.product_url}")
215
+ ```
216
+
217
+ ```text
218
+ Short-sleeve camp-collar shirt in Irish linen | 87.99 USD | https://www.jcrew.com/p/mens/categories/clothing/shirts/linen/short-sleeve-camp-collar-shirt-in-irish-linen/BG997
219
+ Gamine button-up shirt in linen | 87.99 USD | https://www.jcrew.com/p/womens/categories/clothing/matching-sets/gamine-button-up-shirt-in-linen/CV033
220
+ Washed linen shirt with point collar | 104.99 USD | https://www.jcrew.com/p/mens/categories/clothing/shirts/linen/washed-linen-shirt-with-point-collar/CH601
221
+ ```
222
+
223
+ `page.effective_query` shows the search that actually ran: the understood
224
+ filters, and which facets `relax` dropped. To get the next page, pass
225
+ `cursor=page.next_cursor`. Each item is a `ProductListItem`, a lighter
226
+ version of `Product`. To get the full record, call `lookup_product` with the
227
+ item's URL, or `retrieve_products` with the item UUIDs.
228
+
229
+ Related calls:
230
+
231
+ - `count_product_facets`: facet value counts for a query;
232
+ - `more_like_this_products`: products similar to the ones you pass;
233
+ - `list_taxonomy_categories` and `list_taxonomy_attributes`: the category tree
234
+ and attribute names that facets accept.
235
+
236
+ ## Stores and brands
237
+
238
+ ```python
239
+ store = client.get_store("allbirds.com") # is this store covered, and how well?
240
+ for catalog in store.catalogs:
241
+ print(catalog.catalog_display_name, catalog.product_count)
242
+
243
+ stores = client.search_stores("sustainable sneakers", limit=3)
244
+ print([s.store.primary_domain for s in stores.results])
245
+ # ['seavees.com', 'stellamccartney.com', '8000kicks.com']
246
+
247
+ brands = client.search_brands("minimalist sneakers", limit=3)
248
+ print([(b.brand_name, b.domain) for b in brands.results])
249
+ # [('Little Love Bug Co.', 'littlelovebugcompany.com'), ('ALIVEFORM', 'aliveform.co'), ('Camper', 'camper.com')]
250
+
251
+ brand = client.get_brand_details("allbirds")
252
+ print(brand.founded_year, brand.origin_country, brand.price_tier)
253
+ # 2016 New Zealand PREMIUM
254
+ ```
255
+
256
+ More brand calls: `search_brand_prefix` (autocomplete), `search_similar_brands`,
257
+ `get_brand_directory`, `get_brand_coverage`, `browse_brand_products` and
258
+ `get_brand_top_categories`. For stores there is also `search_similar_stores`.
259
+
260
+ ## Async
261
+
262
+ `AsyncStoreRouter` has the same constructor and the same methods as
263
+ `StoreRouter`, as coroutines:
264
+
265
+ ```python
266
+ import asyncio
267
+ from storerouter import AsyncStoreRouter
268
+
269
+ async def main() -> None:
270
+ async with AsyncStoreRouter() as client:
271
+ page = await client.search_products(q="wool sweater", limit=2)
272
+ print([item.title for item in page.items])
273
+
274
+ asyncio.run(main())
275
+ ```
276
+
277
+ `StoreRouter` runs the async client on a private event-loop thread. It
278
+ therefore works inside Jupyter and other code that already runs an event
279
+ loop. Close it with `client.close()`, or use it in a `with` block.
280
+
281
+ ## The normalized product schema
282
+
283
+ Every lookup returns a `Product` (`storerouter.models.Product`). Every search
284
+ result is a `ProductListItem`, which is a subset of `Product`. Python
285
+ attributes use snake_case. The JSON wire format uses camelCase:
286
+ `model_dump(by_alias=True)`.
287
+
288
+ | Field | What it holds |
289
+ | ------------------------------------ | ---------------------------------------------------------------------------------------------- |
290
+ | `uuid`, `catalog_key`, `product_url` | StoreRouter's stable ID, the store's catalog, and the canonical product URL |
291
+ | `title`, `description` | As the store states them |
292
+ | `brand` | `name`, `slug`, `url`, `description` |
293
+ | `current_price`, `original_price` | Sale price and list price, as numbers |
294
+ | `currency` | ISO 4217 code, for example `USD` |
295
+ | `in_stock`, `availability` | Stock rolled up across variants, and the schema.org availability term |
296
+ | `variants` | Per-variant `sku`, `color`, `size`, `in_stock`, `current_price`, `original_price`, `image_url` |
297
+ | `sizes`, `colors` | Sizes and color labels (with swatches and hex codes when the store has them) |
298
+ | `primary_image`, `images` | Store image URL plus a StoreRouter CDN copy (`cdn_url`), with dimensions |
299
+ | `identifiers` | `product_id`, `gtin`, `product_group_id` |
300
+ | `categories`, `breadcrumbs` | The store's own categories and breadcrumb trail |
301
+ | `details` | `materials`, `fit`, `dimensions`, `patterns` |
302
+ | `audience` | `genders`, `age_groups` |
303
+ | `rating`, `reviews` | Aggregate rating and individual reviews |
304
+ | `promotions`, `videos`, `tags` | Promotions with codes, product videos, and store tags |
305
+ | `additional_attributes` | Name/value pairs as the store states them |
306
+ | `enrichment` | Added by StoreRouter (see below) |
307
+ | `item_condition`, `language_code` | schema.org item condition, and the language of the text |
308
+ | `updated_at` | When StoreRouter last refreshed the product |
309
+
310
+ StoreRouter adds `enrichment` itself. It holds:
311
+
312
+ - the product `type`;
313
+ - a taxonomy `category_path`, root first;
314
+ - `color_family`, `gender`, `age_groups` and `styles`;
315
+ - category-specific `attributes`: fit and fabric for apparel, skin type for
316
+ skincare, notes for fragrance, upholstery for furniture;
317
+ - a `canonical_brand` profile;
318
+ - a one-line `summary`.
319
+
320
+ The full machine-readable schema is the
321
+ [OpenAPI document](https://cdn.storerouter.ai/openapi/platform/v1/openapi.json).
322
+
323
+ ## Errors and retries
324
+
325
+ The client retries 429, 500, 502, 503 and 504 responses, and connections that
326
+ fail to open. It waits for `Retry-After` when the server sends one, and uses
327
+ exponential backoff otherwise. Read timeouts are not retried. Set the number
328
+ of retries with `StoreRouter(max_retries=...)` (the default is 2, and 0
329
+ disables retries).
330
+
331
+ When a call fails, it raises a typed error. Each one is an alias of the
332
+ matching `octogen_ai_sdk` error:
333
+
334
+ | Error | When |
335
+ | ------------------------- | ---------------------------------------------------- |
336
+ | `MissingAPIKeyError` | No key passed, and none in the environment |
337
+ | `AuthenticationError` | 401: the key is invalid |
338
+ | `InsufficientCreditError` | 402: the organization is out of credit |
339
+ | `PermissionDeniedError` | 403: the key cannot use this endpoint or catalog |
340
+ | `NotFoundError` | 404: product not found, or store not covered |
341
+ | `ValidationError` | 422: invalid request |
342
+ | `RateLimitError` | 429 after retries; `error.retry_after` is in seconds |
343
+ | `InternalServerError` | 5xx after retries |
344
+ | `APITimeoutError` | The request timed out |
345
+ | `APIConnectionError` | Network failure |
346
+
347
+ `APIError` is the base class of every HTTP error, and `StoreRouterError` is
348
+ the base class of them all. HTTP errors carry `status_code`, `detail` and
349
+ `request_id`.
350
+
351
+ ## Configuration
352
+
353
+ | Setting | Argument | Environment variable | Default |
354
+ | -------- | --------------------------------------- | ------------------------------------------------------- | ------------------------------- |
355
+ | API key | `api_key=` | `STOREROUTER_API_KEY` (then `OCTOGEN_PLATFORM_API_KEY`) | required |
356
+ | Base URL | `base_url=` | `STOREROUTER_BASE_URL` | `https://api.storerouter.ai/v1` |
357
+ | Timeout | `timeout=` (seconds or `httpx.Timeout`) | | 60 s |
358
+ | Retries | `max_retries=` | | 2 |
359
+
360
+ Every method also accepts a per-call `timeout=`. Requests use
361
+ `Authorization: Bearer <key>`. Billing is per successful request, and every
362
+ organization gets free credit each month: see
363
+ [pricing](https://storerouter.ai/docs/guides/pricing-and-billing).
364
+
365
+ ## Other ways to use StoreRouter
366
+
367
+ - **MCP server for AI agents**: `https://mcp.dev.storerouter.ai/mcp`
368
+ (Streamable HTTP with OAuth). It gives Claude, ChatGPT, Cursor and other MCP
369
+ clients product search, product lookup, and store and brand tools. See the
370
+ [setup guides](https://storerouter.ai/docs/mcp/overview).
371
+ - **LangChain**: [`langchain-storerouter`](https://pypi.org/project/langchain-storerouter/)
372
+ - **TypeScript**: [`@storerouter/sdk`](https://www.npmjs.com/package/@storerouter/sdk)
373
+ - **CLI**: [`@storerouter/cli`](https://www.npmjs.com/package/@storerouter/cli)
374
+ - **REST**: the [API reference](https://storerouter.ai/docs/api-reference)
375
+ and the [OpenAPI document](https://cdn.storerouter.ai/openapi/platform/v1/openapi.json)
376
+
377
+ `storerouter` is built on [`octogen-ai-sdk`](https://pypi.org/project/octogen-ai-sdk/),
378
+ the lower-level async SDK for the same API. It adds the sync client, retries,
379
+ batch lookup and the `STOREROUTER_*` settings. Every `octogen_ai_sdk` model is
380
+ available from `storerouter.models`.
381
+
382
+ ## Documentation
383
+
384
+ - [StoreRouter docs](https://storerouter.ai/docs)
385
+ - [API reference](https://storerouter.ai/docs/api-reference)
386
+ - [MCP server](https://storerouter.ai/docs/mcp/overview)
387
+ - [Source and issues](https://github.com/octogen-ai/octogen-dev/tree/main/sdks/storerouter-python)
388
+
389
+ ## License
390
+
391
+ Apache-2.0