{"openapi":"3.1.0","info":{"title":"Stylumia Catalog API","version":"0.2.0","description":"Read-only access to Stylumia's retailer product catalog crawl.\n\n**Every operation in this document is a draft** (`x-status: draft`). Shapes,\nfield names, and error codes may still change before general availability;\ntreat this as a preview, not a stability guarantee.\n\n## One filter set, three operations\n\n`retailer`, `category`, `department`, `rcd`, `start_date`, and `end_date`\nare the same six parameters on every operation below — narrow once and\nreuse the query anywhere:\n\n- `GET /v1/sample` — up to 100 rows, for judging the shape of the data.\n- `GET /v1/catalog` — the complete filtered set, cursor-paginated.\n- `GET /v1/coverage` — how much exists for those filters, before you pull\n  any of it.\n\nThere is no discovery chain to walk. A guessed `retailer`, `category`, or\n`department` fails with `valid_options` naming the real values, which is\nthe only vocabulary lookup an integrator needs. `rcd` is shorthand for a\n`retailer:category:department` combination — send one parameter instead\nof three, or several comma-separated to select more than one combination.\n\nEvery response echoes the filters the server actually resolved in\n`meta.resolved`, and carries a compact `meta.coverage` block, so one\nrequest answers both \"what did I get\" and \"how much is there\".\n\n## Error contract\n\nEvery non-2xx response is an RFC 9457 problem document\n(`application/problem+json`) shaped by the `Problem` schema, carrying\n`code`, `retryable`, `recovery`, and — on vocabulary errors —\n`valid_options`. `retryable` is the field to branch on: `false` means fix\nthe request before resending, `true` means back off and retry. `recovery`\nis human-readable guidance that matches the public error catalogue\nbyte-for-byte. Each operation below documents the exact set of codes it\ncan emit via `x-error-codes`.\n\n## Credentials\n\nSign in to the developer portal, create a named API key, and copy it — the\nsecret is shown exactly once. Send it as\n`Authorization: Bearer <your API key>`.\n\nKeys do not expire; revoking is the only thing that ends one, and it takes\neffect on the very next request. Create as many as you need — one per\nservice, script or agent — so a leak or a revocation never takes down more\nthan the one integration.\n"},"servers":[{"url":"https://api.stylumia.com"}],"tags":[{"name":"catalog","description":"Read-only retailer product catalog."}],"security":[{"bearerAuth":[]}],"paths":{"/v1/sample":{"get":{"operationId":"getSample","summary":"Sample rows","description":"Up to 100 rows for the given filters. Uncursored — a sample is for\njudging the shape of the data before committing to a full\n`GET /v1/catalog` walk; anything larger belongs there instead.\n\nRequires the `catalog:sample` scope, distinct from the `catalog:read`\nscope `GET /v1/catalog` and `GET /v1/coverage` need.\n","tags":["catalog"],"x-status":"draft","x-required-scope":"catalog:sample","x-error-codes":["invalid_request","unknown_retailer","unknown_category","unknown_department","invalid_date_range","missing_credentials","invalid_credentials","insufficient_scope","rate_limited","service_unavailable","upstream_timeout","internal_error"],"parameters":[{"$ref":"#/components/parameters/Retailer"},{"$ref":"#/components/parameters/Category"},{"$ref":"#/components/parameters/Department"},{"$ref":"#/components/parameters/Rcd"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"}],"responses":{"200":{"description":"Up to 100 sample product rows matching the given filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"5XX":{"$ref":"#/components/responses/ServerError"}}}},"/v1/catalog":{"get":{"operationId":"getCatalog","summary":"Get catalog","description":"The complete filtered set, walked with a cursor. Results are sorted\nby `sku` so a page's contents never shift under a caller mid-walk.\n\nThe cursor encodes position only — the same filters must be resent\nunchanged on every page. Continue with the `cursor` from\n`pagination.next_cursor` until `pagination.has_more` is `false`.\n\nRequires the `catalog:read` scope.\n","tags":["catalog"],"x-status":"draft","x-required-scope":"catalog:read","x-error-codes":["invalid_request","unknown_retailer","unknown_category","unknown_department","invalid_date_range","invalid_cursor","missing_credentials","invalid_credentials","insufficient_scope","rate_limited","service_unavailable","upstream_timeout","internal_error"],"parameters":[{"$ref":"#/components/parameters/Retailer"},{"$ref":"#/components/parameters/Category"},{"$ref":"#/components/parameters/Department"},{"$ref":"#/components/parameters/Rcd"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor from a previous response's `pagination.next_cursor`. Omit for the first page. A cursor this server did not issue fails with `invalid_cursor`.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows to return in this page.","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"A page of product rows matching the given filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"5XX":{"$ref":"#/components/responses/ServerError"}}}},"/v1/coverage":{"get":{"operationId":"getCoverage","summary":"Get coverage","description":"Coverage metrics for each retailer/category/department combination\nmatching the given filters: how many rows the full corpus holds,\nhow many match this filter, and the crawl-date range they span. Use\nthis to decide which `start_date` / `end_date` window on\n`GET /v1/catalog` will actually return rows before spending a call\nfinding out.\n\nRequires the `catalog:read` scope.\n","tags":["catalog"],"x-status":"draft","x-required-scope":"catalog:read","x-error-codes":["invalid_request","unknown_retailer","unknown_category","unknown_department","invalid_date_range","missing_credentials","invalid_credentials","insufficient_scope","rate_limited","service_unavailable","upstream_timeout","internal_error"],"parameters":[{"$ref":"#/components/parameters/Retailer"},{"$ref":"#/components/parameters/Category"},{"$ref":"#/components/parameters/Department"},{"$ref":"#/components/parameters/Rcd"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"}],"responses":{"200":{"description":"Coverage metrics for every combination matching the given filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"5XX":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in the developer portal: 43 opaque characters with no prefix. Send it as `Authorization: Bearer <your API key>`. Keys do not expire; revoke one in the console and it stops working on the next request."}},"parameters":{"Retailer":{"name":"retailer","in":"query","required":false,"description":"Restrict to this retailer's slug. An unrecognized value fails with `unknown_retailer`, whose `valid_options` names the closest real slugs.","schema":{"type":"string","example":"home_depot"}},"Category":{"name":"category","in":"query","required":false,"description":"Restrict to this category's slug, scoped by `retailer` if given. An unrecognized value fails with `unknown_category`, whose `valid_options` names the closest real slugs.","schema":{"type":"string","example":"power_tools"}},"Department":{"name":"department","in":"query","required":false,"description":"Restrict to this department's slug, scoped by `retailer` if given. An unrecognized value fails with `unknown_department`, whose `valid_options` names the closest real slugs.","schema":{"type":"string","example":"tools"}},"Rcd":{"name":"rcd","in":"query","required":false,"description":"One or more `retailer:category:department` triples, comma-separated. Shorthand for sending `retailer`, `category`, and `department` individually — send this instead when you already know the exact combination(s) you want. A malformed value fails with `invalid_request`.","schema":{"type":"string","example":"home_depot:power_tools:tools"}},"StartDate":{"name":"start_date","in":"query","required":false,"description":"Only rows with `crawl_date` on or after this date, inclusive. ISO 8601 (`YYYY-MM-DD`). Malformed, or after `end_date`, fails with `invalid_date_range`.","schema":{"type":"string","format":"date","example":"2026-06-14"}},"EndDate":{"name":"end_date","in":"query","required":false,"description":"Only rows with `crawl_date` on or before this date, inclusive. ISO 8601 (`YYYY-MM-DD`). Malformed, or before `start_date`, fails with `invalid_date_range`.","schema":{"type":"string","format":"date","example":"2026-08-14"}}},"responses":{"BadRequest":{"description":"The request is malformed, or references an unrecognized retailer, category, department, date range, cursor, or `rcd` value.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthorized":{"description":"No credentials were presented, or the presented token is unknown, expired, or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"The token is valid but the team is not entitled to this operation.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateLimited":{"description":"The published per-team rate limit was exceeded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"ServerError":{"description":"Something unforeseen failed on the server side, the service is down or degraded, or the server was too slow to answer. The request was not necessarily the problem — see `retryable`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"Product":{"type":"object","description":"A single crawled product row.","required":["product_id","sku","url","domain","retailer","department","category","brand","model_number","title","mrp","selling_price","discount_pct","currency","color","image_url","crawl_date"],"properties":{"product_id":{"type":"string","description":"Stable id, a content hash of the row. Listings are re-crawled, not diffed against a permanent catalog: a product relisted under a new title mints a new `product_id`, it does not update the old one.","example":"prd_5741354fa5d111f0a1ce7bcc"},"sku":{"type":"string","description":"Retailer-specific product identifier. Format varies by retailer. Unique within a retailer only — the same `sku` string can appear under a different retailer for an unrelated product.","example":"1003097550"},"url":{"type":"string","format":"uri","description":"Canonical URL of the product page as crawled.","example":"https://www.homedepot.com/p/1003097550"},"domain":{"type":"string","description":"Domain the product page was crawled from.","example":"homedepot.com"},"retailer":{"type":"string","description":"Slug of the retailer this row was crawled from.","example":"home_depot"},"department":{"type":"string","description":"Slug of the department this row belongs to.","example":"tools"},"category":{"type":"string","description":"Slug of the category this row belongs to.","example":"power_tools"},"brand":{"type":"string","description":"Brand name as listed on the retailer's page.","example":"DEWALT"},"model_number":{"type":"string","description":"Manufacturer model number as listed on the retailer's page.","example":"DCD771C2"},"title":{"type":"string","description":"Product title as listed on the retailer's page.","example":"20V MAX Cordless Drill/Driver Kit"},"mrp":{"type":"number","description":"Manufacturer's listed (undiscounted) price, in `currency`.","example":179.0},"selling_price":{"type":"number","description":"Actual price charged at crawl time, in `currency`.","example":139.0},"discount_pct":{"type":"number","description":"Discount off `mrp`, as a percentage, at crawl time.","example":22.3},"currency":{"type":"string","description":"ISO 4217 currency code for `mrp` and `selling_price`.","example":"USD"},"color":{"type":["string","null"],"description":"Listed color, when the retailer publishes one for this product. `null` when not applicable or not listed.","example":"Yellow"},"image_url":{"type":"string","format":"uri","description":"URL of the primary product image as crawled.","example":"https://images.example.com/1003097550.jpg"},"crawl_date":{"type":"string","format":"date","description":"Date (`YYYY-MM-DD`) this row was crawled.","example":"2026-06-14"}},"example":{"product_id":"prd_5741354fa5d111f0a1ce7bcc","sku":"1003097550","url":"https://www.homedepot.com/p/1003097550","domain":"homedepot.com","retailer":"home_depot","department":"tools","category":"power_tools","brand":"DEWALT","model_number":"DCD771C2","title":"20V MAX Cordless Drill/Driver Kit","mrp":179.0,"selling_price":139.0,"discount_pct":22.3,"currency":"USD","color":"Yellow","image_url":"https://images.example.com/1003097550.jpg","crawl_date":"2026-06-14"}},"Coverage":{"type":"object","description":"Coverage metrics for one retailer/category/department combination.","required":["retailer","department","category","rcd","corpus_rows","matching_rows","earliest_date","latest_date"],"properties":{"retailer":{"type":"string","description":"Slug of the retailer.","example":"home_depot"},"department":{"type":"string","description":"Slug of the department.","example":"tools"},"category":{"type":"string","description":"Slug of the category.","example":"power_tools"},"rcd":{"type":"string","description":"This combination as a `retailer:category:department` triple, ready to pass back as `rcd`.","example":"home_depot:power_tools:tools"},"corpus_rows":{"type":"integer","description":"Total rows the full dataset holds for this combination, independent of any filter applied.","example":2361},"matching_rows":{"type":"integer","description":"Rows within this combination that also satisfy the request's date filters, if any.","example":6},"earliest_date":{"type":"string","format":"date","description":"Earliest `crawl_date` (`YYYY-MM-DD`) among matching rows.","example":"2026-06-14"},"latest_date":{"type":"string","format":"date","description":"Most recent `crawl_date` (`YYYY-MM-DD`) among matching rows.","example":"2026-08-14"}},"example":{"retailer":"home_depot","department":"tools","category":"power_tools","rcd":"home_depot:power_tools:tools","corpus_rows":2361,"matching_rows":6,"earliest_date":"2026-06-14","latest_date":"2026-08-14"}},"CoverageSummary":{"type":"object","description":"The compact coverage block carried on every response, answering \"how much exists\" alongside \"what did I get\" — summed across every combination matching the request's filters.","required":["combinations","matching_rows","corpus_rows","earliest_date","latest_date"],"properties":{"combinations":{"type":"integer","description":"Number of retailer/category/department combinations matching the request's filters.","example":3},"matching_rows":{"type":"integer","description":"Total rows across those combinations that also satisfy the request's date filters, if any.","example":14},"corpus_rows":{"type":"integer","description":"Total rows the full dataset holds across those combinations, independent of any date filter.","example":4301},"earliest_date":{"type":["string","null"],"format":"date","description":"Earliest `crawl_date` (`YYYY-MM-DD`) among matching rows. `null` when no combination matches.","example":"2026-06-14"},"latest_date":{"type":["string","null"],"format":"date","description":"Most recent `crawl_date` (`YYYY-MM-DD`) among matching rows. `null` when no combination matches.","example":"2026-08-14"}},"example":{"combinations":3,"matching_rows":14,"corpus_rows":4301,"earliest_date":"2026-06-14","latest_date":"2026-08-14"}},"Pagination":{"type":"object","description":"Cursor pagination state for this response. `limit` and `total_matching` are present on `GET /v1/catalog`; `GET /v1/sample` and `GET /v1/coverage` are uncursored and omit them.","required":["next_cursor","has_more"],"properties":{"limit":{"type":"integer","description":"Maximum rows requested for this page.","example":100},"total_matching":{"type":"integer","description":"Total rows matching the request's filters, across every page.","example":2361},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page. Pass back as the `cursor` parameter to continue. `null` when there is no further page."},"has_more":{"type":"boolean","description":"Whether a further page exists beyond this one."}},"example":{"limit":100,"total_matching":2361,"next_cursor":null,"has_more":false}},"Meta":{"type":"object","description":"Response metadata.","required":["request_id","resolved","coverage"],"properties":{"request_id":{"type":"string","description":"Unique id for this request. Quote it when reporting a problem, especially a 5xx.","example":"8f14e45f-ceea-467e-9d5f-9c3f2d1b7a3e"},"resolved":{"type":"object","description":"Every filter the server actually applied, including omitted ones as `null`, so a response is never ambiguous about what was queried.","required":["retailer","category","department","rcd","start_date","end_date"],"properties":{"retailer":{"type":["string","null"],"example":"home_depot"},"category":{"type":["string","null"],"example":"power_tools"},"department":{"type":["string","null"],"example":"tools"},"rcd":{"type":["string","null"],"example":null},"start_date":{"type":["string","null"],"format":"date","example":null},"end_date":{"type":["string","null"],"format":"date","example":null}}},"coverage":{"$ref":"#/components/schemas/CoverageSummary"}},"example":{"request_id":"8f14e45f-ceea-467e-9d5f-9c3f2d1b7a3e","resolved":{"retailer":"home_depot","category":"power_tools","department":"tools","rcd":null,"start_date":null,"end_date":null},"coverage":{"combinations":1,"matching_rows":6,"corpus_rows":2361,"earliest_date":"2026-06-14","latest_date":"2026-08-14"}}},"ProductListResponse":{"type":"object","description":"Response envelope for `GET /v1/sample` and `GET /v1/catalog`.","required":["data","pagination","meta"],"properties":{"data":{"type":"array","description":"The matching product rows for this page.","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}},"CoverageListResponse":{"type":"object","description":"Response envelope for `GET /v1/coverage`.","required":["data","pagination","meta"],"properties":{"data":{"type":"array","description":"Coverage metrics, one entry per matching retailer/category/department combination.","items":{"$ref":"#/components/schemas/Coverage"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}},"Problem":{"type":"object","description":"RFC 9457 problem details, extended for programmatic handling. Every non-2xx response has this shape, served as `application/problem+json`.","required":["type","title","status","detail","code","retryable","recovery","request_id"],"properties":{"type":{"type":"string","format":"uri","description":"Stable URI identifying this error type; resolves to its entry in the public error catalogue (`{base}/errors#{code}`).","example":"https://developer-api.stylumia.net/errors#unknown_department"},"title":{"type":"string","description":"Short, human-readable summary of the error type. Constant per `code`.","example":"Unknown department"},"status":{"type":"integer","description":"HTTP status code, repeated from the response line for convenience.","example":400},"detail":{"type":"string","description":"Human-readable explanation specific to this occurrence of the problem.","example":"'toolz' is not a recognized department for retailer 'home_depot'."},"code":{"type":"string","description":"Stable machine-readable error code. Append-only — safe to branch on in code.","example":"unknown_department"},"retryable":{"type":"boolean","description":"`false` means fix the request before resending; `true` means back off and retry — the request itself was not the problem.","example":false},"recovery":{"type":"string","description":"What to do about this error, byte-identical to the public error catalogue.","example":"Call GET /v1/coverage to list the retailer, category and department combinations available to you, or use one of the suggestions in valid_options."},"request_id":{"type":"string","description":"Unique id for the request that produced this error. Quote it when reporting a problem.","example":"8f14e45f-ceea-467e-9d5f-9c3f2d1b7a3e"},"field":{"type":"string","description":"Name of the request parameter at fault. Present only on errors tied to one parameter.","example":"department"},"valid_options":{"type":"array","description":"On vocabulary errors, the closest real values to what was sent (or the first few valid values), so a failed call can self-correct without a documentation round trip.","items":{"type":"string"},"example":["tools","appliances","building_materials"]}},"example":{"type":"https://developer-api.stylumia.net/errors#unknown_department","title":"Unknown department","status":400,"detail":"'toolz' is not a recognized department for retailer 'home_depot'.","code":"unknown_department","retryable":false,"recovery":"Call GET /v1/coverage to list the retailer, category and department combinations available to you, or use one of the suggestions in valid_options.","request_id":"8f14e45f-ceea-467e-9d5f-9c3f2d1b7a3e","field":"department","valid_options":["tools","appliances","building_materials"]}}}}}