Headless Integration

Table of Contents

Headless integration means you own the customer experience—web, app, or other clients—and Stella supplies questionnaire definitions, analysis, and matching variant IDs over HTTPS. You keep product presentation, accounts, cart, and checkout. No Stella JavaScript, iframe, or web component is required.

This is the API for that model.

The embeddable shop components (widget, chat, PDP button, popup) are documented separately in Getting Started and Documentation. Use those when Stella UI runs inside your storefront. Use this page when you render the quiz and results yourself.

If you already call POST /api/analyses for a custom quiz, switch to this v1 flow unless you need include_webhook_payload, CRM/webhook on create, or funnel-host submission without an API key. The practical gains today are a faster create (matching is separate and cacheable), matching a product or collection without re-running the quiz, and a published funnel definition for your UI. Larger catalogs can keep using the same Match filters with bigger candidate sets later; that is an extension of this API, not a different product.

Product catalog import

  • If you use Shopify, install our app to sync your products with our system.
  • For Shopware and other platforms, take a look at the Product Feed for a description of how to provide a CSV feed that we will sync every 12 hours. To create a proof of concept, you can also provide us with a CSV file to import manually.
  • For large product catalogs, we will soon provide an endpoint to import your products in batches and push catalog updates at scale.

What you can do with the data

Stella returns identifiers and questionnaire structure. You combine those with your own product data (titles, images, prices, stock, URLs).

SurfaceExampleHow to use the API
Custom quizNative app or your own web formGET the funnel, render steps, POST answers
Result pageCurated lists that match Stella tabsGET matches with resultPage=true, hydrate shopSystemIds
Collection or search“Your colours” badge or matching-only filterGET matches with productId / variantId for the current page or a bounded set
Product pageHighlight matching colourways or shadesGET matches for that product’s IDs
Saved profileReuse one analysis across sessionsStore the analysis id against your customer or session

Prices, availability, and purchase rules stay yours. Apply them at display time. If matching is slow or unavailable, keep normal shopping and omit optional badges.

Matching does not re-run image analysis. A second questionnaire submission creates a new analysis; Stella does not merge submissions into a customer account. Keep the link between your user/session and the analysis ID in your system.

The three operations

Your app → GET  /api/v1/funnels/{funnelId}     → steps for your quiz UI
        → POST /api/v1/analyses               → analysis id (no recommendations)
        → GET  /api/v1/analyses/{id}/matches  → matching variant IDs
Your app ← your catalog (titles, prices, stock, URLs)
OperationAuthYou sendStella returns
Funnel definitionCompany API Key (funnel.read)Funnel ID, optional locale and versionSteps and option keys for that Funnel Version
Create analysisCompany API Key (analysis.create)funnelId, answers, optional selfie{ id, funnelVersionId, isPreview }
Match variantsNone (analysis ID is the capability)Analysis ID, optional product/variant filtersmatchedVariants (your shopSystemId strings)

Matching is a separate GET so browsers and CDNs can cache it. Creating an analysis never returns recommended products and does not send CRM or webhooks.

Call these paths at https://api.askstella.ai/api/v1/... (the dashboard host serves the same routes if you already call app.askstella.ai).

Authentication

Funnel reads and analysis creates use a Company API Key. This is not the 25-character shop-component company key, not a dashboard session, and not a CRM webhook key.

  1. Get an invitation to the Stella platform from your Stella admin.
  2. In the Stella app, open Settings → Developer.
  3. Company Admins can create a key (name and optional expiry). The key value is shown once.
  4. Send it as a Bearer token. Do not put it in websites, mobile binaries, query strings, or logs.
Authorization: Bearer stella_ck_<key>
Content-Type: application/json

Keys are scoped to one company (max 20 active). Default scopes are funnel.read and analysis.create. A key cannot read another company’s funnels. Rotation is create a new key, then revoke the old one.

HTTPerrorWhen
401UNAUTHORIZEDMissing, malformed, revoked, or wrong-scope key
401API_KEY_EXPIREDexpiresAt is in the past

Keyed routes respond with Cache-Control: no-store. CORS allows Authorization and Idempotency-Key. Do not send cookies.

Match does not usethe authentication header. Anyone who knows an analysis ID can retrieve matching variant IDs for that analysis. The response does not include answers, Color Analysis, selfies, or contact data. Treat analysis IDs as unguessable capabilities (the same idea as a private result-page link).

GET /api/v1/funnels/{funnelId}

Loads the questionnaire for one funnel so you can render it yourself.

Query

ParameterRequiredMeaning
localeNoLanguage tag for translated step copy. Defaults to the company’s language, with fallback to the stored default text.
versionIdNoFunnel Version to return. Omit to use the current published version (or the current draft if nothing is published yet). Allowed values are only that published id or the current draft id.

