Shopper profiles
A shopper profile is what one shopper likes, what they told you, the sizes they wear and their measurements. It is their whole "algorithm": feeds, rankings and sizes read it, and so does the Stylor chat agent. The shopper endpoints let your own app or site build on it with your public key, the same way the Stylor widget does.
There is one engine for everyone. An anonymous shopper and one who saved their profile to an email differ only in how long the profile lasts and where it can be used.
Two credentials
Every shopper call carries two things, with different jobs.
| Your public key | The shopper's token | |
|---|---|---|
| Header | Authorization: Bearer sgpt-pk-… |
X-Shopper-Token: <token> |
| Says | Which organization is calling | Which shopper this is |
| Decides | Rate limits, quota, and the catalogue feeds, similar and rank choose from | Whose profile is read and changed |
Both are safe on a device. The token opens one profile and nothing else.
Shopper calls are metered as api.v1.shoppers. Until your plan carries that resource they draw on the chat's allowance, api.v2.agent.chat; the X-RateLimit-Resource header on each response says which was charged. If your organization turned shopper profiles off in its agent settings, calls that need a profile fail with 403, and feed, similar and rank run unpersonalised.
Getting a token
Anonymous
Call POST /v1/shoppers/auth/start with no token. It makes an anonymous profile and returns { token, profile }. Keep the token on the device and send it on every shopper call from then on.
curl -X POST https://stylor.ai/api/v1/shoppers/auth/start \
-H "Authorization: Bearer $STYLOR_PUBLIC_KEY"You often don't need to call it. Recording a signal, remembering something, sizing with save: true or asking for a sign-in code all make a profile when no token is sent, and return its token in the same response. Until you hold a token, send those calls one at a time: three likes sent at once by a new shopper make three profiles.
An anonymous profile belongs to your organization. Its token opens it only with your organization's key, and it lasts 180 days from its last use.
Signed in with an email
To keep a profile for good, and to open it on another device or at another store that runs Stylor, the shopper confirms their email with a six-digit code.
POST /v1/shoppers/auth/codewith{ "email": "[email protected]" }and the token you hold. The code is emailed in your store's name and lasts 10 minutes.POST /v1/shoppers/auth/verifywith{ "email", "code" }and the same token.- Keep the
tokenfrom the verify response in place of the old one. It is the account's token and works with any organization's key.
If the email already has a profile, what was learnt on this device moves into it and merged is true. On a new device, start with no token: auth/code makes a profile for the code, and auth/verify swaps it for the saved one.
When a token stops working
A 401 with That shopper token is not valid here. Start a profile or sign in again. means the token has expired, its profile was deleted, or it is another organization's anonymous profile. Drop it, and either carry on without one or start again. Tokens last 400 days and are renewed whenever a response returns one.
Signals
POST /v1/shoppers/me/signals records what the shopper does: likes, dislikes, clicks, cart adds, purchases, whole looks they loved or passed on, and taste they put in words (said). Each is learnt in the same call, so the next feed or ranking already knows.
{
"signals": [
{ "kind": "like", "itemId": "701e5cee-41da-4e91-a688-69e8c8dd5fe3" },
{ "kind": "look_dislike", "itemIds": ["6a6dca06-…", "35a725c8-…"] },
{ "kind": "said", "text": "minimal earthy workwear", "occasion": "work" }
]
}A purchase counts most and a click least; a dislike pushes down only what is close to it. Reactions fade with a 60-day half-life, so taste can move. To take a reaction back (an accidental like, or a heart tapped again), send { "kind": "undo", "undoes": "like", "itemId": "…" }: the latest such reaction is deleted and the taste relearnt without it.
Taste is kept as several styles per garment slot, not one average, so a shopper who likes black streetwear and neutral office wear gets both. GET /v1/shoppers/me shows them in words.
Feed, similar and rank
POST /v1/shoppers/me/feedreturns a page of your catalogue for the shopper. Each of their styles gets a share of the page by its strength, near-duplicates are dropped, and one place in ten goes to something new so the profile keeps learning. A new shopper gets a spread to react to. Filter byslot,genderandageGroup; products that are not worn (homeware, toys) are never included.POST /v1/shoppers/me/similarreturns products like one they picked, nudged by their taste.POST /v1/shoppers/me/rankputs any list of your products (a category page, your own search results) in the shopper's order.
Feed and similar answer with compact cards: name, price, one image and why it was chosen. To open a product page, read the whole product with POST /v1/products, which returns every variant with its stock, every photo and the store it is from, priced in the shopper's market. All three work without a token, unpersonalised.
Remembering and forgetting
POST /v1/shoppers/me/remember keeps what the shopper tells you:
notes: things that hold beyond one visit ("starting a new office job in March"), each with akind.sizes: sizes they wear, by brand ({ "brand": "Levi's", "kind": "jeans", "label": "30", "fit": "right" }). A size replaces the one kept for the same brand and kind.body: their measurements,bust,waist,hips,inseamandheight, with aunitofcmorin, and how they like things to fit (fitted,regularorrelaxed). Kept in centimetres. A measurement sent replaces the kept one; the rest stay.
Measurements are checked before anything is kept. One outside the range a person can have fails the call with 422 and nothing is written: a waist of 32 sent as cm was meant as inches, and kept as given it would size someone else.
POST /v1/shoppers/me/forget takes something back: a note or a style by its id, every size kept for a brand (sizeBrand), or all their measurements ({ "body": true }). DELETE /v1/shoppers/me deletes the profile and everything learnt from it.
Give shoppers a screen that shows their profile (GET /v1/shoppers/me) with a way to forget each part. What was read from their reactions is labelled as such in the response, never as something they said.
Sizing
POST /v1/shoppers/me/size gives the size to buy in up to 24 products. It is the sizing the Stylor chat agent uses, so an app and the chat give the same answer.
It reads the sizes the shopper wears at other brands against those brands' published charts (about 90 brands) for an estimate of their measurements, takes their own measurements over that, and lays the result on each product's own store's size guide.
{
"itemIds": ["701e5cee-41da-4e91-a688-69e8c8dd5fe3", "35a725c8-23fb-4009-a52a-2a778d2e504a"],
"sizes": [{ "brand": "Levi's", "kind": "jeans", "label": "30" }]
}{
"results": [
{
"itemId": "701e5cee-41da-4e91-a688-69e8c8dd5fe3",
"name": "Straight-Leg Jean — Washed Blue",
"size": "30",
"runnerUp": null,
"confidence": "certain",
"basis": "one source",
"reason": "Your 30 waist at Levi's: our 30 is your size.",
"inStock": true
}
]
}- What is read. The sizes and measurements in the request, laid over the ones the profile keeps, the request winning. The token is optional: without one, only the request is read. Send
save: trueto keep what you sent on the profile. - How sure.
certainis said plainly.hedgedis a best guess for the one reason inbasis(between sizes, sizes that disagree, a single weak source, no size guide to check against). Between two sizes,optionsgives both, each with what it gives. - Stock.
inStocksays whether the recommended size is in stock now, in the shopper's market when you sendcountry. - Nothing to go on. With no sizes or measurements sent or kept, each
sizeisnulland the response'sneedssays what to ask for:["sizes", "measurements"], or onlymeasurementswhen the product's store does not size from other brands.
Each product is sized under its store's own rules: brands the store left out are not read, and a size the shopper wears at the store itself is their size there.
Asking for sizes: the sizes card
When needs asks for sizes, show the same "Which brands do you wear?" card the Stylor chat shows. POST /v1/shoppers/me/size/card returns it as data, built by the same code as the chat's:
{ "department": "mens", "garments": ["tshirt", "jeans"], "country": "US" }The answer's card holds the rows to ask (garments), every brand with its size chips per department and garment (brands[].sizes["mens:jeans"]), the bottom sizes that usually go with each top size (brands[].likely), the fit choices (fits: tight, right, loose), and the sizes the profile already keeps (saved) when you send the token. Every field is optional: with no garments it asks a t-shirt and jeans, and with no department it holds both and you ask womenswear or menswear first. A brand with logo: true has a public logo at /brand-logos/<slug>.png on the API's host; show logos only while the card's logos is true.
The card is your organization's, not one store's, so it is built under the default rules: logos on and no brand left out. A store's own settings still apply when its products are sized.
Save what the shopper picks with POST /v1/shoppers/me/remember, one row per brand and garment, as the card has them:
{
"sizes": [
{ "brand": "Uniqlo", "garment": "tshirt", "kind": "tops", "label": "M", "fit": "tight" },
{ "brand": "Levi's", "garment": "jeans", "kind": "jeans", "label": "32", "fit": "loose" }
]
}Remember keeps tight and loose as the profile's small and large, and reads the kind from garment when you leave it out. Then size the products again with no sizes in the request: the profile's are read.
Good practices
- Keep the token in the device's secure storage, and replace it whenever a response returns a new one.
- Record reactions as they happen rather than in a batch at the end of a session.
- Ask for an email only when the shopper wants to keep their profile; everything works anonymously.
- When the shopper asks to delete their profile, call
DELETE /v1/shoppers/meand drop the token.