Complete the look
/v2/agent/looks/completeWhat else the model in a product's photo is wearing, from your store.
This is the endpoint behind the complete-the-look strip
(complete-the-look.js): called once per product page view, it
answers with the store's products the model wears with this one, each
with its alternates. The shop-the-look models read the product's own
model photos (a full-body photo first, then its others) with the
product as the anchor, so the product itself, its other colourways and
the other pieces of a set it belongs to are never answered.
It is the one Agent API endpoint meant to be called from a merchant's
own storefront domain, so it answers the browser's CORS preflight
(OPTIONS, 204) and sends Access-Control-Allow-Origin: * on every
response, errors included.
Naming the product
Name the product however the page can: itemId when the page renders
ours, otherwise the variant sku, the name and the page url, which
are resolved to one catalogue row between them. A product that cannot
be placed answers none. image is the photo the shopper is looking
at: when it is one of the product's own model photos (matched by file
name, never fetched), that photo's look is answered, and photo in
the response names it.
Which store
One organization can run several storefronts under one public key.
The scope (datasetIds, default every active dataset) is narrowed to
the storefront the request's Origin header names, which the browser
sets and a page cannot change. matchStore: false turns that off.
States
The answer is kept per product and photo for a week, so the first
visitor's request runs the models and later ones read it. state is
ready with garments, none when there is nothing to show (no
garment found or sold in the shopper's market, the product could not
be placed, or the organization has switched its widgets off), or
pending when a run took longer than 6 seconds (it carries on; ask
again a few seconds later) or the store's products are not indexed
yet. The field is state, not status.
Prices
Send country (Shopify.country) and the cards are priced in the
shopper's market; a garment they cannot buy there (not sold, or sold
out in every size) is left out. For a market whose price list Stylor
does not hold, send page with the storefront's currency
(Shopify.currency.active and .rate) and the price the page shows
for this product: the cards are then converted as the storefront
converts them, and carry converted: true.
Metering
Quota counts runs, not requests: a request that started no run (a
stored answer, a product that could not be placed, a store not yet
indexed, the widgets switched off) gives its unit of
api.v2.agent.looks.complete back. The requests-per-minute limit
counts every request. The demo key is not accepted.
Request body
application/jsonrequiredOur full item id, when the page renders it. Wins over the other identifiers.
The SKU of the variant on display.
The product title.
The product page URL or path; its handle tells apart products that share a name.
The product photo the shopper is looking at, as the page has it. Used only when it is one of the product's own model photos, matched by file name and never fetched.
Datasets to look in. Omit or send [] for every active dataset.
Narrow the scope to the storefront named by the request's Origin header. Only an explicit false turns it off.
The shopper's country, ISO 3166-1 alpha-2 in any case; on Shopify, Shopify.country. Cards are priced in their market; garments they cannot buy there are left out. A value that is not a two-letter code is treated as unknown.
What the storefront page shows about the shopper's currency, for a market Stylor holds no price list for. Anything malformed is ignored.
The currency the page shows, Shopify.currency.active.
The storefront's conversion rate, Shopify.currency.rate. Above 0 and at most 100000.
The price the page shows for this product, in major units, from which the storefront's rounding is learned.
The page's language tag (Shopify.locale). With it, a ready answer carries the strip's default words in that language as strings.
Response
Response headers
X-RateLimit-ResourceThe quota resource this endpoint is metered against: api.v2.agent.chat
for the chat endpoint, api.v2.agent.looks.render for the looks
endpoint (api.v2.agent.looks on a plan not yet moved to it),
api.v2.agent.match for shop the look, api.v2.agent.looks.complete
for complete the look and api.v2.agent.pose for the pose endpoint.
These are separate from the v1 resources, so Agent API usage never
draws down a v1 allowance.
X-RateLimit-Quota-LimitRequests allowed in the current billing period, or unlimited.
X-RateLimit-Quota-RemainingRequests left in the current billing period, or unlimited.
X-RateLimit-Quota-ResetUnix epoch seconds at which the billing period resets.
X-RateLimit-RPM-LimitRequests allowed per minute for this resource.
X-RateLimit-RPM-RemainingRequests left in the current one-minute window.
X-RateLimit-RPM-ResetUnix epoch seconds at which the one-minute window resets.
ready: garments holds the look. pending: not known yet; ask again a few seconds later. none: nothing to show.
readypendingnoneWhat the model wears with the product, one entry per garment; empty unless state is ready.
What it is, in a shopper's word (trousers, sneaker).
A catalogue item as the match endpoint presents it — enough to draw a
card and link out, without the full Product record.
The next closest products, best first, without any the shopper cannot buy.
The model photo the answer is for, as a file-name key, or null for the product's default photo. Compare it with the photo on screen to tell its look from an unrelated image.
Only on a ready answer when language was sent. The strip's default heading and lines in that language; more holds a {count} placeholder.
Errors
24Every error is JSON with a single error string unless noted. Match on the status code; the message is for humans.
Request body must be valid JSON.The body could not be parsed as JSON.
Request body must be a JSON object.The body is empty, or is valid JSON that is not an object (for example an array).
No active datasets found for this organization. Activate one via the dashboard (Dataset → Settings) or through the REST API before proceeding.datasetIds was omitted and the organization has datasets, but none is active.
All requested datasets are archived. Reactivate at least one via the dashboard or REST API.Every dataset in datasetIds is archived.
No datasets found for this organization. Create one via the dashboard or REST API, or pass datasetIds to use draft/archived datasets before proceeding.The organization has no datasets at all.
datasetIds must be an array of strings.datasetIds is present and is not an array, or contains a value that is not a string.
image must be a URL string of at most 2048 characters.image is present and is not a string, or is longer than 2048 characters.
Invalid datasetIds for this organization: <ids>One or more of datasetIds does not belong to the organization. The offending ids are listed, comma-separated.
curl -X POST "https://stylor.ai/api/v2/agent/looks/complete" \
-H "Authorization: Bearer $STYLOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sku": "CLS-NVY-M",
"name": "Coastal Linen Shirt",
"url": "https://shop.example.com/products/coastal-linen-shirt",
"image": "https://shop.example.com/cdn/shop/files/coastal-linen-shirt-model.jpg",
"country": "US",
"language": "en"
}'{
"state": "ready",
"photo": "coastal-linen-shirt-model",
"garments": [
{
"slot": "trousers",
"item": {
"id": "1b7e33c0-9a2d",
"itemId": "1b7e33c0-9a2d-4f8e-b5c6-7d8e9f0a1b2c",
"name": "Everyday Chino",
"brand": "Acme",
"price": 79,
"currency": "USD",
"market": "us",
"url": "https://shop.example.com/products/everyday-chino",
"image": "https://stylor.ai/api/v1/image/org_5f3a/ds_9c21/1b7e33c0-9a2d/0.jpg/-/resize/600x/-/format/jpeg",
"slot": "bottom",
"family": "pant",
"colour": "beige"
},
"alternates": []
}
],
"strings": {
"heading": "Complete the look",
"subtitle": "The rest of what the model is wearing, from our collection.",
"single": "Wear it with",
"more": "Show {count} more"
}
}