Example

GET /api/v1/funnels/cm1234567890?locale=en
Authorization: Bearer stella_ck_…
{
  "id": "cm1234567890",
  "version": { "id": "cmversion1234567890", "number": 3 },
  "steps": [
    {
      "id": 101,
      "type": "HAIR_COLOR",
      "orderNumber": 0,
      "title": "What is your hair colour?",
      "selections": [
        {
          "key": "BROWN",
          "caption": "Brown",
          "imageUrl": "https://cdn.askstella.ai/images/funnel/selections/hair-color-brown.jpg",
          "orderNumber": 0
        }
      ]
    }
  ]
}

steps is the stored Funnel Version configuration (camelCase JSON). Titles and custom option captions come from that version and locale fallback. Submit the option key, not the caption. Built-in Color Analysis steps (hair, eyes, skin colour, undertone, selfie) are submitted as analysis scalars (hairColor, eyeColor, skinColor, selfie, …), not as selectionValues. Custom single- and multi-selection steps use selectionValues with stepId and selectedKey from this payload.

Omitted from every step: includeInGenericAnalysis, genericAnalysis, stepSettings, deleted, and per-step funnelId. Credentials and CRM settings are never returned.

Find the funnel id and version ids in the Stella dashboard (funnel Versions).

HTTPerrorWhen
404NOT_FOUNDUnknown funnel, or funnel belongs to another company
409FUNNEL_NOT_PUBLISHEDNo published version and no draft
400VERSION_NOT_FOUNDversionId is not the current published or draft version

POST /api/v1/analyses

Creates an Analysis from quiz answers. Same answer fields as legacy POST /api/analyses, with these differences:

  • Company API Key is required (analysis.create).
  • funnelId is required (funnel-subdomain hosts are not used for v1).
  • include_webhook_payload is rejected (query or body).
  • No matching, CRM, or webhook delivery.
  • Invalid or oversized selfies return 400 before the image model runs.
  • Total JSON body must be at most 4 MiB; decoded selfie at most 2 MiB (JPEG, PNG, GIF, or WebP; prefer JPEG).

Omit createdWithVersionId to use the current published Funnel Version (or draft if none is published). Send the current draft id to create a preview analysis (isPreview: true)—that is the main reason to pass a version id. A superseded published id is still accepted: answers are migrated in memory to the current published version, funnelVersionId is that published id, and payload_warnings asks you to update the integration.

Do not send caller-chosen analysis IDs, company IDs, or calculated Color Analysis results. Color type is computed server-side and is not included in this response.

POST /api/v1/analyses
Authorization: Bearer stella_ck_…
Content-Type: application/json
Idempotency-Key: quiz-completion-9001
{
  "funnelId": "cm1234567890",
  "createdWithVersionId": "cmversion1234567890",
  "hairColor": "BROWN",
  "eyeColor": "GREEN",
  "skinColor": 30,
  "selfie": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
  "selfieSource": "UPLOADED",
  "selectionValues": [
    { "stepId": 101, "selectedKey": "DRY" },
    { "stepId": 202, "selectedKey": "ACNE" }
  ]
}

Field meanings, enums, and selfie data-URL rules match the analyses API. v1 does not run frontend skincare-from-selfie analysis; skip that step or omit selfie for those funnels.

Success (200) — the analysis is persisted even when Color Analysis is null, and even when a selfie was sent but image analysis failed (no 503).

{
  "id": "cmanalysis9001",
  "funnelVersionId": "cmversion1234567890",
  "isPreview": false
}

Unexpected or missing answers usually still save the analysis and add payload_warnings (same behaviour as the legacy route). Handle that array so funnel changes do not go unnoticed.

{
  "id": "cmanalysis9001",
  "funnelVersionId": "cmversion1234567890",
  "isPreview": false,
  "payload_warnings": ["Funnel has a SKIN_COLOR step but skinColor is missing."]
}

Idempotency-Key (optional). Without it, every call creates a new analysis. With it, the key is scoped to your company and this route. The same key and same body replay the first response. A different body with the same key returns 409 IDEMPOTENCY_CONFLICT. A retry while the first call is still running returns 409 REQUEST_IN_PROGRESS with Retry-After: 2. Raw selfie bytes are not stored on the idempotency record (the digest uses a hash of the selfie field).

HTTPerrorWhen
400INVALID_JSONBody is not a JSON object
400INVALID_REQUESTMissing funnelId, or include_webhook_payload present
400(legacy validation)Unknown createdWithVersionId, invalid selfie (INVALID_SELFIE in the body)
404NOT_FOUNDFunnel missing or not in this company
409FUNNEL_NOT_PUBLISHEDFunnel has no version
409IDEMPOTENCY_CONFLICT / REQUEST_IN_PROGRESSIdempotency key reuse
413REQUEST_TOO_LARGEBody larger than 4 MiB

