Product matching
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.
Send products
Section titled “Send products”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. |
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
Section titled “Photos”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.
Send products again
Section titled “Send products again”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 sameexternalIdagain 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 newexternalId. - 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.
Statuses
Section titled “Statuses”| 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.
Answer a question
Section titled “Answer a question”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 |
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 matches
Section titled “Get matches”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:
curl "https://api.beautyrouter.com/v1/product-matches?status=needs_reply&limit=100" \ -H "Authorization: Bearer $USER_TOKEN"Use a matched product
Section titled “Use a matched product”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.
