Catalog Matching
Decide whether two product records describe the same purchasable item.
POST https://kovca.io/api/v1/catalog/matchAuthenticate with your API key in the Authorization: Bearer header. The API compares exactly the two records you send. It doesn't look products up, and it never adds facts that aren't in your records.
Request body
Send product_a and product_b, and optionally variant_attributes. Each product is a record with these fields:
| Field | Type | Description |
|---|---|---|
| title | string, required | Product title as listed, up to 300 characters. |
| brand | string, optional | Brand or manufacturer, up to 100 characters. |
| model | string, optional | Model name, up to 100 characters. |
| category | string, optional | Product category, up to 100 characters. |
| attributes | object, optional | Up to 30 string attributes with any names, such as color, size, capacity, or finish. |
| identifiers | object, optional | Any of: gtin, upc, ean, mpn, manufacturer_sku, supplier_sku, model_number. Up to 64 characters each. |
| Field | Type | Description |
|---|---|---|
| variant_attributes | string[], optional | Top-level. Up to 30 attribute names that distinguish purchasable variants in your catalog, for example ["color", "size"]. Without it, a conflict on any attribute rules out same_variant. |
{
"product_a": {
"title": "Apple iPhone 16 Pro 256GB Black Titanium",
"brand": "Apple",
"model": "iPhone 16 Pro",
"attributes": {
"storage": "256GB",
"color": "Black Titanium"
}
},
"product_b": {
"title": "iPhone 16 Pro - Black Titanium - 256 GB",
"brand": "Apple",
"model": "iPhone 16 Pro",
"attributes": {
"storage": "256 GB",
"color": "Black Titanium"
}
}
}Response
{
"request_id": "req_6tVb1QmXcR8pLz3K",
"match_type": "same_variant",
"requires_review": false,
"matched_attributes": [
"brand",
"model",
"storage",
"color"
],
"conflicting_attributes": [],
"normalized": {
"brand": "Apple",
"model": "iPhone 16 Pro",
"attributes": {
"storage": "256GB",
"color": "Black Titanium"
}
}
}| Field | Type | Description |
|---|---|---|
| request_id | string | Unique ID for this request. |
| match_type | "same_variant" | "same_product_different_variant" | "different_product" | "uncertain" | See below. |
| requires_review | boolean | True for uncertain results and when identifiers and descriptions disagree. |
| matched_attributes | string[] | Fields present in both records with equivalent values: brand, model, normalized attribute keys, gtin, mpn, manufacturer_sku, model_number. |
| conflicting_attributes | string[] | Fields present in both records with different values. A field missing from one record is never a conflict. |
| normalized | object | brand, model, and attributes that both records supply and agree on, in normalized form. Fields are omitted rather than guessed. |
Match types
same_variant: Both records describe the same purchasable item: same product and same variant.same_product_different_variant: Same product model, but a different color, size, capacity, pack size, or configuration.different_product: Different products, model lines, or generations.uncertain: There isn't enough evidence to decide. Always returned with requires_review: true.
How records are compared
Normalization
- Case, whitespace, and Unicode forms are normalized before comparing.
- Units are normalized:
256 GB→256GB,15.6 inches→15.6in; also TB, MB, cm, mm, kg, g, lb, oz, ml, L, W, V, and mAh. Values aren't converted between units. - Attribute keys are compared ignoring case, and spaces and hyphens count as underscores. Different attribute names aren't merged: use the same names in both records.
- Company suffixes such as Inc., Ltd., and GmbH are ignored when comparing brands.
Identifiers
gtin,upc, andeanare converted to GTIN-14 and check-digit validated. Invalid codes are ignored. A shared valid code is strong evidence of the same item; different valid codes mean different items.mpn,manufacturer_sku, andmodel_numberare compared only when the brands don't conflict.supplier_skuis never used as evidence, because suppliers assign their own codes.- A shared identifier isn't accepted on its own. If the descriptions contradict it, the result is
uncertain.
Variant conflicts
A conflicting model, or a conflict on any attribute listed in variant_attributes, rules out same_variant. Without variant_attributes, a conflict on any attribute does. Kovca doesn't assume which attributes define a variant in your industry.
Titles and descriptions
When identifiers and structured attributes don't settle it, Kovca interprets the titles and other text. An exact match from text alone needs strong agreement that the model line is the same, that no variant detail differs, and that both records state their variant details. Otherwise the result is uncertain. Kovca prefers uncertain to a wrong exact match.
Examples
curl https://kovca.io/api/v1/catalog/match \
-H "Authorization: Bearer $KOVCA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_a": {
"title": "Apple iPhone 16 Pro 256GB Black Titanium",
"brand": "Apple",
"model": "iPhone 16 Pro",
"attributes": {
"storage": "256GB",
"color": "Black Titanium"
}
},
"product_b": {
"title": "iPhone 16 Pro - Black Titanium - 256 GB",
"brand": "Apple",
"model": "iPhone 16 Pro",
"attributes": {
"storage": "256 GB",
"color": "Black Titanium"
}
}
}'Errors
400 INVALID_REQUEST: a field is missing, too long, the wrong type, or not allowed. The message names the field.401 UNAUTHORIZED,429 RATE_LIMITED,503 SERVICE_UNAVAILABLE,504 TIMEOUT: see Errors and limits.
Limitations
- Compares two records per request. There is no catalog search, ingestion, or storage.
- Doesn't convert between units (for example 1 TB vs 1000 GB) or know which colors are marketing names for the same shade.
- Treat
uncertainandrequires_review: trueas a signal for a person or a stricter rule, not as "probably the same". - Records are processed to produce the result and are not stored by Kovca.