GET /api/v1/analyses/{analysisId}/matches

Public, read-only matching. Resolves company and funnel from the analysis. Evaluates Recommended Variants with the same matcher as the Stella result page (tagging, complexion and custom tags, SRL, Funnel AI, Fixed Set lift), after the same in-memory answer migration the storefront uses. A later published Funnel Version does not make Match return 409. Color Analysis is not recomputed. The analysis row is not updated.

HEAD and OPTIONS are supported. HEAD checks the query and analysis and returns the same cache headers as a successful GET, without running the matcher. OPTIONS is side-effect-free.

GET /api/v1/analyses/cmanalysis9001/matches

Query (only these keys; anything else is 400 INVALID_QUERY)

ParameterMeaning
Repeated variantIdYour Variant.shopSystemId values (exact, case-sensitive).
Repeated productIdYour Product.shopSystemId values; expanded to eligible variants of those products.
resultPage=trueRestrict to enabled result-page tabs and return tabs. Default is false.
localeTab labels/descriptions. Defaults to the company language.

IDs are deduplicated in request order. Within productId or variantId, values are OR’d. Product filters, variant filters, and result-page scope are AND’d. Omit filters to match the funnel’s full eligible assortment.

Unknown or out-of-scope IDs are omitted from the result (no “unresolved” field). Matcher failures return 5xx with MATCHER_FAILURE, never HTTP 200 with an empty list that hides an outage. A genuine empty match (nothing suitable) is 200 with "matchedVariants": [].

Preview / draft analyses are served so you can Match while implementing against draft. Those responses are not CDN-cached.

{
  "matchedVariants": ["SKU-100-BLUE-M", "SKU-100-BLUE-L"]
}

IDs are your shop-system variant IDs. Internal Stella primary keys are not returned as sellable IDs. The body does not include answers, selfies, contact fields, or product cards.

Result-page mode (resultPage=true) intersects matches with configured tabs. Products not on any tab are excluded (no fallback to the full assortment). If tabs are disabled or empty, both matchedVariants and tabs are []. tabs[].key is the tab’s CRM name. name / description are localized labels. maxItemsToShow is a presentation hint only; membership is not truncated. Null description / maxItemsToShow are omitted. matchedVariants is the union of tab variant lists in first-appearance order.

{
  "matchedVariants": ["SKU-100-BLUE-M", "SKU-200-RED-S"],
  "tabs": [
    { "key": "shirts", "name": "Shirts", "variants": ["SKU-100-BLUE-M"] },
    {
      "key": "dresses",
      "name": "Dresses",
      "description": "Your dresses",
      "maxItemsToShow": 3,
      "variants": ["SKU-200-RED-S"]
    }
  ]
}

Limits (explicit errors; no silent truncation)

LimitHTTPerror
Encoded query string > 8 KiB414URI_TOO_LONG
More than 100 productId + variantId values, empty IDs, bad resultPage, unknown query keys400INVALID_QUERY
More than 2,000 eligible input variants for the request422MATCHING_SCOPE_TOO_LARGE
Serialized body > 1 MiB422MATCHING_RESPONSE_TOO_LARGE
Unknown analysis404NOT_FOUND

For larger assortments, pass productId / variantId from the current page or a bounded collection rather than matching the entire catalog on every view.

Caching and CORS

Successful non-preview Match GET/HEAD responses:

Cache-Control: public, max-age=900, s-maxage=600, stale-while-revalidate
Access-Control-Allow-Origin: *

That matches shop-component init: 15 minutes in the browser, 10 minutes on the CDN, then stale-while-revalidate. Product, tagging, and tab publishes invalidate Stella’s caches. Preview Matches and all error responses use Cache-Control: no-store.

Call Match with credential-free fetch (no cookies, no Authorization). CORS is *; origins are not reflected.

Two analyses with the same matching answers share Stella’s internal recommendation cache. Different companies or Funnel Versions do not.

Compared with shop components and the legacy analyses API

Shop componentsPOST /api/analysesv1 headless
Quiz UIStella popup / embedYou or StellaYou
AuthCompany key in JSFunnel host or platformCompany API Key (funnel + create)
RecommendationsLocal storage / widgetOptional include_webhook_payloadSeparate public Match GET
CRM / webhookUnchangedOn completeNot triggered by v1 create

Keep using the webhook and legacy analyses API where you already depend on CRM payload shape or funnel-host submission. Do not mix include_webhook_payload into v1.