Get Sephora data with one API
Sephora API returns live Sephora data as clean JSON for sephora The primary endpoint, search, returns product results including product id, sku id, name, brand and price.
3 active endpoints, on 1 and 2 credit tiers.
- POST/sephora/v1/search
- POST/sephora/v1/price
- POST/sephora/v1/product_detail
What Sephora endpoints does ReefAPI ship?
3 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.
Sephora API
3 of 3 endpoints, ready to run
Search the Sephora catalog.
// Press "Try it" and this pane shows exactly what the // live site returned this second — including an empty // result, if that is the truth. No key, no account.
How the Sephora API works
Sephora is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.
No OAuth app, no request signing, no per-site account. One key covers all 185 engines.
Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.
Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.
One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.
Product id vs SKU id, and which fields are null rather than false
Sephora prices and stocks every shade and size separately, so almost every question you have is a question about a SKU rather than a product. The second thing to know is that this API distinguishes "Sephora said no" from "Sephora said nothing": a stock or sale field is null when the page is silent, never false. Measured on P427418 (The Ordinary, one size) and P513655 (a 14-shade MAC lipstick).
| Field | Measured format | Watch out for |
|---|---|---|
| product_id | a P followed by 5-9 digits: P427418, P513655, P516231 | the tail of every sephora.com/product/ URL and the product_id on every search row |
| sku_id | digits only, no prefix: 2837375, 2031383, 2826154 | one per shade or size. Price and stock live here, not on the product. |
| price / price_max (search row) | price is the LOW end; price_max holds the high end and is null for a single price | measured: price 32.0 with price_max 45.0 renders as "$32.00 - $45.00" in price_display |
| variant_count (search row) | null on every search row measured | the real count is availability.variant_count from product_detail (14 for P513655) |
| rating | float out of 5: four decimals from search (4.2421), two from product_detail (4.45) | rating.best_rating is 5. review_count is separate, and loves_count (242,661 on P427418) is Sephora's favourites counter, not a review count. |
| in_stock | true, false, or null when Sephora said nothing at all | measured on P513655: 13 of 14 shades true, shade "Rebel" false, availability.in_stock_variant_count 13 of 14 |
| on_sale / sale_price / value_price / is_low_stock | null across all 14 measured variants | null means not stated. A normally priced shade returns current_price equal to regular_price from the price action. |
| sort | relevance, top_rated, price_low, price_high, newest | anything else is REJECTED, not ignored: best_selling returned INVALID_PARAM listing the five allowed values |
| search result count | Sephora's own NLP search never returns zero | "zzqqxx nonsense blorp" returned total_results 100, all haircare. Judge the rows, not the count. |
| price with product_id | a safety check, not a lookup key | SKU 2837375 sent with product_id P427418 returned NOT_FOUND naming P513655 as the product Sephora actually files it under, and priced nothing |
product_detail is the cheap way to get the whole shade matrix: one call returned 14 variants, each with its own sku_id, variation_value, price, in_stock and image. Set include_reviews false when you only want the matrix; reviews are roughly 2.5 KB each and are the main lever on response size.
What people build with Sephora
The jobs this data is most often used for.
endpoints
credits per call
Pricing and assortment teams use Sephora to search the Sephora catalog.
Brand-protection teams use Sephora to get re-check ONE shade's price and stock, cheaply.
Retail analysts use Sephora to get one Sephora product in full, by its product id (or its sephora.com URL).
What Sephora data costs
The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.
Full pricing →- 1,000 free credits on signup, no card
- One key, all 185 APIs, one credit pool
- Failed and blocked calls are never charged
- Credits roll over and never expire
Call it in two lines
Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.
curl -X POST https://api.reefapi.com/sephora/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"query":"mascara","max_results":30}'import requests
r = requests.post(
"https://api.reefapi.com/sephora/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"query": "mascara",
"max_results": 30
},
)
print(r.json()["data"])Have a question? We got answers.
The questions people actually ask before wiring up Sephora.
Get a free key →Should I store the product id or the SKU id?▾
Both, keyed by SKU. The product id (P513655) identifies the lipstick; the SKU id (2837375) identifies the shade "Stone", and price, stock and low-stock are all per shade. A measured 14-shade product had 13 shades in stock and one out, all at the same $25.00, so there is no single product-level answer to "is it available". Store the SKU as your row key and keep the product id alongside it.
Why is variant_count null on search results?▾
The search listing does not publish it. It was null on every search row measured, and the number you want is availability.variant_count from product_detail, which returned 14 for P513655 alongside in_stock_variant_count 13. Search is for discovery; treat any shade-level question as a product_detail call.
What does in_stock: null mean, is the item out of stock?▾
No. Null means Sephora's page said nothing about stock for that SKU, and silence is not an out-of-stock claim, so it is deliberately not collapsed to false. A genuinely unavailable shade comes back as in_stock false, which is what shade "Rebel" returned on the measured lipstick. The same rule applies to on_sale and is_low_stock: null is unknown, false is stated.
I searched for nonsense and still got a full page of products. Why?▾
Because Sephora runs the query through its own NLP search, and that search never returns zero. "zzqqxx nonsense blorp" came back with total_results 100 and a page of Color Wow, Olaplex and Kérastase haircare. This is upstream behaviour, not a fallback added here. Judge relevance from the rows themselves, brand and name and category, rather than treating a non-zero count as proof of a match.
sale_price and value_price are null on every variant. Is the sale data missing?▾
They are null because nothing was on sale. Sephora prints a struck-through price only when there is a discount, and a value price only on a value-size SKU; when it prints one number, both extras stay null and the number lands in price. The price action makes it explicit: SKU 2837375 returned current_price 25.0, regular_price 25.0 and on_sale false, so equal values are the normal not-on-sale case.
Why did the price action fail with NOT_FOUND when I passed a product id?▾
Because product_id on that action is a consistency check rather than a lookup key. Sending SKU 2837375 with product_id P427418 returned NOT_FOUND stating that Sephora files that SKU under P513655, and nothing was priced. That is the intended outcome: it tells you your stored catalogue has drifted, instead of quietly writing another product's price into it. Omit product_id and you simply get Sephora's own product id back in the response.
What happens with an unknown SKU or an invalid sort value?▾
Both fail loudly, and differently. An unknown SKU (9999999) returned NOT_FOUND in Sephora's own wording rather than substituting a default shade. An unknown sort value (best_selling) returned INVALID_PARAM in under 2 ms with the five allowed values in the error detail, because rejecting it beats accepting it and silently serving the default order.
What is in specifications.attributes?▾
Sephora's own structured facet map, keyed by its internal attribute names with abbreviated values. P427418 returned ageRange [20s, 30s, 40s, 50s], skinType [normalSk, drySk, comboSk, oilySk], skinConcerns [dullness, unevenTexture, fineLinesWrinkles], formulation [serum], sunProtections [none] and ingredientPreferences [retinoid, sulfateFree, parabenFree, fragranceFree]. The keys vary by category, so read them rather than hard-coding a schema, and note the values are Sephora's tokens (normalSk), not display labels.
What is the Sephora API?▾
Sephora API is a ReefAPI endpoint group for sephora It returns live JSON through POST requests under /sephora/v1.
Is the Sephora API free to try?▾
Yes. ReefAPI starts with 1,000 free credits, no card required. Sephora calls use the same shared credit balance as every other ReefAPI engine.
Do I need a Sephora login or account?▾
No login to Sephora is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the Sephora data?▾
The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the Sephora API use?▾
Sephora actions currently cost 1-2 credits per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.
Can I call Sephora from an AI assistant or MCP client?▾
Yes. Connect ReefAPI once through MCP and your assistant can call sephora actions with the same key, credit pool and JSON envelope used by normal REST requests.
15 More APIs APIs on the same key
One key, one credit pool, one response envelope. If you are pulling Sephora, you are one call away from the rest of the category — no second contract, no second integration.
Try it on your own data before you pay anything
The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 184 APIs, and the same envelope everywhere.
Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts.