Create outfit compositions

post/v1/outfit/compositions

Retired. This endpoint answers 410 Gone for every API caller. It is documented for reference only; new integrations should render looks through the v2 agent API instead.

Takes the products the stylist recommended in a chat and turns them into complete outfits: a language model picks coherent combinations from the most recent recommendation, then an image model renders each combination as a single composed look (optionally on the shopper's own photo).

What it needs from the chat

The chat identified by chatId must belong to your organization and its most recent assistant message must contain at least one search slot with results — that is what "the most recent recommendation" means. Slots from earlier turns are also passed to the model as context and may be drawn on, but the latest turn is the primary source. Up to five products per slot are considered.

How many outfits you get

count asks for 1–10 outfits. It is then clamped to what the latest recommendation can actually support: the product of min(results in slot, 3) over the latest slots, capped at 10 and never below 1. A single-slot recommendation with two results therefore yields at most two outfits however large count was. Individual renders can fail; those are dropped, so the response may contain fewer outfits than the clamped count. The request only fails when none succeeds.

Images

Each render is uploaded to Stylor's image storage and returned as an imageUrl. When the upload fails the image is returned inline instead as a compressed JPEG data URL (imageSource: compressed), and if even that fails, as the original PNG data URL (imageSource: original). Renders use the 9:16 aspect ratio by default; override it through preferences.aspectRatio. At most three product photos (two when a user photo is supplied) are handed to the image model; the remaining products are described in text.

JSON vs. streaming

By default the call blocks until every outfit has been selected and rendered, then returns everything at once. Send stream: true to receive a text/event-stream instead: each outfit is announced as soon as the model has chosen it, its image follows as soon as it is rendered, and a done event carries the summary. Validation and quota errors are still returned as JSON with the usual status code before the stream opens; failures after that arrive as error events on the stream with HTTP status 200.

Persistence

Every successful outfit is stored with a compositionId (the id in the response) under your organization and the chat. When slotId is given, the ids are also written to that slot's compositions list in the chat, so they can be retrieved with the conversation. A slot update that fails does not fail the request.

Public keyapi.v1.outfit.compositionsDemo key OKStreams SSEDeprecated

Request body

application/jsonrequired
chatIdstringrequired

Id of a chat owned by your organization whose latest assistant message contains search slots with results.

e.g. "8b4d3f6e-2c1a-4f0e-9d7b-5a6c1e2f3a4b"
countinteger

Outfits to generate. Parsed as an integer ("3" and 3.7 both become 3); values outside 1–10 are rejected. Then clamped to the number of distinct combinations the latest recommendation supports.

default: 5min: 1max: 10e.g. 3
userPhotoUrlstring

Optional photo of the shopper to dress. A data:image/…;base64,… URL or a publicly fetchable https image URL. When present, the render composes the outfit on this person and only two product photos (preferring flat-lay shots) are passed to the image model.

e.g. "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
OutfitPreferences

Hints passed to the models. Stored alongside each composition. Unknown keys are kept but not read.

genderstring

Presented to the outfit-selection model as the shopper's gender. Free text; not validated.

e.g. "women"
stylestring

Free-text style direction for the outfit-selection model (for example relaxed weekend brunch).

e.g. "relaxed weekend brunch"
aspectRatiostring

Aspect ratio of the rendered image, as accepted by the image model (for example 9:16, 1:1, 3:4).

default: "9:16"e.g. "1:1"
slotIdstring

Optional id of a slot in the chat's latest assistant message. The generated composition ids are written to that slot's compositions list.

e.g. "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9"
streamboolean

Send true to receive a text/event-stream instead of a single JSON body.

default: falsee.g. false

Response

200In JSON mode, every outfit that rendered successfully plus a summary. In streaming mode (stream: true) the body is a text/event-stream; see the stream events below.
application/json
array<OutfitVisualization>required

One entry per outfit that rendered successfully.

idstring (uuid)required

Composition id. Also stored on the chat slot when slotId was supplied.

e.g. "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
imageUrlstringrequired

Where to load the rendered image. An https://stylor.ai/api/v1/image/… URL when imageSource is cdn; otherwise a data:image/…;base64,… URL.

e.g. "https://stylor.ai/api/v1/image/images/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.png"
imageSourcestringrequired

cdn — uploaded and served by URL; compressed — upload failed, inline JPEG (max 800px wide, quality 80); original — compression also failed, inline original PNG.

One ofcdncompressedoriginal
e.g. "cdn"
imageDatastring

JSON mode only. The uncompressed render as a data:image/…;base64,… URL, always included regardless of imageSource. This makes JSON responses large; prefer imageUrl and consider streaming mode, which omits this field.

e.g. "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
reasoningstringrequired

Same text as combination.reasoning.

e.g. "The cream knit and straight-leg jeans share a relaxed silhouette."
OutfitCombinationrequired
OutfitCompositionsMetadatarequired
totalCombinationsintegerrequired

Number of outfits returned (renders that failed are not counted).

e.g. 3
generationTimeintegerrequired

Wall-clock time for the whole request, in milliseconds.

e.g. 21870
usedUserPhotobooleanrequired

Whether userPhotoUrl was supplied.

e.g. false
cdnUploadsintegerrequired

Outfits whose image was uploaded (imageSource of cdn).

e.g. 3
compressedImagesintegerrequired

Outfits returned as inline compressed JPEG.

e.g. 0
originalImagesintegerrequired

Outfits returned as the inline original image.

e.g. 0
text/event-stream
string

SSE frames of the form event: <name> / data: <json> separated by a blank line. See the stream events for each payload.

Stream events

6

Each frame is event: <name> followed by data: <json> and a blank line. Events are listed in the order they normally arrive.

preparing

Sent immediately, before any lookup, so a client can lay out placeholder cards. count is the validated (not yet clamped) request count.

data
{
  "count": 3
}
combination

One outfit has been selected. index is its 0-based position and id the composition id; the matching visualization (or visualization_error) will carry the same id and index. Combinations arrive in order; images do not.

data
{
  "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "index": 0,
  "combination": {
    "name": "Weekend Neutrals",
    "description": "A soft, tonal outfit for a relaxed brunch.",
    "reasoning": "The cream knit and straight-leg jeans share a relaxed silhouette.",
    "items": [
      {
        "slotId": "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
        "slotType": "top",
        "productId": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
        "confidence": 0.92
      }
    ]
  }
}
visualization

The render for one combination is ready. Renders run concurrently, so these can arrive in any order and may interleave with later combination events. Unlike the JSON response, no inline imageData is included; imageUrl is a data URL when imageSource is not cdn.

data
{
  "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "index": 0,
  "imageUrl": "https://stylor.ai/api/v1/image/images/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.png",
  "imageSource": "cdn",
  "reasoning": "The cream knit and straight-leg jeans share a relaxed silhouette.",
  "combination": {
    "name": "Weekend Neutrals",
    "description": "A soft, tonal outfit for a relaxed brunch.",
    "reasoning": "The cream knit and straight-leg jeans share a relaxed silhouette.",
    "items": [
      {
        "slotId": "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
        "slotType": "top",
        "productId": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
        "confidence": 0.92
      }
    ]
  }
}
visualization_error

The render for one combination failed. The outfit is dropped from the saved set; the stream continues.

data
{
  "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "index": 1,
  "error": "Failed to generate this visualization"
}
done

Every render has settled and the surviving outfits are saved. metadata matches the JSON response. The stream closes after this event.

data
{
  "metadata": {
    "totalCombinations": 3,
    "generationTime": 21870,
    "usedUserPhoto": false,
    "cdnUploads": 3,
    "compressedImages": 0,
    "originalImages": 0
  }
}
error

A fatal failure; the stream closes after this event and no done follows. error is one of Chat not found or access denied, No outfit items found in most recent recommendation, Failed to generate outfit combinations, Failed to generate any visualizations, or Something went wrong generating your outfit visualizations. Let's try that again. for any unexpected failure.

data
{
  "error": "No outfit items found in most recent recommendation"
}

Errors

26

Every error is JSON with a single error string unless noted. Match on the status code; the message is for humans.

400
Request body must be valid JSON.

The request body could not be parsed as JSON. Checked before authentication.

400
Request body must be a JSON object.

The request body is empty, or parsed to something other than a JSON object. Checked before authentication.

404
Chat not found or access denied

No chat with that chatId belongs to your organization. Streaming mode: sent as an error event.

410
This endpoint has been retired (v1/outfit/compositions).

Always, for API callers. This check runs before authentication and consumes no quota.

422
chatId is required

chatId is missing or not a string. In streaming mode this is returned as JSON before the stream opens.

422
count must be between 1 and 10

count does not parse as an integer from 1 to 10. In streaming mode this is returned as JSON before the stream opens.

422
No outfit items found in most recent recommendation

The chat's latest assistant message has no search slot with results. Streaming mode: sent as an error event.

502
Failed to generate outfit combinations

The language model returned no usable combinations. Streaming mode: sent as an error event.

502
Failed to generate any visualizations

Every image render failed (for example no product had a usable photo). Streaming mode: sent as an error event.

curl -X POST "https://stylor.ai/api/v1/outfit/compositions" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "chatId": "8b4d3f6e-2c1a-4f0e-9d7b-5a6c1e2f3a4b",
  "count": 3
}'
{
  "visualizations": [
    {
      "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "imageUrl": "https://stylor.ai/api/v1/image/images/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.png",
      "imageSource": "cdn",
      "imageData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
      "reasoning": "The cream knit and straight-leg jeans share a relaxed silhouette, and the white sneakers keep the whole look light.",
      "combination": {
        "name": "Weekend Neutrals",
        "description": "A soft, tonal outfit for a relaxed brunch.",
        "reasoning": "The cream knit and straight-leg jeans share a relaxed silhouette, and the white sneakers keep the whole look light.",
        "items": [
          {
            "slotId": "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
            "slotType": "top",
            "productId": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
            "confidence": 0.92
          },
          {
            "slotId": "7a6b5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
            "slotType": "bottom",
            "productId": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
            "confidence": 0.88
          },
          {
            "slotId": "0e1f2a3b-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
            "slotType": "shoes",
            "productId": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
            "confidence": 0.81
          }
        ]
      }
    }
  ],
  "metadata": {
    "totalCombinations": 1,
    "generationTime": 18432,
    "usedUserPhoto": false,
    "cdnUploads": 1,
    "compressedImages": 0,
    "originalImages": 0
  }
}