Send products for matching
- Production and sandbox. The token decides the environment.
https://api.beautyrouter.com/v1/product-matches
using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Post, RequestUri = new Uri("https://api.beautyrouter.com/v1/product-matches"), Headers = { { "Authorization", "Bearer <token>" }, }, Content = new StringContent("{ \"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\" ] } ] }") { Headers = { ContentType = new MediaTypeHeaderValue("application/json") } }};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.beautyrouter.com/v1/product-matches"
payload := strings.NewReader("{ \"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\" ] } ] }")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>") req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");RequestBody body = RequestBody.create(mediaType, "{ \"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\" ] } ] }");Request request = new Request.Builder() .url("https://api.beautyrouter.com/v1/product-matches") .post(body) .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json") .build();
Response response = client.newCall(request).execute();import axios from 'axios';
const options = { method: 'POST', url: 'https://api.beautyrouter.com/v1/product-matches', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, data: { 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'] } ] }};
try { const { data } = await axios.request(options); console.log(data);} catch (error) { console.error(error);}const url = 'https://api.beautyrouter.com/v1/product-matches';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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"]}]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}val client = OkHttpClient()
val mediaType = MediaType.parse("application/json")val body = RequestBody.create(mediaType, "{ \"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\" ] } ] }")val request = Request.Builder() .url("https://api.beautyrouter.com/v1/product-matches") .post(body) .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json") .build()
val response = client.newCall(request).execute()<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.beautyrouter.com/v1/product-matches', [ 'body' => '{ "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" ] } ] }', 'headers' => [ 'Authorization' => 'Bearer <token>', 'Content-Type' => 'application/json', ],]);
echo $response->getBody();import requests
url = "https://api.beautyrouter.com/v1/product-matches"
payload = { "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"] } ] }headers = { "Authorization": "Bearer <token>", "Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.json())require 'uri'require 'net/http'
url = URI("https://api.beautyrouter.com/v1/product-matches")
http = Net::HTTP.new(url.host, url.port)http.use_ssl = true
request = Net::HTTP::Post.new(url)request["Authorization"] = 'Bearer <token>'request["Content-Type"] = 'application/json'request.body = "{ \"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\" ] } ] }"
response = http.request(request)puts response.read_bodycurl --request POST \ --url https://api.beautyrouter.com/v1/product-matches \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "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" ] } ] }'import Foundation
let headers = [ "Authorization": "Bearer <token>", "Content-Type": "application/json"]let parameters = ["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"] ] ]] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://api.beautyrouter.com/v1/product-matches")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)request.httpMethod = "POST"request.allHTTPHeaderFields = headersrequest.httpBody = postData as Data
let session = URLSession.sharedlet dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) }})
dataTask.resume()Sends up to 500 products. Returns one match for each product, in the same order. The matches belong to the logged-in user’s company, and other companies’ matches are not visible.
externalId is unique within the company. Sending the same externalId again returns the existing match. The
same externalId twice in one request fails with validation_failed. A product with a barcode can come back
matched right away.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
object
Your ID for the product. Unique within the company, and at most once per request.
The product name as your user typed it.
Anything else that helps identify the product.
Up to 10 photos of the product, as HTTPS URLs that Beauty Router downloads.
Examples
{ "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" ] } ]}Responses
Section titled “Responses”One match for each product.
object
One match for each product you sent.
object
The product match ID.
Your ID for the product. Unique within the company.
in_review: Beauty Router is matching the product. needs_reply: Beauty Router needs more information, asked
in question. matched: final, with productId.
The matched product ID. Only when status is matched.
What Beauty Router needs to know. Only when status is needs_reply.
Examples
{ "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" } ]}validation_failed: a field is missing or has a wrong value. field names it.
The body of every error response.
object
object
A stable error code.
A sentence for developers. It can change, so do not match on it.
The request field that caused the error. Only some errors have it.
Only on verification_data_missing and app_setup_incomplete. What still needs to be set.
Only on order_total_changed and order_has_problems. A fresh quote.
object
One group per supplier. Each group becomes one order.
object
object
An amount in dollars, with up to two decimal places.
The discount on the whole line, in US dollars.
An amount in dollars, with up to two decimal places.
The total of the lines, in US dollars.
The discount on the whole order, in US dollars.
object
The shipping charge in US dollars, or null when it cannot be worked out.
How much more to order for free shipping, or null when it does not apply.
The sales tax in US dollars, or null when it cannot be worked out.
object
The exemption certificate used, or null.
Why no certificate applies, or null when one does.
The amount the salon pays for this group in US dollars, or null when it cannot be worked out. Send it back as
expectedTotal. A group with problems has no expectedTotal.
now: Beauty Router charges the card when the order is created. after_verification: the company is
prequalified, so the charge waits until its verification ends.
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
The lines no supplier can take.
object
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
What stops every group.
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
The sum of all the groups in US dollars, or null when a group’s total is.
Always usd.
Examples
{ "error": { "code": "validation_failed", "message": "ein must have 9 digits.", "field": "ein" }}invalid_user_token: the user token is missing or wrong. user_token_expired: the user token expired. Log the
user in again with POST /v1/login.
The body of every error response.
object
object
A stable error code.
A sentence for developers. It can change, so do not match on it.
The request field that caused the error. Only some errors have it.
Only on verification_data_missing and app_setup_incomplete. What still needs to be set.
Only on order_total_changed and order_has_problems. A fresh quote.
object
One group per supplier. Each group becomes one order.
object
object
An amount in dollars, with up to two decimal places.
The discount on the whole line, in US dollars.
An amount in dollars, with up to two decimal places.
The total of the lines, in US dollars.
The discount on the whole order, in US dollars.
object
The shipping charge in US dollars, or null when it cannot be worked out.
How much more to order for free shipping, or null when it does not apply.
The sales tax in US dollars, or null when it cannot be worked out.
object
The exemption certificate used, or null.
Why no certificate applies, or null when one does.
The amount the salon pays for this group in US dollars, or null when it cannot be worked out. Send it back as
expectedTotal. A group with problems has no expectedTotal.
now: Beauty Router charges the card when the order is created. after_verification: the company is
prequalified, so the charge waits until its verification ends.
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
The lines no supplier can take.
object
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
What stops every group.
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
The sum of all the groups in US dollars, or null when a group’s total is.
Always usd.
Examples
{ "error": { "code": "invalid_user_token", "message": "The user token is missing or not valid." }}{ "error": { "code": "user_token_expired", "message": "The user token expired. Log the user in again." }}internal_error: something failed on Beauty Router’s side. Try again later.
The body of every error response.
object
object
A stable error code.
A sentence for developers. It can change, so do not match on it.
The request field that caused the error. Only some errors have it.
Only on verification_data_missing and app_setup_incomplete. What still needs to be set.
Only on order_total_changed and order_has_problems. A fresh quote.
object
One group per supplier. Each group becomes one order.
object
object
An amount in dollars, with up to two decimal places.
The discount on the whole line, in US dollars.
An amount in dollars, with up to two decimal places.
The total of the lines, in US dollars.
The discount on the whole order, in US dollars.
object
The shipping charge in US dollars, or null when it cannot be worked out.
How much more to order for free shipping, or null when it does not apply.
The sales tax in US dollars, or null when it cannot be worked out.
object
The exemption certificate used, or null.
Why no certificate applies, or null when one does.
The amount the salon pays for this group in US dollars, or null when it cannot be worked out. Send it back as
expectedTotal. A group with problems has no expectedTotal.
now: Beauty Router charges the card when the order is created. after_verification: the company is
prequalified, so the charge waits until its verification ends.
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
The lines no supplier can take.
object
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
What stops every group.
object
What stops a group or the whole request. See Problems.
A sentence you can show. It can change, so do not match on it.
The product. Only on problems with one product.
The supplier. Only on problems with one supplier.
How many units can be ordered. Only on insufficient_stock.
The sum of all the groups in US dollars, or null when a group’s total is.
Always usd.
Examples
{ "error": { "code": "internal_error", "message": "Something went wrong. Try again later." }}