Product Catalog Validator
commerce.catalog_validate
Overview
You send products and their variants, and we report the domain problems: a SKU used twice, a variant whose option value the product never declared, a declared combination with no variant behind it, a variant priced in a different currency from its product, a required field left blank, a parentId that does not resolve, a cycle, or a grandchild in a catalogue that only allows one level. A malformed request fails; a badly modelled catalogue produces issues — reporting them is the job, so we do not refuse the catalogue we were asked to inspect. Every issue carries a JSON Pointer into the document you sent, so you navigate to the problem instead of searching for it. There is no schema input: structural conformance belongs to schema.validate_json, and this skill does no JSON-Schema validation at all.
The problems that break a storefront are rarely structural. The JSON is well formed and the schema passes, and yet two products share a SKU, a variant carries a colour the product never declared, half the size matrix is missing, and one product's parent was deleted last month. Those are relationships across the document, which a schema cannot express, and this skill exists to check exactly them — and to point at each one with a pointer you can follow.
Reliability
Fully deterministic: the same catalogue under the same rules produces the same issues, in the same order — product by product in document order, with parent problems last — so two runs can be compared line by line. The report is bounded: issues stop at rules.maxIssues while the counts keep counting, so summary.issueCount can exceed the array and truncated says so. A catalogue with problems is a successful call: only a malformed request fails. The line between the two is attribution, not severity: every issue is attributed to a product id, so a repeated id means the report could not say which product a problem belongs to and the call fails, while a repeated SKU is attributable and is therefore reported as SKU_DUPLICATE rather than refused. A property this skill does not implement is refused, not ignored: an unknown top-level property is a broken request and comes back as the platform's TOOL_INPUT_INVALID, one on a product or variant is CATALOG_INVALID, and one in rules is CATALOG_RULES_INVALID. Failures, and what to do about each. CATALOG_INVALID — trigger: a product has no id, has a blank one, one longer than 128 characters, or repeats one; a product or variant carries an unknown property; options, media or optionValues are the wrong shape; or a product declares more than 8 options, more than 100 values for one, or more than 200 media entries; retryable: no, the same request always fails; next: fix the shape at the path we name. Note that a badly modelled catalogue is not this — it comes back as issues. CATALOG_LIMIT_EXCEEDED — trigger: more than 2000 products, or a COMPLETE matrix with more than 20000 combinations for one product — which needs several options multiplied together, since one option is capped at 100 values and 101 of them is CATALOG_INVALID first; retryable: no; next: validate in batches, or use SUBSET for the product with the enormous matrix. CATALOG_VARIANT_LIMIT_EXCEEDED — trigger: more than 200 variants on one product, or more than 20000 in the call; retryable: no; next: split the catalogue, or model the extra variants as a separate product. CATALOG_RULES_INVALID — trigger: a rule names a field that does not exist on its shape, requiredProductFields or requiredVariantFields is an empty array, an enum is outside its values, maxIssues is outside 1 to 10000, or rules carries a property we do not implement; retryable: no; next: fix the rule we name.
Context footprint
The report is bounded by rules.maxIssues, default 1000, so a systematically broken 20000-variant catalogue answers with 1000 issues and an honest total rather than 20000 entries. Three ceilings bound a request — 2000 products, 20000 variants, and 1 MiB — and the counts are reachable inside the bytes when entries are minimal: 2001 bare products is about 30 KB and 20001 bare variants about 700 KB. A realistic catalogue is far heavier per entry, so a full one meets the 1 MiB request ceiling long before either count, and is then refused for size rather than answered; send it in batches. A successful call costs 1 execution credit.
When to use it
Before a catalogue import, a launch, or a channel export, when you need to know whether the data is coherent and not merely well formed.
When the question is structural conformance to a schema, or when you want the data cleaned rather than reported on.
Scope
Non-goals
- Does not validate against a JSON Schema, and takes no schema input — schema.validate_json is the platform's one JSON-Schema validator
- Does not fetch the catalogue from anywhere; it checks what you send
- Does not fix, normalise or deduplicate anything
- Does not check stock, publish, or change a product's status
- Does not validate a SKU's format — commerce.sku_normalize is that skill
- Does not check prices against a market or a competitor
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| catalog | object | Yes | Closed catalog object with bounded products, variants, categories, and currency properties. |
| rules | object | No | Closed catalog-rule object; omit it to apply the documented safe defaults. Its documented commercial properties are bounded to 256 entries — unconstrained RFC 8259 JSON items. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| valid | boolean | Yes | Whether no error-severity catalog issue exists. |
| issues | array | Yes | Bounded array of catalog issue receipts with severity, code, path, and message. The item shape is defined by the nested schema and the array contains from 0 through 100000 items. |
| summary | object | Yes | Closed product, variant, category, error, and warning count summary. Its documented commercial properties are bounded to 256 entries — unconstrained RFC 8259 JSON items. |
| rules | object | Yes | Closed resolved catalog rules with defaults filled in. Its documented commercial properties are bounded to 256 entries — unconstrained RFC 8259 JSON items. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"commerce.catalog_validate","input":{"catalog":{"products":[{"id":"P-100","title":"Basic T-Shirt","price":"19.90","currency":"EUR"}]}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("commerce.catalog_validate", {"catalog":{"products":[{"sku":"TSHIRT-BASE","media":["https://cdn.example.com/tshirt.jpg"],"status":"ACTIVE","currency":"EUR","options":[{"values":["S","M"],"name":"size"},{"values":["black"],"name":"colour"}],"price":"19.90","title":"Basic T-Shirt","id":"P-100","variants":[{"id":"V-1","optionValues":{"colour":"black","size":"S"},"sku":"TSHIRT-S-BLACK","price":"19.90","currency":"EUR"},{"id":"V-2","optionValues":{"colour":"black","size":"M"},"sku":"TSHIRT-M-BLACK","price":"19.90","currency":"EUR"}]}]}});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| CATALOG_INVALID | 400 | No | The request payload failed this skill's input validation. |
| CATALOG_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| CATALOG_VARIANT_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| CATALOG_RULES_INVALID | 400 | No | The request payload failed this skill's input validation. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not validate against a JSON Schema, and takes no schema input — schema.validate_json is the platform's one JSON-Schema validator
- Does not fetch the catalogue from anywhere; it checks what you send
- Does not fix, normalise or deduplicate anything
- Does not check stock, publish, or change a product's status
- Does not validate a SKU's format — commerce.sku_normalize is that skill
- Does not check prices against a market or a competitor
- Checking that a document conforms to a JSON Schema
- Normalising or repairing product data
- Deciding what a product should be priced at
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "commerce.catalog_validate",
"input": {
"catalog": {
"products": [
{
"sku": "TSHIRT-BASE",
"media": [
"https://cdn.example.com/tshirt.jpg"
],
"status": "ACTIVE",
"currency": "EUR",
"options": [
{
"values": [
"S",
"M"
],
"name": "size"
},
{
"values": [
"black"
],
"name": "colour"
}
],
"price": "19.90",
"title": "Basic T-Shirt",
"id": "P-100",
"variants": [
{
"id": "V-1",
"optionValues": {
"colour": "black",
"size": "S"
},
"sku": "TSHIRT-S-BLACK",
"price": "19.90",
"currency": "EUR"
},
{
"id": "V-2",
"optionValues": {
"colour": "black",
"size": "M"
},
"sku": "TSHIRT-M-BLACK",
"price": "19.90",
"currency": "EUR"
}
]
}
]
}
}
}Examples
A clean two-variant product under the default rules
{
"catalog": {
"products": [
{
"sku": "TSHIRT-BASE",
"media": [
"https://cdn.example.com/tshirt.jpg"
],
"status": "ACTIVE",
"currency": "EUR",
"options": [
{
"values": [
"S",
"M"
],
"name": "size"
},
{
"values": [
"black"
],
"name": "colour"
}
],
"price": "19.90",
"title": "Basic T-Shirt",
"id": "P-100",
"variants": [
{
"id": "V-1",
"optionValues": {
"colour": "black",
"size": "S"
},
"sku": "TSHIRT-S-BLACK",
"price": "19.90",
"currency": "EUR"
},
{
"id": "V-2",
"optionValues": {
"colour": "black",
"size": "M"
},
"sku": "TSHIRT-M-BLACK",
"price": "19.90",
"currency": "EUR"
}
]
}
]
}
}{
"valid": true,
"summary": {
"productCount": 1,
"variantCount": 2,
"issueCount": 0,
"bySeverity": {},
"truncated": false
},
"rules": {
"requiredProductFields": [
"id",
"title",
"price",
"currency"
],
"requiredVariantFields": [
"id",
"sku",
"optionValues"
],
"skuUniqueness": "GLOBAL",
"optionMatrix": "SUBSET",
"requireMedia": false,
"priceCoherence": true,
"maxIssues": 1000
},
"issues": []
}A catalogue with a duplicate SKU, a currency mismatch, an undeclared option value, a gap in a COMPLETE matrix, a missing title, a negative price and an unresolved parent
{
"rules": {
"optionMatrix": "COMPLETE"
},
"catalog": {
"products": [
{
"title": "Blue Mug",
"id": "P-100",
"price": "9.90",
"options": [
{
"values": [
"S",
"M"
],
"name": "size"
}
],
"currency": "EUR",
"sku": "MUG-BLUE",
"variants": [
{
"sku": "MUG-BLUE-S",
"optionValues": {
"size": "S"
},
"currency": "EUR",
"id": "V-1"
},
{
"sku": "MUG-BLUE-S",
"optionValues": {
"size": "XL"
},
"currency": "USD",
"id": "V-2"
}
]
},
{
"id": "P-200",
"parentId": "P-999",
"sku": "MUG-RED",
"price": "-3.00",
"currency": "EUR"
}
]
}
}{
"valid": false,
"summary": {
"productCount": 2,
"variantCount": 2,
"issueCount": 7,
"bySeverity": {
"ERROR": 7
},
"truncated": false
},
"rules": {
"requiredProductFields": [
"id",
"title",
"price",
"currency"
],
"requiredVariantFields": [
"id",
"sku",
"optionValues"
],
"skuUniqueness": "GLOBAL",
"optionMatrix": "COMPLETE",
"requireMedia": false,
"priceCoherence": true,
"maxIssues": 1000
},
"issues": [
{
"code": "SKU_DUPLICATE",
"severity": "ERROR",
"path": "/catalog/products/0/variants/1/sku",
"productId": "P-100",
"variantId": "V-2",
"detail": "This SKU already appears earlier in the catalogue, and rules.skuUniqueness is GLOBAL."
},
{
"code": "CURRENCY_MISMATCH",
"severity": "ERROR",
"path": "/catalog/products/0/variants/1/currency",
"productId": "P-100",
"variantId": "V-2",
"detail": "The variant declares a different currency from its product, so one product would be priced in two currencies."
},
{
"code": "OPTION_VALUE_UNDECLARED",
"severity": "ERROR",
"path": "/catalog/products/0/variants/1/optionValues/size",
"productId": "P-100",
"variantId": "V-2",
"detail": "The variant's value for option 'size' is not among the values the product declares for it."
},
{
"code": "OPTION_COMBINATION_MISSING",
"severity": "ERROR",
"path": "/catalog/products/0/variants",
"productId": "P-100",
"detail": "No variant exists for the declared combination size=M."
},
{
"code": "REQUIRED_FIELD_MISSING",
"severity": "ERROR",
"path": "/catalog/products/1/title",
"productId": "P-200",
"detail": "Required product field 'title' is missing or blank."
},
{
"code": "PRICE_INVALID",
"severity": "ERROR",
"path": "/catalog/products/1/price",
"productId": "P-200",
"detail": "The price is negative."
},
{
"code": "PARENT_UNRESOLVED",
"severity": "ERROR",
"path": "/catalog/products/1/parentId",
"productId": "P-200",
"detail": "parentId names a product that is not in this catalogue."
}
]
}Recommended use cases
- Gate a catalogue import before it reaches your storefront
- Find the variant that was quietly created with an option value nobody declared
- Prove a size-and-colour matrix is complete before a launch
Avoid for
- Checking that a document conforms to a JSON Schema
- Normalising or repairing product data
- Deciding what a product should be priced at
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.