Introduction
The Stylor Agent API is a stylist that plans its own work. You send the conversation so far, and an agent decides what the reply needs: checking what the store stocks, searching your catalogue, choosing which products to put on screen, composing outfits into rendered looks, asking the shopper a multiple-choice question, offering follow-up chips and, when you allow it, reading and changing the shopper's cart.
Every one of those steps runs on Stylor's servers. Each result goes back to the model before it writes, so the answer is based on what your store actually stocks, not on guesses.
Base URL and authentication
All requests go to:
https://stylor.ai/apiEvery Agent API endpoint takes an organization public API key as a Bearer token. Public keys are safe to use in browser and mobile code. Private keys are rejected with 401.
curl https://stylor.ai/api/v2/agent/chat \
-H "Authorization: Bearer $STYLOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Do you carry linen shirts?"}],"stream":false}'Endpoints
| Method | Path | What it does | Metered as |
|---|---|---|---|
POST |
/v2/agent/chat |
Runs one agent turn. Streams Server-Sent Events by default. | api.v2.agent.chat |
POST |
/v2/agent/looks |
Renders one outfit as an image. Call once per look the agent announces. | api.v2.agent.looks |
POST |
/v2/agent/match |
Experimental. Shop the look: finds the store's products in a photo of someone dressed, with alternates for each. About a second. | api.v2.agent.match |
The Agent API has its own quota resources, separate from the v1 Chat and Outfit endpoints, and every plan includes them. Each request uses one unit, however many tools the agent calls during the turn. Every response, streamed ones included, carries the X-RateLimit-* headers.
The request lifecycle
One call to /v2/agent/chat goes through these stages:
- Checks. The API key, subscription, quota and requests-per-minute limit are checked, then the body is validated. A failure here returns an ordinary JSON error with the matching HTTP status, and no stream is opened.
- Stream opens. The response starts with status
200andContent-Type: text/event-stream. - The agent loop. The model writes text and calls tools. Each tool's result goes back to the model, which then writes more text or calls more tools. A turn is capped at ten model calls.
- Events. Text arrives as
text_delta. Tool activity arrives astool_calledandtool_output. Anything meant for the screen arrives as its own event:search_results,looks,question,suggestionsandcart_action. - Finish. The stream ends with
done, which carries the final text and a replay-readyhistory. If the run fails partway, it ends witherrorinstead.
Nothing is stored on the server between requests. The history you get back is the whole conversation state, and you send it back on the next turn.
Agent API (v2) versus Chat API (v1)
| v1 Chat | v2 Agent | |
|---|---|---|
| Streaming | Opt-in | On by default; pass stream: false for JSON |
| Product search | The model emits search descriptors and your client runs the searches | The agent runs searches itself, sees the results and picks what to show |
| What you receive | Search slots to execute | Finished product rows with full product records |
| Conversation state | Stored on the server under a chat id | Kept by you as the history array; there is no chat id |
| Outfit images | Composed from the stored chat | The agent names the exact items in each look; you render each look with /v2/agent/looks |
| Questions and chips | Not structured | question cards and suggestions chips as events |
| Cart | Not supported | Cart tools when you send a cart snapshot |
Because the agent sees its own search results, it can retry a search that came back empty, show the best four of twelve items, and tell the shopper honestly when the store does not carry something.
A minimal end-to-end flow
The steps below go from a first message to rendered looks. Examples are in JavaScript. The streaming guide has a fuller reader and a Python version.
1. Send a message
const API = 'https://stylor.ai/api';
const KEY = process.env.STYLOR_API_KEY;
let history = [];
async function sendMessage(text) {
const messages = [...history, { role: 'user', content: text }];
const res = await fetch(`${API}/v2/agent/chat`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ messages }),
});
// Errors before the stream opens come back as JSON.
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.includes('text/event-stream')) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `HTTP ${res.status}`);
}
await readEvents(res, handleEvent);
}2. Consume the stream
Each frame is event: <name>, a newline, data: <json>, and a blank line.
async function readEvents(res, onEvent) {
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split('\n\n');
buffer = frames.pop();
for (const frame of frames) {
let name = '';
let data = '';
for (const line of frame.split('\n')) {
if (line.startsWith('event: ')) name = line.slice(7);
else if (line.startsWith('data: ')) data += line.slice(6);
}
if (name && data) onEvent(name, JSON.parse(data));
}
}
}3. Render products and looks
function handleEvent(name, data) {
switch (name) {
case 'text_delta':
appendAssistantText(data.delta);
break;
case 'search_results':
// data.kind is "outfit" (worn together) or "list" (alternatives).
renderProductRow(data.label, data.kind, data.results.map(toCard));
break;
case 'looks':
for (const look of data.looks) renderLook(look);
break;
case 'question':
renderQuestionCard(data); // send the chosen option's label as the next message
break;
case 'suggestions':
renderChips(data.suggestions); // keep only the latest set
break;
case 'done':
history = data.history; // keep it for the next turn
break;
case 'error':
showError(data.error);
break;
}
}
function toCard(product) {
const key = product.product.assets?.images?.[0]?.uploaded?.key;
return {
id: product.itemId,
name: product.product.name,
price: product.product.price,
currency: product.product.currency,
url: product.product.url,
image: key
? `${API}/v1/image/${key}`
: product.product.assets?.images?.[0]?.originalImageUrl,
};
}The product records on search_results and looks are complete. Draw cards straight away; you don't need another lookup.
4. Call the looks endpoint once per announced look
A looks event lists outfits but contains no images. Request each image separately. Send all the requests at once so the images appear as they finish.
function renderLook(look) {
const slot = showLookPlaceholder(look.label, look.items.map(toCard));
fetch(`${API}/v2/agent/looks`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
label: look.label,
itemIds: look.items.map((item) => item.itemId),
customInstructions: look.customInstructions,
}),
})
.then((res) => res.json().then((body) => ({ res, body })))
.then(({ res, body }) => {
if (!res.ok) throw new Error(body.error);
slot.setImage(body.visualization.imageData); // a data: URI
})
.catch(() => slot.showFailed());
}Images are generated at a 3:4 aspect ratio unless you pass preferences.aspectRatio. Rendering takes several seconds. The image comes back inline as a data: URI and is not hosted anywhere, so store it yourself if you need to show it again later.
5. Continue the conversation
On the next turn, send the stored history followed by the shopper's new message. The sendMessage function above already does this.
await sendMessage('I need something for a summer wedding, menswear.');
await sendMessage('Show me trousers to match'); // history from turn one is includedPrices in the shopper's country
A Shopify store can sell in several countries at different prices. Send the shopper's country as country (on a storefront, Shopify.country) and products come back at their prices, with their sales and their stock. See Prices in the shopper's country.
Where to go next
- Streaming the agent: every event with its payload, ordering, history handling, errors and retries.
- Cart integration: letting the agent read and change the shopper's cart.
- Shop the look: the experimental endpoint that finds the store's products in a photo of someone dressed.
- Prices in the shopper's country: sending
country, what comes back, sales, and what happens when a shopper's prices are not available.