Skip to content

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.

Machine-readable: /skills/v1/browse_catalog.request.schema.json

FieldTypeRequiredNotes
querystringnoFree-text search over title / description. 1–256 chars.
categorystringnoCategory / collection filter. 1–128 chars.
limitintegernoPage size. 1–50. Default at your discretion (10 is typical).
cursorstringnoOpaque 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"
}
{
"skill_id": "browse_catalog",
"site_id": "01EXAMPLE00000000000000000",
"arguments": {
"query": "medium roast",
"limit": 10
},
"observed_at": "2026-07-05T18:22:15.140Z",
"nonce": "01K1EXAMPLE0000000000000000"
}

Machine-readable: /skills/v1/browse_catalog.response.schema.json

Return a JSON body with:

FieldTypeRequiredNotes
productsarrayyesUp to 128 products matching the query.
next_cursorstring | nullyesOpaque cursor for the next page, or null when the last page was returned. 1–512 chars if present.

Each product has:

FieldTypeRequiredNotes
skustringyesYour SKU. 1–256 chars.
titlestringyesProduct title. 1–512 chars.
priceobjectyes{ amount, amount_decimal, currency }.
availabilityenumyesin_stock | low_stock | out_of_stock | unknown.
descriptionstringno1–2048 chars.
urlstringno1–2048 chars.
image_urlstringno1–2048 chars.
brandstringno1–256 chars.
variantsarraynoPresent on multi-variant products only; up to 64.
optionsarraynoPresent on multi-variant products only; up to 32.
has_more_variantsbooleannotrue 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:

FieldTypeNotes
amountintegerMinor units (cents / pence / smallest atomic unit).
amount_decimalstringSame value as a decimal string — "30.00" for USD 3000. 1–64 chars.
currencystringISO 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.

{
"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"
}
{
"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"
}

Sill validates your response against the canonical schema before passing anything to the agent. Common rejections:

  • Extra top-level or nested fields. additionalProperties: false applies at every object depth — a stray internal_notes on a product is rejected as malformed_response.
  • Wrong availability value. Must be one of the four enum values above.
  • Inconsistent price.amount vs price.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.

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.

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);
}
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)
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)
}
  • Contract overview — envelope shape, versioning, and the full HMAC verification samples.
  • check_availability — single-SKU stock lookup, called after a browse_catalog selection.