Skip to content

Product matching

View as Markdown

Salons often type their products into your salon app by hand. Product matching links each of those products to a product in the Beauty Router catalog. You send your products, and Beauty Router returns a match for each one. A product that is not in the catalog yet gets added to it. When a match is final, it has the Beauty Router product ID.

Matches belong to the company of the logged-in user. Other companies’ matches are not visible: a request for one fails with not_found, and lists leave them out. externalId is unique within the company.

POST /v1/product-matches takes up to 500 products per request.

Field Description
externalId Your ID for the product, up to 128 characters, unique within the company. Required.
name The product name as your user typed it. Required.
barcode The barcode. Optional.
brand The brand name. Optional.
size The size, such as 33.8 fl oz. Optional.
details Anything else that helps identify the product. Optional.
images Up to 10 photos of the product, as HTTPS URLs that Beauty Router downloads. Optional.
Terminal window
curl https://api.beautyrouter.com/v1/product-matches \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"externalId": "inv-10482",
"name": "Amberlin bond shampoo liter",
"barcode": "850012345678",
"brand": "Amberlin",
"size": "33.8 fl oz"
},
{
"externalId": "inv-10517",
"name": "Jadewell 6N",
"brand": "Jadewell",
"details": "Hair color tube, dark natural blonde",
"images": ["https://app.example.com/uploads/inv-10517-front.jpg"]
}
]
}'

The response has one match for each product:

{
"data": [
{
"id": "match-3Pa8sDf2Gh",
"externalId": "inv-10482",
"status": "matched",
"productId": "prod-4Fz9QbX2kL",
"createdAt": "2026-10-27T09:14:02Z",
"updatedAt": "2026-10-27T09:14:02Z"
},
{
"id": "match-9Lk4jHg7Fd",
"externalId": "inv-10517",
"status": "in_review",
"createdAt": "2026-10-27T09:14:02Z",
"updatedAt": "2026-10-27T09:14:02Z"
}
]
}

A product with a barcode can come back matched right away, as the first one did here.

The same externalId twice in one request fails with validation_failed.

Photos help identify a product, so send them when you have them.

Beauty Router caches the result of analyzing a photo by its URL. The cache is global and permanent: it covers every user and salon location, and it never expires. So:

  • Give different pictures different URLs. Never reuse a URL for a totally different picture, even years later, and even for another user or salon location. Beauty Router would use the earlier result without analyzing the new picture. A similar picture at the same URL is fine.
  • Keep the URL when the picture stays the same. Beauty Router then uses the earlier result, which is much faster. This helps when many products or salon locations share a picture, for example from your own image catalog.

Beauty Router caches its results, so you can send a product again at any time without worrying about a new recognition. A repeated request also returns much faster:

  • By externalId. Sending the same externalId again for the same company returns the existing match, even if other fields changed. This result is cached forever. To match a changed product again, send it with a new externalId.
  • By product data. Beauty Router also caches by all the recognition fields, which is every field except externalId. When all of them are the same, Beauty Router does not recognize the product again.

Several of your products can match the same catalog product. Send each of them.

Status Meaning
in_review Beauty Router is matching the product.
needs_reply Beauty Router needs more information. question says what it needs.
matched The match is final. productId is the Beauty Router product ID.

A review ends in one of two ways: matched with the product ID, or needs_reply with a question. A match never ends as “not found”. When the product is not in the catalog yet, Beauty Router adds it and returns matched with the new product ID. When Beauty Router cannot tell what the product is, it asks for more information.

When a match is created as matched or changes status, Beauty Router sends a product_match.updated webhook with the match.

When a match is needs_reply, show the question to your user and send their answer with PUT /v1/product-matches/{matchId}. For example:

Question Your user’s answer
Is this product from Jadewell or Amberlin? Jadewell
Is this the 2 oz tube or the 3 oz tube? The 2 oz tube
What does the label say under the product name? Demi-Permanent Hair Color 6N
Terminal window
curl -X PUT https://api.beautyrouter.com/v1/product-matches/match-9Lk4jHg7Fd \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reply": "Jadewell" }'

The match goes back to in_review, and the response is the match:

{
"id": "match-9Lk4jHg7Fd",
"externalId": "inv-10517",
"status": "in_review",
"createdAt": "2026-10-27T09:14:02Z",
"updatedAt": "2026-10-27T18:02:47Z"
}

A match that is not needs_reply fails with reply_not_expected.

GET /v1/product-matches/{matchId} returns one match.

GET /v1/product-matches lists matches with paging. Filter by status with status, for example to find every match that waits for a reply:

Terminal window
curl "https://api.beautyrouter.com/v1/product-matches?status=needs_reply&limit=100" \
-H "Authorization: Bearer $USER_TOKEN"

A matched match has the productId of a catalog product. Find its details in the catalog files, read them with GET /v1/catalog/products/{productId}, or show them in your salon app with showProductDetails(element, productId, options). See Embedded marketplace.

A product that Beauty Router added for your match reaches the catalog files with the next daily delta.