browse_catalog
browse_catalog returns a bounded array of catalog products for an optional free-text query and category filter. All request fields are optional; a call with an empty arguments object returns the first page of the catalog.
Request
Section titled “Request”Machine-readable: /skills/v1/browse_catalog.request.schema.json
| Field | Type | Required | Notes |
|---|---|---|---|
query | string | no | Free-text search over title / description. 1–256 chars. |
category | string | no | Category / collection filter. 1–128 chars. |
limit | integer | no | Page size. 1–50. Default at your discretion (10 is typical). |
cursor | string | no | Opaque page cursor returned in a prior next_cursor. 1–512 chars. |
{ "additionalProperties": false, "properties": { "category": { "maxLength": 128, "minLength": 1, "type": "string" }, "cursor": { "maxLength": 512, "minLength": 1, "type": "string" }, "limit": { "maximum": 50, "minimum": 1, "type": "integer" }, "query": { "maxLength": 256, "minLength": 1, "type": "string" } }, "type": "object"}Example request
Section titled “Example request”{ "skill_id": "browse_catalog", "site_id": "01EXAMPLE00000000000000000", "arguments": { "query": "medium roast", "limit": 10 }, "observed_at": "2026-07-05T18:22:15.140Z", "nonce": "01K1EXAMPLE0000000000000000"}Response
Section titled “Response”Machine-readable: /skills/v1/browse_catalog.response.schema.json
Return a JSON body with:
| Field | Type | Required | Notes |
|---|---|---|---|
products | array | yes | Up to 128 products matching the query. |
next_cursor | string | null | yes | Opaque cursor for the next page, or null when the last page was returned. 1–512 chars if present. |
Each product has:
| Field | Type | Required | Notes |
|---|---|---|---|
sku | string | yes | Your SKU. 1–256 chars. |
title | string | yes | Product title. 1–512 chars. |
price | object | yes | { amount, amount_decimal, currency }. |
availability | enum | yes | in_stock | low_stock | out_of_stock | unknown. |
description | string | no | 1–2048 chars. |
url | string | no | 1–2048 chars. |
image_url | string | no | 1–2048 chars. |
brand | string | no | 1–256 chars. |
variants | array | no | Present on multi-variant products only; up to 64. |
options | array | no | Present on multi-variant products only; up to 32. |
has_more_variants | boolean | no | true when the product has more variants than were returned. |
The price object is a minor-unit integer with a paired decimal string and ISO 4217 currency code:
| Field | Type | Notes |
|---|---|---|
amount | integer | Minor units (cents / pence / smallest atomic unit). |
amount_decimal | string | Same value as a decimal string — "30.00" for USD 3000. 1–64 chars. |
currency | string | ISO 4217 code, uppercase. 3–8 chars. |
Return amount and amount_decimal consistently — a decimal representation that disagrees with the minor-unit integer is a validation failure.
Full response schema
Section titled “Full response schema”{ "additionalProperties": false, "properties": { "next_cursor": { "maxLength": 512, "type": ["string", "null"] }, "products": { "items": { "additionalProperties": false, "properties": { "availability": { "enum": ["in_stock", "low_stock", "out_of_stock", "unknown"], "type": "string" }, "brand": { "maxLength": 256, "minLength": 1, "type": "string" }, "description": { "maxLength": 2048, "minLength": 1, "type": "string" }, "has_more_variants": { "type": "boolean" }, "image_url": { "maxLength": 2048, "minLength": 1, "type": "string" }, "options": { "items": { "additionalProperties": false, "properties": { "name": { "maxLength": 128, "minLength": 1, "type": "string" }, "values": { "items": { "maxLength": 256, "minLength": 1, "type": "string" }, "maxItems": 64, "type": "array" } }, "required": ["name", "values"], "type": "object" }, "maxItems": 32, "type": "array" }, "price": { "additionalProperties": false, "properties": { "amount": { "type": "number" }, "amount_decimal": { "maxLength": 64, "minLength": 1, "type": "string" }, "currency": { "maxLength": 8, "minLength": 3, "type": "string" } }, "required": ["amount", "amount_decimal", "currency"], "type": "object" }, "sku": { "maxLength": 256, "minLength": 1, "type": "string" }, "title": { "maxLength": 512, "minLength": 1, "type": "string" }, "url": { "maxLength": 2048, "minLength": 1, "type": "string" }, "variants": { "items": { "additionalProperties": false, "properties": { "availability": { "enum": ["in_stock", "low_stock", "out_of_stock", "unknown"], "type": "string" }, "options": { "items": { "additionalProperties": false, "properties": { "name": { "maxLength": 128, "minLength": 1, "type": "string" }, "value": { "maxLength": 256, "minLength": 1, "type": "string" } }, "required": ["name", "value"], "type": "object" }, "maxItems": 32, "type": "array" }, "price": { "additionalProperties": false, "properties": { "amount": { "type": "number" }, "amount_decimal": { "maxLength": 64, "minLength": 1, "type": "string" }, "currency": { "maxLength": 8, "minLength": 3, "type": "string" } }, "required": ["amount", "amount_decimal", "currency"], "type": "object" }, "sku": { "maxLength": 256, "minLength": 1, "type": "string" }, "title": { "maxLength": 512, "minLength": 1, "type": "string" } }, "required": ["sku", "title", "price", "availability"], "type": "object" }, "maxItems": 64, "type": "array" } }, "required": ["sku", "title", "price", "availability"], "type": "object" }, "maxItems": 128, "type": "array" } }, "required": ["products", "next_cursor"], "type": "object"}Example response
Section titled “Example response”{ "products": [ { "sku": "beans_medium_12oz", "title": "Medium roast, 12 oz bag", "description": "Ethiopian, natural process.", "price": { "amount": 1800, "amount_decimal": "18.00", "currency": "USD" }, "availability": "in_stock", "url": "https://example.com/products/beans-medium-12oz" } ], "next_cursor": "opaque-page-2-token"}What Sill checks
Section titled “What Sill checks”Sill validates your response against the canonical schema before passing anything to the agent. Common rejections:
- Extra top-level or nested fields.
additionalProperties: falseapplies at every object depth — a strayinternal_noteson a product is rejected asmalformed_response. - Wrong
availabilityvalue. Must be one of the four enum values above. - Inconsistent
price.amountvsprice.amount_decimal. Sill does not cross-check the two numerically, but the merchant contract commits to keeping them consistent — an agent will surface the decimal string to a shopper.
Verifying Sill’s signature
Section titled “Verifying Sill’s signature”The signature scheme is identical for every skill. See the contract overview for full TypeScript, Python, and Go samples that verify the X-Sill-Signature: t=<unix>,v1=<hex> header where <hex> is HMAC-SHA256("<t>.<raw-body>") with your shared secret.
TypeScript (compact)
Section titled “TypeScript (compact)”import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header: string, body: string, secret: string): boolean { const map = Object.fromEntries(header.split(',').map((p) => p.split('=', 2))); const t = Number(map.t); const sig = String(map.v1 ?? ''); if (!Number.isFinite(t) || sig.length === 0) return false; if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false; const expected = createHmac('sha256', secret).update(`${t}.${body}`).digest(); const got = Buffer.from(sig, 'hex'); if (expected.length !== got.length) return false; return timingSafeEqual(expected, got);}Python (compact)
Section titled “Python (compact)”import hmac, hashlib, time
def verify(header: str, body: bytes, secret: bytes) -> bool: parts = dict(p.split('=', 1) for p in header.split(',') if '=' in p) try: t = int(parts['t']) except (KeyError, ValueError): return False sig = parts.get('v1', '') if abs(int(time.time()) - t) > 300 or not sig: return False expected = hmac.new(secret, f'{t}.'.encode() + body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, sig)Go (compact)
Section titled “Go (compact)”func Verify(header string, body []byte, secret []byte) bool { var t int64 = -1 var sig string for _, p := range strings.Split(header, ",") { kv := strings.SplitN(p, "=", 2) if len(kv) != 2 { continue } switch kv[0] { case "t": n, err := strconv.ParseInt(kv[1], 10, 64) if err != nil { return false } t = n case "v1": sig = kv[1] } } if t < 0 || sig == "" { return false } diff := time.Now().Unix() - t if diff < 0 { diff = -diff } if diff > 300 { return false } mac := hmac.New(sha256.New, secret) mac.Write([]byte(strconv.FormatInt(t, 10) + ".")) mac.Write(body) got, err := hex.DecodeString(sig) if err != nil { return false } return hmac.Equal(mac.Sum(nil), got)}See also
Section titled “See also”- Contract overview — envelope shape, versioning, and the full HMAC verification samples.
check_availability— single-SKU stock lookup, called after abrowse_catalogselection.