Analyzer tests
Template-based validation system for export attributes — assign reusable, parameterized validators from a curated catalog.
Analyzer test templates are the supported validation system for export attributes. You pick from a curated catalog of templates, supply a small JSON configuration, and the assignment runs against every row of the export.
Each template is parameterized and safe to expose to all authenticated users — no admin role required, and updates to the engine apply automatically without per-assignment migration.
Prerequisites
You need a valid access token. See Authentication for how to obtain one with the Client Credentials flow. Pass it as a Bearer token in every request:
-H "Authorization: Bearer <token>"Quick start
List available templates
curl "https://export-template-api.productsup.com/V2/analyzer-test-templates" \
-H "Authorization: Bearer <token>"This endpoint is the source of truth for the template catalog. Its response includes the live configuration schema for each template, so any new optional field (e.g. skipEmpty) appears here automatically once deployed.
Assign a template to an attribute
curl -X POST "https://export-template-api.productsup.com/V2/attributes/123/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "numeric_check", "configuration": {"value": 0, "operator": ">="}}]'The analyzer test is stored in the export template's draft. It takes effect in the platform after you publish the export template — see Publishing changes.
View results
curl "https://export-template-api.productsup.com/V1/export-templates/{templateId}/attributes" \
-H "Authorization: Bearer <token>"The response includes an analyzerTests array for each attribute showing all assigned validations.
How tests aggregate
Each assignment runs independently against every row. A row fails validation on an attribute if any assignment on that attribute flags it.
If you need "value must be in range A AND format B", create one assignment for each constraint and the row fails if either fires. If you need disjunction ("matches pattern A OR pattern B"), express it inside a single regex pattern using regex alternation.
Available templates
Numeric check
Validates numeric values against a threshold using comparison operators.
Use cases:
- Price must be non-negative.
- Stock quantity within range.
- Rating between 1 and 5.
Configuration:
{
"value": 0,
"operator": ">=",
"skipEmpty": false
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
value | number | yes | Numeric threshold to compare against. | |
operator | string | yes | One of >, <, >=, <=, =, !=. The validation passes when attribute [operator] value is true. | |
skipEmpty | boolean | no | false | When true, empty or null values pass this test. Empty values are then only flagged by a not_empty_validation test: it is added automatically when the attribute is mandatory or conditionally mandatory; otherwise add one yourself. |
With skipEmpty: false, empty values are interpreted as 0. With skipEmpty: true, empty values pass this test, and only a not_empty_validation test flags them.
Example — validate price is at least 0:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/123/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "numeric_check", "configuration": {"value": 0, "operator": ">="}}]'Length check
Validates string length.
Use cases:
- Product title max 100 characters.
- Description minimum length.
- SKU exactly 10 characters.
Configuration:
{
"value": 100,
"operator": "<=",
"skipEmpty": false
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
value | integer | yes | Length threshold (must be ≥ 0). | |
operator | string | yes | One of >, <, >=, <=, =, !=. The validation passes when length(attribute) [operator] value is true. | |
skipEmpty | boolean | no | false | When true, empty or null values pass this test. Empty values are then only flagged by a not_empty_validation test: it is added automatically when the attribute is mandatory or conditionally mandatory; otherwise add one yourself. |
With skipEmpty: false, empty values have length() = 0, so length(empty) <= 100 is true (passes) and length(empty) >= 1 is false (fails). With skipEmpty: true, empty values always pass.
Example — title must be at most 100 characters:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/456/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "length_check", "configuration": {"value": 100, "operator": "<="}}]'Enum check
Validates that a value is in (or not in) a predefined list.
Use cases:
- Availability status must be from approved list (whitelist).
- Brand must NOT be a placeholder value (blacklist).
- Color must come from an approved palette.
Configuration:
{
"allowedValues": ["in stock", "out of stock", "preorder"],
"operator": "IN",
"caseSensitive": false,
"skipEmpty": false
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
allowedValues | array of strings/numbers | yes | — | List of values to compare against. |
operator | string | no | "IN" | "IN" (whitelist — pass if value is in the list) or "NOT IN" (blacklist — pass if value is NOT in the list). |
caseSensitive | boolean | no | true | When false, comparison wraps both sides in mb_lower() so case differences don't fail the test. |
skipEmpty | boolean | no | false | When true, empty or null values pass this test. Empty values are then only flagged by a not_empty_validation test: it is added automatically when the attribute is mandatory or conditionally mandatory; otherwise add one yourself. |
Default skipEmpty: false means empty values fail enum checks because they are not in any allowed list. With true, empty values pass, and only a not_empty_validation test flags them, so an empty value is not counted twice.
Example — availability whitelist with case-insensitive matching:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/789/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "enum_check", "configuration": {"allowedValues": ["in stock", "out of stock", "preorder", "backorder"], "operator": "IN", "caseSensitive": false, "skipEmpty": true}}]'Example — brand blacklist with placeholder values:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/790/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "enum_check", "configuration": {"allowedValues": ["N/A", "Generic", "No brand", "Does not exist"], "operator": "NOT IN", "caseSensitive": false}}]'Regex pattern
Validates a value against a regular expression.
Use cases:
- Date format validation (
YYYY-MM-DD). - Product code format (e.g.
ABC-12345). - Phone number format.
- Detect HTML tags or broken encoding.
Configuration:
{
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"shouldMatch": true,
"skipEmpty": false
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
pattern | string | yes | — | PCRE pattern. With or without delimiters. |
shouldMatch | boolean | no | true | When true, the value must match the pattern to pass. When false, the value must NOT match the pattern to pass. |
skipEmpty | boolean | no | false | When true, empty/null values are treated as valid. |
Pattern delimiters
The pattern can be supplied with or without delimiters:
- Without delimiters — e.g.
"^\\d{4}$"is automatically wrapped in/delimiters. - With delimiters — e.g.
"/^\\d{4}$/u"is used as-is. Accepted delimiter characters:/,#,~,|,@,!. Useful when your pattern itself contains/(e.g. URL patterns), so you can write"#https?://[^/]+#"instead of escaping every slash.
JSON escaping
Backslashes need to be escaped in JSON strings. When writing patterns:
- In JSON request:
"pattern": "^\\d{4}$"(double backslash in source). - Actual regex executed:
^\d{4}$(single backslash).
| Regex want | JSON write | After parse |
|---|---|---|
\d | \\d | \d |
\w | \\w | \w |
\s | \\s | \s |
\\ (literal backslash) | \\\\ | \\ |
skipEmpty and shouldMatch interaction
shouldMatch | skipEmpty | Behavior on empty value |
|---|---|---|
true | false (default) | Empty value fails (pattern doesn't match an empty string for most patterns). |
true | true | Empty value passes (skip-empty short-circuits). |
false | false (default) | Empty value passes (pattern doesn't match an empty string, so "must not match" is satisfied). |
false | true | Empty value passes. |
Setting skipEmpty: true is most useful when shouldMatch: true (i.e. "value must be a valid format"), since you want empty values to be handled by a not_empty_validation test rather than failing the format check too.
Example — date format validation:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/101/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "regex_pattern", "configuration": {"pattern": "^\\d{4}-\\d{2}-\\d{2}$", "shouldMatch": true, "skipEmpty": true}}]'Example — flag descriptions containing HTML tags:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/102/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "regex_pattern", "configuration": {"pattern": "#<[^>]+>#", "shouldMatch": false}}]'shouldMatch: false means "valid when does NOT match". A description containing HTML triggers the test; clean text passes. The # delimiters avoid escaping the / that would otherwise be needed in a URL-style pattern.
Not empty / null validation
Validates that a value is not empty or null.
Use cases:
- Mandatory attributes must contain a value.
- Detect optional fields that became empty unexpectedly.
Configuration:
{}No configuration parameters.
Example — title is required:
curl -X POST "https://export-template-api.productsup.com/V2/attributes/201/analyzer-tests" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[{"templateId": "not_empty_validation", "configuration": {}}]'Composing multiple templates
You can assign multiple templates to a single attribute. Each assignment is evaluated independently, and a row is flagged for that attribute if any assignment fires.
Example: price range
Validate price is between 0 and 999,999. Two assignments — a row fails if either bound is violated:
[
{
"templateId": "numeric_check",
"configuration": {"value": 0, "operator": ">="}
},
{
"templateId": "numeric_check",
"configuration": {"value": 999999, "operator": "<="}
}
]Example: required field with format
Date must be present AND match YYYY-MM-DD:
[
{
"templateId": "not_empty_validation",
"configuration": {}
},
{
"templateId": "regex_pattern",
"configuration": {
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"shouldMatch": true,
"skipEmpty": true
}
}
]The format test sets skipEmpty: true so an empty value is flagged once (by the not_empty_validation test), not twice.
Example: title constraints
Title must be between 10 and 100 characters:
[
{
"templateId": "length_check",
"configuration": {"value": 10, "operator": ">="}
},
{
"templateId": "length_check",
"configuration": {"value": 100, "operator": "<="}
}
]API reference
| Method | Endpoint | Description |
|---|---|---|
GET | /V2/analyzer-test-templates | List all available templates with their configuration schemas. |
POST | /V2/attributes/{attributeId}/analyzer-tests | Assign one or more templates to an attribute. |
PATCH | /V2/attributes/{attributeId}/analyzer-tests/{assignmentId} | Update an existing assignment. |
DELETE | /V2/attributes/{attributeId}/analyzer-tests/{assignmentId} | Remove an assignment from an attribute. |
GET | /V1/export-templates/{templateId}/attributes | List export template attributes including their analyzerTests array. |
GET | /V2/export-templates/{id}/schema/full | Get the JSON Schema describing all attributes and their assigned analyzer tests for a given export template. |
Common assignment-body fields
In addition to templateId and configuration, the following optional fields can be supplied when creating or updating an assignment:
| Field | Type | Description |
|---|---|---|
name | string (≤ 255) | Display name shown in the Developer Portal UI. |
color | string (hex, e.g. #ff0000) | Display color for the assignment chip in the UI. |
caption | string (≤ 255) | Caption shown when the test fails. Defaults to "Invalid value" when the field is left out of the request. The Developer Portal sends "Invalid Value" when its Error Message field is empty. |
group | string (≤ 30) | Grouping label for display purposes (UI only). |
hint | string (≤ 2000) | Long-form hint shown to the user when the test fails. |
errorId | integer | ID of the associated monitorable error from pds_error. |
requiredColumns | array of strings | List of attribute names the test depends on. |
mandatory | boolean | Deprecated. All analyzer tests are now mandatory. The field is accepted for backward compatibility but has no effect. |
Error responses
| Status | Meaning |
|---|---|
400 Bad Request | Invalid configuration, missing required fields, or template-specific validation failure (e.g. unknown operator, empty allowedValues, invalid regex syntax). |
401 Unauthorized | Missing or invalid authentication. |
403 Forbidden | Insufficient permissions. |
404 Not Found | Attribute, assignment, or template does not exist. |
See the API reference for complete error response schemas and examples.
Permissions
All authenticated users can:
- List available templates.
- Assign templates to attributes.
- View, update, and unassign their assignments.
Best practices
Start simple
Begin with the minimum validation that catches obvious bad data, then add complexity.
Step 1 — validate price is non-negative:
[
{"templateId": "numeric_check", "configuration": {"value": 0, "operator": ">="}}
]Step 2 — add an upper bound:
[
{"templateId": "numeric_check", "configuration": {"value": 0, "operator": ">="}},
{"templateId": "numeric_check", "configuration": {"value": 999999, "operator": "<="}}
]Skip empty for format tests
If a value-format test (e.g. "must match URL pattern") should not also flag empty values — because a not_empty_validation test already covers them — set skipEmpty: true:
{
"templateId": "regex_pattern",
"configuration": {
"pattern": "^https?://.+$",
"shouldMatch": true,
"skipEmpty": true
}
}This avoids the same row failing twice (once for being empty, once for not matching the URL pattern), which would distort readiness math without changing what gets flagged.
Use case-insensitive enum when source data varies
If your source data has inconsistent capitalisation, enable case-insensitive matching once instead of listing every variant:
{
"templateId": "enum_check",
"configuration": {
"allowedValues": ["in stock", "out of stock", "preorder"],
"operator": "IN",
"caseSensitive": false
}
}Avoid conflicting constraints
Each assignment runs independently and a row fails if any fires. Conflicting constraints fail every row.
Avoid:
// All values fail — can't be both > 100 AND < 50
[
{"templateId": "numeric_check", "configuration": {"value": 100, "operator": ">"}},
{"templateId": "numeric_check", "configuration": {"value": 50, "operator": "<"}}
]Prefer:
// Valid range: between 50 and 100
[
{"templateId": "numeric_check", "configuration": {"value": 50, "operator": ">="}},
{"templateId": "numeric_check", "configuration": {"value": 100, "operator": "<="}}
]If you need disjunction within a single value-format check (pattern A OR pattern B), express it inside one regex using regex alternation ((?:A|B)), not as separate assignments.
Test with sample data
After assigning templates, run the analyzer against a representative sample of the export's data and check the readiness output. Aim for the same per-attribute readiness numbers you saw under V1 if you're migrating an existing V1 catalog.
Next steps
- API reference — full request and response schemas for every endpoint.
- About analyzer tests — conceptual overview from the user-facing side.
- Attributes — how attributes work on an export template, the surface analyzer tests attach to.
For help, contact your Productsup Customer Success Manager or our Technical Support Team via support@productsup.com.
How is this guide?