Catalog Matching

Decide whether two product records describe the same purchasable item.

Endpoint
POST https://kovca.io/api/v1/catalog/match

Authenticate 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:

FieldTypeDescription
titlestring, requiredProduct title as listed, up to 300 characters.
brandstring, optionalBrand or manufacturer, up to 100 characters.
modelstring, optionalModel name, up to 100 characters.
categorystring, optionalProduct category, up to 100 characters.
attributesobject, optionalUp to 30 string attributes with any names, such as color, size, capacity, or finish.
identifiersobject, optionalAny of: gtin, upc, ean, mpn, manufacturer_sku, supplier_sku, model_number. Up to 64 characters each.
FieldTypeDescription
variant_attributesstring[], optionalTop-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.
Example request
{
  "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

200 OK
{
  "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"
    }
  }
}
FieldTypeDescription
request_idstringUnique ID for this request.
match_type"same_variant" | "same_product_different_variant" | "different_product" | "uncertain"See below.
requires_reviewbooleanTrue for uncertain results and when identifiers and descriptions disagree.
matched_attributesstring[]Fields present in both records with equivalent values: brand, model, normalized attribute keys, gtin, mpn, manufacturer_sku, model_number.
conflicting_attributesstring[]Fields present in both records with different values. A field missing from one record is never a conflict.
normalizedobjectbrand, 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, and ean are 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, and model_number are compared only when the brands don't conflict.
  • supplier_sku is 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 uncertain and requires_review: true as 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.