Analyzer tests
Template-based validation system for export attributes — assign reusable, parameterised 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 parameterised 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 Productsup Keycloak JWT. See Get access. 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": ">="}}]'View results
curl "https://export-template-api.productsup.com/V1/export/{exportId}/export-fields" \
-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 will fail 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": ">="
}| Field | Type | Required | Description |
|---|---|---|---|
value | number | yes | Numeric threshold to compare against. |
operator | string | yes | One of >, <, >=, <=, =, !=. The validation passes when column [operator] value is true. |
Empty values are interpreted as 0. To skip empty rows entirely, pair this template with a separate not_empty_validation assignment.
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": "<="
}| Field | Type | Required | Description |
|---|---|---|---|
value | integer | yes | Length threshold (must be ≥ 0). |
operator | string | yes | One of >, <, >=, <=, =, !=. The validation passes when length(column) [operator] value is true. |
Empty values have length() = 0. Comparisons behave naturally: length(empty) <= 100 is true (passes), length(empty) >= 1 is false (fails).
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/null values are treated as valid. The dedicated empty-column test still flags empty rows independently. |
Default skipEmpty: false means empty values fail enum checks because they are not in any allowed list. Set to true for tests that should pass empty rows and rely on a separate not_empty_validation to flag missing values. This avoids double-counting empties against both the enum test and the empty test.
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 | Behaviour 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 rows to be handled by the empty-column 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 empty rows are flagged once (by the not-empty 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/{exportId}/export-fields | View export fields 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". |
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 column 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 rows — because the empty-column readiness test or not_empty_validation 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 will 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?