API reference

The public API that the widget, the tag and the pixels call. Base URL: https://api.fitmytype.com. This page is generated from the API code, so it always lists every public endpoint. The machine-readable document is openapi.json (OpenAPI 3.1.0). Changes are listed in the changelog.

Every answer carries X-Request-Id and X-Api-Version. Every error has the shape {"error":{"code","message","request_id"}}; a 422 validation_failed also names the invalid fields in error.fields. A 429 answer has a Retry-After header with the seconds to wait.

Widget and catalogue

GET /v1/brands/{brand_id}/client

The client of a brand (for createWidget with brand_id only)

Public, cacheable 1 hour. An unknown brand, or one of a paused or churned client, answers 404 brand_not_found.

Parameters
NameInTypeDescription
brand_id requiredpathstringThe brand's numeric id.
Answer fields (200)
NameTypeDescription
client_id requiredinteger
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/plugins/customization

Widget customization (theme and texts) of a client

Browsers may cache it 5 minutes; the CDN never does (D40). From a domain the client does not allow, the answer is {eligible:false, reason:"not_whitelisted"} (D39).

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
Answer fields (200)
NameTypeDescription
theme requiredobject
i18n requiredobject
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/plugins/origin_check

Is the page that framed the widget iframe an allowed domain of the client?

Called once per page load by the iframe (T13.03.01) before it talks to the parent page. Exact host or wildcard subdomain, as CORS; www. and no www. differ unless both are listed, and http: pages count only outside production. Never cached. From a domain the client does not allow, the answer is {allowed:false, reason:"not_whitelisted"} (D39).

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
origin requiredquerystringThe parent page origin, e.g. https://www.trimtexstore.com.
Answer fields (200)
NameTypeDescription
allowed requiredboolean
reason"not_whitelisted"
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/plugins/frame_policy

Which pages may frame the widget iframe of a client (frame-ancestors)

Asked by the plugin host for every iframe page request (T13.03.02), which copies the value into the page's Content-Security-Policy: frame-ancestors. The same origins the origin check allows: https://<host> for each allowed domain, https://*.<host> for a wildcard, plus https://admin.shopify.com for a Shopify client (theme editor), plus the environment's back-office origin for every serving client (its live and reference previews frame the same page). An unknown, malformed or not-serving client gets 'none'. Also sent in the X-Fmt-Frame-Ancestors header. Never cached; follows a domain change within 60 s.

Parameters
NameInTypeDescription
cidquerystringThe client id from the iframe URL (?cid=).
Answer fields (200)
NameTypeDescription
frame_ancestors requiredstring
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/sizing_charts/{id_or_slug}

One size chart, published version, in the shape the widget and the engine read

By numeric id or unique name. Cached 5 minutes by the CDN and browsers, purged when the chart is published or deleted. An unknown chart, a chart of another client, a chart never published, or a paused or churned client answers 404 (never cached). From a domain the client does not allow, the answer is {eligible:false, reason:"not_whitelisted"} (D39).

Parameters
NameInTypeDescription
id_or_slug requiredpathstringThe chart's numeric id, or its unique name (slug) such as 1_Bottom.
client_id requiredquerystringThe client's numeric id.
systemquerystringSize system to present as primary (e.g. FR); default the chart's own. Unknown → 400 naming the chart's systems.
Answer fields (200)
NameTypeDescription
id requiredintegerAdded: the chart id; a recommendation records it with version.
version requiredintegerAdded: the published version served (0 = stored before versions).
revision requiredstringAdded: sha256 of the served version's content.
unique_name requiredstring
name requiredstring
gender required"female" | "male" | "unisex" | "babies" | "boys" | "girls" | null
body_part required"top" | "bottom" | "full" | "bosom" | "feet" | "head"
fit required"tight" | "regular" | "loose" | "extreme" | "custom" | null
basis required"body" | "garment"Added: garment charts carry garment values and ease (D11).
primary_system requiredobject
systems requiredarray of stringAdded: every size system the chart has labels in, primary first.
primary_measurements requiredarray of "Chest" | "Underchest" | "Waist" | "Hips" | "Height" | "Weight" | "ShoulderSpan" | "ArmLength" | "LegContour" | "Inseam" | "InseamFloor" | "FootLength" | "HeadCircumference"
length_unit required"cm" | "in"
comment requiredstring
sizes requiredarray of object
ease_cm requiredobject | null
measurements requiredarray of objectOne row per size and measurement: = for an exact value, a > min and a < max for a range.
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/plugin_references

Reference brands for the shopper reference step, most used first

The D19-compatible wire format (D91): type_of_references is cloth, shoe or head; the answer maps brand name → category slug → reference products, with most_used_brands (default 5, at most 20) and client_brand. With the client's plugin_use_client_references flag on and a non-empty list, only those brands in the merchant's order. Readable from any origin (*) and cached 10 minutes, keyed by the full URL. An unknown or paused client answers 404, a bad gender or type 422 naming the field, the database down 503 (errors never cached). From a domain the client does not allow: {eligible:false, reason:"not_whitelisted", references:{}, most_used_brands:[], client_brand:null} (D39).

Parameters
NameInTypeDescription
gender requiredquery"female" | "male" | "unisex" | "boys" | "girls" | "babies"D19 compatible: the shopper's gender.
type_of_references requiredquery"cloth" | "shoe" | "head"D19 compatible: cloth, shoe or head.
client_id requiredquerystringThe client's numeric id.
number_of_most_usedquerystringD19 compatible: how many most-used brand names (default 5, at most 20, 0 for none).
languagequerystringAdded: the language of the category labels (default en; a missing label falls back to EN).
Answer fields (200)
NameTypeDescription
references requiredobjectD19 compatible: brand name → category slug → reference products.
most_used_brands requiredarray of stringD19 compatible: brand names, most used first.
client_brand requiredstring | nullD19 compatible: the client's own brand when it is in the catalogue.
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422A query field is missing or invalid (error.fields names it).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/plugin_sizing_charts

A reference brand's published size charts for a category and gender

Only the reference catalogue's published, current charts of the brand (a merged brand answers for the kept one), for the shopper's gender and unisex (children: their own gender only), each with its size systems and sizes. An unknown brand or category answers []; no brand_id 422. Readable from any origin (*), cached 10 minutes. From a domain the client does not allow: {eligible:false, reason:"not_whitelisted"} (D39).

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
gender requiredquery"female" | "male" | "unisex" | "boys" | "girls" | "babies"D19 compatible: the shopper's gender.
statusquery"active"D19 compatible: always active.
brand_id requiredquerystringD19 compatible: the reference brand.
categoryquerystringD19 compatible: a category slug; without it every category of the brand.
languagequerystringAdded: the language of the category labels (default en; a missing label falls back to EN).
Answer fields (200)
NameTypeDescription
eligible requiredfalse
reason required"not_whitelisted"
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422A query field is missing or invalid (error.fields names it).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/sizes

The size system catalogue (EU, FR, UK, US, … and shoe scales)

The same for every shop: no client_id, readable from any origin (Access-Control-Allow-Origin: *), cached 5 minutes by the CDN and browsers. Ids never change (EU is 2). There is no write route (405). The catalogue unreadable answers 503 (never cached).

Parameters
NameInTypeDescription
languagequerystringLanguage of the names (fr, fr-FR). A language with no translation gets the English names.
Answer fields (200)
NameTypeDescription
items requiredarray of object
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/products/plugin_info

Is this product eligible, and the widget's set-up for it (compatible keys, then ours)

The compatible answer (D19, CONTRACTS §1.1) with our keys added after it; each field's description starts with D19 compatible: or Added:. Not eligible: only {eligible:false, reason} (D50 reasons). From a domain the client does not allow, from an unknown client, or without Origin (curl, a server), the answer is {eligible:false, reason:"not_whitelisted"} (D39). An eligible answer may be kept 60 s by the browser; the CDN never keeps it (D52). The database down answers 503 with Retry-After.

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
client_product_id requiredquerystringThe merchant's product id, exactly as in the feed (no trimming, no case change).
available_sizesquerystringThe page's size names, comma-separated (S,M,L). Only part of the cache key; never makes a product not eligible.
Answer fields (200)
NameTypeDescription
eligible requiredtrueD19 compatible: the widget may show.
social_proof requiredbooleanD19 compatible: social-proof data drives the reco.
reco_mode required"classic" | "social_proof" | "usual_size" | nullD19 compatible: explicit mode, then social proof, then chart, then usual size.
shoe_shape_slug requiredstring | nullD19 compatible: shoe last (E07), null for clothes.
widget_customization requiredobjectD19 compatible: widget flags; both true when the client saved none.
gender required"male" | "female" | "unisex" | "boys" | "girls" | "babies" | nullD19 compatible: product gender.
measurement_names requiredarray of stringD19 compatible: the chart's measurement names only (no ranges).
body_part required"clothes" | "feet" | "head" | nullD19 compatible: routing body part (E07).
sizes requiredarray of objectD19 compatible: the chart's sizes in chart order, order from 1.
sizing_chart_slug requiredstring | nullD19 compatible: the chart's slug.
comfort_renderer_theme requiredobjectD19 compatible: fit-scale colours (ours by default, D8).
has_references_step requiredbooleanD19 compatible: the reference-brands step is shown.
has_usual_size_step requiredbooleanD19 compatible: the usual-size step is shown.
preferred_size_system_id requiredinteger | nullD19 compatible: the id of usual_size_system_name in GET /v1/sizes (EU = 2); null when usual_size_list is empty.
usual_size_list requiredarray of stringD19 compatible: the sizes a usual-size shopper picks from, in that system (the merchant's list or the chart sizes): in usual-size mode, and for every product with a chart when the merchant shows the usual-size path (D105); [] otherwise.
usual_size_system_name requiredstring | nullD19 compatible: the size system code of usual_size_list (EU); null when the list is empty.
sizing_adjustment requirednumberD19 compatible: the product's merchant size offset (−1, 0 or +1; T04.07.03, D104), 0 by default. Fit, whitelist and note stay on the server.
reason requirednullAdded: null when eligible.
product requiredobjectAdded: the product's public fields (no database ids or validation data).
product_body_part required"bosom" | "top" | "bottom" | "full" | "feet" | "head" | nullAdded: the garment region.
steps requiredobjectAdded: which optional steps the flow shows.
fit_preferences requiredarray of "slim" | "regular" | "relaxed"Added: the fit preferences (slim, regular, relaxed) the chart has ease for; [] for body charts (D105).
garment_type required"top" | "jacket" | "tights" | "shorts" | "one_piece" | "shoe" | "helmet" | nullAdded: the 3D garment template the clothed body wears for the product (top, jacket, tights, shorts, one_piece, shoe, helmet): the merchant's pick, else the linking rule's, else derived from the category and body part; null = no garment, the widget shows the skin fit view. The widget also shows none when the merchant turned clothes off (plugin_show_garments in the customization theme) or the template is not published yet (T14.14.09).
garment_look required"base" | "tee" | nullAdded: the look of garment_type (D131 3): tee = the CC0 T-shirt (a top product whose category says t-shirt / tee / singlet, or the merchant's "T-shirt" pick), else base (the generated garment); null when garment_type is null. The widget asks the garment route for this look.
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

PUT /wrong_sizing_chart_products

Report that a product page's sizes do not match its size chart

Unchanged legacy path. The body is optional (the legacy SDK sends none): {page_sizes, status}, at most 200 sizes. The server computes the status itself with the shared size match. One report per product per day is kept (later ones update it). An unknown client or product, a paused client, or a domain the client does not allow is dropped with a 200 (D39).

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
client_product_id requiredquerystringThe merchant's product id.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
page_sizesarray of string
status"SOMEHOW_VALID" | "INVALID"
Answer fields (200)
NameTypeDescription
received requiredinteger1 when the report was stored.
droppedinteger
reason"not_whitelisted"
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/cross_sell_products

Suggested products for one cross-sell tab, with the shopper's size (compatible query)

One tab of suggested products for the product page (S07.12, D125), at most 12, each with the size the reco engine gives this profile on that product (null when it has no chart or the engine cannot decide). Latest releases: the same gender (and unisex) and body part, newest first. You might like: the same feed category, the same size chart first. Bought by people like you: kept by at least 5 shoppers close in gender, age, height and weight (training rows of S05.05), most kept first. Bought together: kept in at least 2 of the same orders, most shared orders first; full returns do not count. Only published products still in the feed, with a size in stock and an https page on an allowed domain of the client; never the current product. The cross-sell flag off or no match answers an empty list. An unknown product or chart answers {eligible:false, reason:"unknown_product"} like plugin_info; from a domain the client does not allow, or without Origin, {eligible:false, reason:"not_whitelisted", products:[]} (D39). A bad tab or value answers 422 validation_failed with error.fields. Answers are no-store; body values are never logged. The GET form is kept for compatibility; the widget sends the POST form, so body values never travel in a URL.

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
client_product_id requiredquerystringThe product page's product id (merchant id).
cross_sell_products_type requiredquery"recentProducts" | "similarProducts" | "boughtBySimilarPeopleProducts" | "boughtTogetherProducts"The tab: recentProducts (latest releases), similarProducts (you might like), boughtBySimilarPeopleProducts (bought by people like you), boughtTogetherProducts (bought together).
sizing_chart_slugquerystringThe product's chart; an unknown one answers not found.
genderquery"male" | "female" | "unisex" | "boys" | "girls" | "babies"
agequerystringYears, a whole number 0–120.
heightquerystringcm, 40–250.
weightquerystringkg, 2–300.
foot_lengthquerystringcm, 10–34 (shoe sizes).
client_host_namequerystringAccepted and ignored.
client_base_uriquerystringAccepted and ignored.
Answer fields (200)
NameTypeDescription
eligible requiredtrue
type required"recentProducts" | "similarProducts" | "boughtBySimilarPeopleProducts" | "boughtTogetherProducts"
products requiredarray of object
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422A field is missing or invalid (error.fields names it).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/cross_sell_products

Suggested products for one cross-sell tab, with the shopper's size (JSON body)

One tab of suggested products for the product page (S07.12, D125), at most 12, each with the size the reco engine gives this profile on that product (null when it has no chart or the engine cannot decide). Latest releases: the same gender (and unisex) and body part, newest first. You might like: the same feed category, the same size chart first. Bought by people like you: kept by at least 5 shoppers close in gender, age, height and weight (training rows of S05.05), most kept first. Bought together: kept in at least 2 of the same orders, most shared orders first; full returns do not count. Only published products still in the feed, with a size in stock and an https page on an allowed domain of the client; never the current product. The cross-sell flag off or no match answers an empty list. An unknown product or chart answers {eligible:false, reason:"unknown_product"} like plugin_info; from a domain the client does not allow, or without Origin, {eligible:false, reason:"not_whitelisted", products:[]} (D39). A bad tab or value answers 422 validation_failed with error.fields. Answers are no-store; body values are never logged. The same names as the GET query, as a JSON body; numbers as numbers.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | string
client_product_id requiredstringThe product page's product id (merchant id).
cross_sell_products_type required"recentProducts" | "similarProducts" | "boughtBySimilarPeopleProducts" | "boughtTogetherProducts"The tab: recentProducts (latest releases), similarProducts (you might like), boughtBySimilarPeopleProducts (bought by people like you), boughtTogetherProducts (bought together).
sizing_chart_slugstringThe product's chart; an unknown one answers not found.
gender"male" | "female" | "unisex" | "boys" | "girls" | "babies"
ageinteger | null
heightnumber | null
weightnumber | null
foot_lengthnumber | null
client_host_namestringAccepted and ignored.
client_base_uristringAccepted and ignored.
Answer fields (200)
NameTypeDescription
eligible requiredtrue
type required"recentProducts" | "similarProducts" | "boughtBySimilarPeopleProducts" | "boughtTogetherProducts"
products requiredarray of object
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422A field is missing or invalid (error.fields names it).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/integration_check

Counts of the integration events seen in the last 15 minutes

The docs snippet generator's Verify button (T12.08.04). Counts of product, cart and transaction_end events this client sent in the last 15 minutes, with test and invalid events counted apart; never any event data. Browser calls only from the docs origin. Answers are no-store: poll every 5 s. Limited to 30 calls a minute per IP (429 with Retry-After). A missing or non-numeric client_id answers 422 validation_failed with error.fields; an unknown client answers 404 not_found.

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id.
Answer fields (200)
NameTypeDescription
client_id requiredinteger
window_minutes required15The events counted are from this many minutes back.
poll_seconds required5Poll at this interval; the answer is never cached.
checked_at requiredstring
events requiredobject
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_foundNo client has this id (not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedclient_id is missing or not a whole positive number (validation_failed, with error.fields).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/legal/subprocessor-subscriptions

Subscribe an email address to sub-processor changes

The sub-processor page's form (T13.04.02, D130 2). Sends a confirm email; the address gets change emails only after its link is clicked (valid 7 days). The answer is the same for a new, pending or already subscribed address and never names the address. Browser calls only from the docs origin. Limited to 5 requests per hour per IP, 3 per day per address and 500 per day in total (429 with Retry-After). A bad address answers 422 validation_failed with error.fields and field_errors.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
email requiredstringThe address to notify.
Answer fields (202)
NameTypeDescription
status required"pending"
Answers
StatusError codesMeaning
202Accepted
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedNot a valid email address (validation_failed, with field_errors).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/legal/subprocessor-subscriptions/confirm

Confirm a sub-processor change subscription

The confirm link of the email (T13.04.02-TC11). Docs origin only for browsers.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
token requiredstringThe token from the email link.
Answer fields (200)
NameTypeDescription
status required"confirmed"
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_foundThe link is unknown, already used or older than 7 days (not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/legal/subprocessor-subscriptions/unsubscribe

Stop sub-processor change emails and delete the address

The one-click unsubscribe link of every change email (T13.04.02-TC12). The address is deleted at once. Docs origin only for browsers.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
token requiredstringThe token from the email link.
Answer fields (200)
NameTypeDescription
status required"unsubscribed"
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_foundThe link is unknown or the address is already removed (not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

Size recommendations

The size widget calls these endpoints itself: you do not need to call them to show sizes. This is a read-only overview of their request and answer shapes, so you can read what the widget sends and gets.

POST /v1/reco/clothes

Size-chart (classic) size recommendation for clothes: the D19-compatible answer keys, then ours (CONTRACTS §8)

From a shop page on an allowed domain of client_id (or a server with a recommend API key); anyone else gets {eligible:false, reason:"not_whitelisted", comfort:[]} (D39). Values outside the widget limits, unknown genders or shapes, unknown tape keys and a ± over 5 cm answer 422 validation_failed with error.fields (never the values). An unknown or not-eligible product, or a paused client, answers 404 product_not_eligible with its D50 reason. 60 calls a minute per tracking_id (429), on top of the client + IP limit. Answers are no-store; nothing the shopper sent is logged or stored. The database down answers 503. Quick answers go to the body-service predictor (sd per dimension); tape values in measurements_cm are used as entered (uncertainty_cm, default 1). comfort[0] is recommended_size ?? closest_size; status is recommended, between_sizes, needs_measurements (also when the predictor is down or untrained) or no_match. available never changes the pick. size_conversions (contract v2) holds every chart size in up to 3 other systems, the shop_country's system first. auto: true marks the silent page-load reco: the same answer, counted apart, and an incomplete saved profile answers needs_measurements instead of 422.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | stringThe client's numeric id (a number, as the D19 widget sends it, or digits).
product_id requiredstringThe merchant's product id, exactly as in the feed.
tracking_idstringThe device's tracking id; absent before analytics consent (D7). Never changes the size.
available_sizesarray of stringThe page's sizes (live stock). Only sets available; never changes the pick.
shop_countrystring
channelstring
languagestring
modelstring | number | nullThe A/B "Model Variation" value.
referencesarray of objectReference brands (used from S05.04).
autobooleanAuto-reco (T05.08.01): the widget asked silently at page load from the saved profile. Same answer; an incomplete profile answers needs_measurements instead of 422. Counted and logged apart.
gender required"male" | "female" | "unisex" | "boys" | "girls" | "babies"
agenumber
heightnumbercm
weightnumberkg
body_shapestring | nullFemale A|XA|H|X, male slug such as regular-athletic-narrow.
bra"N/A" | object | null{region, band, cup} (band as a number or text); {region:"N/A", band:"N/A", cup:"N/A"} = no bra.
sizing_chart_slugstring | nullIgnored when the product has a chart (D76).
with_comfortboolean
usualSizeobject | null
measurements_cmobjectTape measurements (measured path, D13), keyed by vocabulary id (chest, waist, hips…).
uncertainty_cmnumberThe shopper's own ± for tape values, 0–5 cm (default 1).
fit_preference"slim" | "regular" | "relaxed"
Answer fields (200)
NameTypeDescription
comfort requiredarray of objectD19: best first; comfort[0] is the shown size. Empty when none.
sizing_chart_info requiredobject | nullD19: the full chart used.
user_measurements requiredobjectD19: the estimated body under the chart measurement names; Height and Weight as given.
size_conversions requiredobjectD19, D89: every chart size (primary label) → its label in up to 3 other systems, the shop country's system first: {M: {FR: {size_name: "38"}, UK: {size_name: "10"}}}. Never the primary system; an empty entry for a size with no other label; present for every status.
contract_version required2
id requiredstringReco id, sent as reco_id in the reco event.
engine_version requiredstring
body_model_versionstring
predictor_versionstring
chart_id requiredstring | nullThe chart used; null for a social-proof answer on a product with no chart.
chart_revision requiredstring | nullThe chart's content hash; null when chart_id is null.
product_id requiredstring
mode required"classic" | "social_proof" | "usual_size" | "shoes" | "head"
input_source required"questions" | "measurements" | "mixed" | "usual_size" | "profile"
status required"recommended" | "between_sizes" | "needs_measurements" | "no_match"
recommended_size requiredstring | null
closest_size requiredstring | null
alternatives requiredarray of string
direction_hint required"size_up_for_relaxed" | "size_down_for_slim" | null
deciding_measurementstring | nullD98, T07.09.09 (additive, contract version unchanged): the predicted primary measurement (vocabulary id, e.g. waist) whose tape value would most raise the chance of the right size; null when the gain is under the engine threshold, when every primary value is entered, for needs_measurements and single-size answers. Absent on usual-size, social-proof, shoe and head answers.
available requiredboolean | null
message_key required"RESULT_RECOMMENDED" | "RESULT_BETWEEN_SIZES" | "RESULT_NEEDS_MEASUREMENTS" | "RESULT_NO_MATCH" | "RESULT_NOT_AVAILABLE" | "PRODUCT_UNIQUE_SIZE" | "EDGE_OF_CHART" | "SORRY_SOCIAL_PROOF"
missing_measurements requiredarray of string
fit_preference required"slim" | "regular" | "relaxed" | "brand"
measurements_used requiredobject
candidates requiredarray of object
comforts requiredobjectVocabulary id → level of the shown size.
primary_size_system requiredstring
incoherent_referencesarray of objectS05.04: references that contradict the body data or each other (D91), by their place in the request. On every classic answer (empty when none is flagged); a flag never blocks the size.
social_proof_sizesarray of objectSocial proof (S05.05): each size's share among similar shoppers who kept the product, best first, ratios rounded to 2 decimals summing to 1; empty for no_match.
first_sizeobjectSocial proof (D19 key): the top size and its ratio; {name: null, ratio: null} for no_match.
second_sizeobjectSocial proof (D19 key): the second size, or nulls.
primary_systemobject | nullSocial proof (D19 key): the size system of the names, {name: "EU"}; null without a chart.
sizing_chart_idinteger | nullSocial proof (D19 key): the product's linked chart, or null when it has none.
ratio_highbooleanSocial proof: the top ratio is above 0.6.
social_proof_level"product" | "category_gender_body_part" | "body_part_gender" | nullSocial proof: which rows gave the shares (the product, then fallbacks); null for no_match.
social_proof_samplesintegerSocial proof: how many similar shoppers were used (or found, for no_match).
morph_paramsarray of number
foundbooleanUsual size (D19 key): a size was found.
namestring | nullUsual size (D19 key): the size, as recommended_size.
size_orderinteger | nullUsual size (D19 key): 0-based place in the chart.
is_usual_sizetrueUsual size: lets the widget send usual_size_add_to_cart.
offset_appliedintegerUsual size: sizes actually moved from the usual size. Classic with a product offset (T04.07.03): sizes the offset moved (0 at the chart's edge).
sizing_adjustmentnumberUsual size, or classic with a product offset: the product's merchant offset that was used.
precision requirednullStays null until calibrated (S14.09).
accuracy_validated requiredfalse
confidence_probability requirednull
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404Unknown resource, or a product with no reco (product_not_eligible with reason).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedValues fail validation (validation_failed); error.fields names them.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/reco/shoes

Foot-length size recommendation: shoes (CONTRACTS §8, S05.07)

From a shop page on an allowed domain of client_id (or a server with a recommend API key); anyone else gets {eligible:false, reason:"not_whitelisted", comfort:[]} (D39). Values outside the widget limits, unknown genders or shapes, unknown tape keys and a ± over 5 cm answer 422 validation_failed with error.fields (never the values). An unknown or not-eligible product, or a paused client, answers 404 product_not_eligible with its D50 reason. 60 calls a minute per tracking_id (429), on top of the client + IP limit. Answers are no-store; nothing the shopper sent is logged or stored. The database down answers 503. foot_length (cm, 10–34) is matched with the product's feet chart, ±0.25 cm (D111); a chart that gives the shoe's inner length adds the toe allowance (0.5–1.0 cm). Without it, adult male / female get an estimate from height (source: predicted, a larger ±), moved by shoe references (type_of_references=shoe); with no height, shoe references alone give it; children and unisex without a foot length answer needs_measurements. With none of foot length, height or a reference: 422 naming foot_length. A product that is not feet is a 422 naming product_id. One zone, FootLength (TOO_TIGHT = too short … TOO_LOOSE = too long); between two sizes the size where the foot is too short is never first. The §8 shape with mode: shoes, no deciding_measurement, no morph_params.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | stringThe client's numeric id (a number, as the D19 widget sends it, or digits).
product_id requiredstringThe merchant's product id, exactly as in the feed.
tracking_idstringThe device's tracking id; absent before analytics consent (D7). Never changes the size.
available_sizesarray of stringThe page's sizes (live stock). Only sets available; never changes the pick.
shop_countrystring
channelstring
languagestring
modelstring | number | nullThe A/B "Model Variation" value.
referencesarray of objectReference brands (used from S05.04).
autobooleanAuto-reco (T05.08.01): the widget asked silently at page load from the saved profile. Same answer; an incomplete profile answers needs_measurements instead of 422. Counted and logged apart.
gender required"male" | "female" | "unisex" | "boys" | "girls" | "babies"
agenumber
heightnumbercm
weightnumberkg
foot_lengthnumbercm, 10–34. Optional with height or a shoe reference.
Answer fields (200)
NameTypeDescription
comfort requiredarray of objectD19: best first; comfort[0] is the shown size. Empty when none.
sizing_chart_info requiredobject | nullD19: the full chart used.
user_measurements requiredobjectD19: the estimated body under the chart measurement names; Height and Weight as given.
size_conversions requiredobjectD19, D89: every chart size (primary label) → its label in up to 3 other systems, the shop country's system first: {M: {FR: {size_name: "38"}, UK: {size_name: "10"}}}. Never the primary system; an empty entry for a size with no other label; present for every status.
contract_version required2
id requiredstringReco id, sent as reco_id in the reco event.
engine_version requiredstring
body_model_versionstring
predictor_versionstring
chart_id requiredstring | nullThe chart used; null for a social-proof answer on a product with no chart.
chart_revision requiredstring | nullThe chart's content hash; null when chart_id is null.
product_id requiredstring
mode required"classic" | "social_proof" | "usual_size" | "shoes" | "head"
input_source required"questions" | "measurements" | "mixed" | "usual_size" | "profile"
status required"recommended" | "between_sizes" | "needs_measurements" | "no_match"
recommended_size requiredstring | null
closest_size requiredstring | null
alternatives requiredarray of string
direction_hint required"size_up_for_relaxed" | "size_down_for_slim" | null
deciding_measurementstring | nullD98, T07.09.09 (additive, contract version unchanged): the predicted primary measurement (vocabulary id, e.g. waist) whose tape value would most raise the chance of the right size; null when the gain is under the engine threshold, when every primary value is entered, for needs_measurements and single-size answers. Absent on usual-size, social-proof, shoe and head answers.
available requiredboolean | null
message_key required"RESULT_RECOMMENDED" | "RESULT_BETWEEN_SIZES" | "RESULT_NEEDS_MEASUREMENTS" | "RESULT_NO_MATCH" | "RESULT_NOT_AVAILABLE" | "PRODUCT_UNIQUE_SIZE" | "EDGE_OF_CHART" | "SORRY_SOCIAL_PROOF"
missing_measurements requiredarray of string
fit_preference required"slim" | "regular" | "relaxed" | "brand"
measurements_used requiredobject
candidates requiredarray of object
comforts requiredobjectVocabulary id → level of the shown size.
primary_size_system requiredstring
incoherent_referencesarray of objectS05.04: references that contradict the body data or each other (D91), by their place in the request. On every classic answer (empty when none is flagged); a flag never blocks the size.
social_proof_sizesarray of objectSocial proof (S05.05): each size's share among similar shoppers who kept the product, best first, ratios rounded to 2 decimals summing to 1; empty for no_match.
first_sizeobjectSocial proof (D19 key): the top size and its ratio; {name: null, ratio: null} for no_match.
second_sizeobjectSocial proof (D19 key): the second size, or nulls.
primary_systemobject | nullSocial proof (D19 key): the size system of the names, {name: "EU"}; null without a chart.
sizing_chart_idinteger | nullSocial proof (D19 key): the product's linked chart, or null when it has none.
ratio_highbooleanSocial proof: the top ratio is above 0.6.
social_proof_level"product" | "category_gender_body_part" | "body_part_gender" | nullSocial proof: which rows gave the shares (the product, then fallbacks); null for no_match.
social_proof_samplesintegerSocial proof: how many similar shoppers were used (or found, for no_match).
morph_paramsarray of number
foundbooleanUsual size (D19 key): a size was found.
namestring | nullUsual size (D19 key): the size, as recommended_size.
size_orderinteger | nullUsual size (D19 key): 0-based place in the chart.
is_usual_sizetrueUsual size: lets the widget send usual_size_add_to_cart.
offset_appliedintegerUsual size: sizes actually moved from the usual size. Classic with a product offset (T04.07.03): sizes the offset moved (0 at the chart's edge).
sizing_adjustmentnumberUsual size, or classic with a product offset: the product's merchant offset that was used.
precision requirednullStays null until calibrated (S14.09).
accuracy_validated requiredfalse
confidence_probability requirednull
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404Unknown resource, or a product with no reco (product_not_eligible with reason).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedValues fail validation (validation_failed); error.fields names them.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/reco/head

Head-circumference size recommendation: helmets and hats (CONTRACTS §8, S05.07)

From a shop page on an allowed domain of client_id (or a server with a recommend API key); anyone else gets {eligible:false, reason:"not_whitelisted", comfort:[]} (D39). Values outside the widget limits, unknown genders or shapes, unknown tape keys and a ± over 5 cm answer 422 validation_failed with error.fields (never the values). An unknown or not-eligible product, or a paused client, answers 404 product_not_eligible with its D50 reason. 60 calls a minute per tracking_id (429), on top of the client + IP limit. Answers are no-store; nothing the shopper sent is logged or stored. The database down answers 503. head_circumference (cm; adults 50–65, boys / girls 48–58, babies 40–58) is matched with the product's head chart, ±0.5 cm (D109). It is never estimated from height or weight: without it a helmet reference (type_of_references=head) gives it, and with neither the answer is 422 naming head_circumference. A product that is not head is a 422 naming product_id. One girth zone, HeadCircumference. The §8 shape with mode: head, no deciding_measurement, no morph_params.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | stringThe client's numeric id (a number, as the D19 widget sends it, or digits).
product_id requiredstringThe merchant's product id, exactly as in the feed.
tracking_idstringThe device's tracking id; absent before analytics consent (D7). Never changes the size.
available_sizesarray of stringThe page's sizes (live stock). Only sets available; never changes the pick.
shop_countrystring
channelstring
languagestring
modelstring | number | nullThe A/B "Model Variation" value.
referencesarray of objectReference brands (used from S05.04).
autobooleanAuto-reco (T05.08.01): the widget asked silently at page load from the saved profile. Same answer; an incomplete profile answers needs_measurements instead of 422. Counted and logged apart.
gender required"male" | "female" | "unisex" | "boys" | "girls" | "babies"
agenumber
heightnumbercm
weightnumberkg
head_circumferencenumbercm. Optional with a helmet reference.
Answer fields (200)
NameTypeDescription
comfort requiredarray of objectD19: best first; comfort[0] is the shown size. Empty when none.
sizing_chart_info requiredobject | nullD19: the full chart used.
user_measurements requiredobjectD19: the estimated body under the chart measurement names; Height and Weight as given.
size_conversions requiredobjectD19, D89: every chart size (primary label) → its label in up to 3 other systems, the shop country's system first: {M: {FR: {size_name: "38"}, UK: {size_name: "10"}}}. Never the primary system; an empty entry for a size with no other label; present for every status.
contract_version required2
id requiredstringReco id, sent as reco_id in the reco event.
engine_version requiredstring
body_model_versionstring
predictor_versionstring
chart_id requiredstring | nullThe chart used; null for a social-proof answer on a product with no chart.
chart_revision requiredstring | nullThe chart's content hash; null when chart_id is null.
product_id requiredstring
mode required"classic" | "social_proof" | "usual_size" | "shoes" | "head"
input_source required"questions" | "measurements" | "mixed" | "usual_size" | "profile"
status required"recommended" | "between_sizes" | "needs_measurements" | "no_match"
recommended_size requiredstring | null
closest_size requiredstring | null
alternatives requiredarray of string
direction_hint required"size_up_for_relaxed" | "size_down_for_slim" | null
deciding_measurementstring | nullD98, T07.09.09 (additive, contract version unchanged): the predicted primary measurement (vocabulary id, e.g. waist) whose tape value would most raise the chance of the right size; null when the gain is under the engine threshold, when every primary value is entered, for needs_measurements and single-size answers. Absent on usual-size, social-proof, shoe and head answers.
available requiredboolean | null
message_key required"RESULT_RECOMMENDED" | "RESULT_BETWEEN_SIZES" | "RESULT_NEEDS_MEASUREMENTS" | "RESULT_NO_MATCH" | "RESULT_NOT_AVAILABLE" | "PRODUCT_UNIQUE_SIZE" | "EDGE_OF_CHART" | "SORRY_SOCIAL_PROOF"
missing_measurements requiredarray of string
fit_preference required"slim" | "regular" | "relaxed" | "brand"
measurements_used requiredobject
candidates requiredarray of object
comforts requiredobjectVocabulary id → level of the shown size.
primary_size_system requiredstring
incoherent_referencesarray of objectS05.04: references that contradict the body data or each other (D91), by their place in the request. On every classic answer (empty when none is flagged); a flag never blocks the size.
social_proof_sizesarray of objectSocial proof (S05.05): each size's share among similar shoppers who kept the product, best first, ratios rounded to 2 decimals summing to 1; empty for no_match.
first_sizeobjectSocial proof (D19 key): the top size and its ratio; {name: null, ratio: null} for no_match.
second_sizeobjectSocial proof (D19 key): the second size, or nulls.
primary_systemobject | nullSocial proof (D19 key): the size system of the names, {name: "EU"}; null without a chart.
sizing_chart_idinteger | nullSocial proof (D19 key): the product's linked chart, or null when it has none.
ratio_highbooleanSocial proof: the top ratio is above 0.6.
social_proof_level"product" | "category_gender_body_part" | "body_part_gender" | nullSocial proof: which rows gave the shares (the product, then fallbacks); null for no_match.
social_proof_samplesintegerSocial proof: how many similar shoppers were used (or found, for no_match).
morph_paramsarray of number
foundbooleanUsual size (D19 key): a size was found.
namestring | nullUsual size (D19 key): the size, as recommended_size.
size_orderinteger | nullUsual size (D19 key): 0-based place in the chart.
is_usual_sizetrueUsual size: lets the widget send usual_size_add_to_cart.
offset_appliedintegerUsual size: sizes actually moved from the usual size. Classic with a product offset (T04.07.03): sizes the offset moved (0 at the chart's edge).
sizing_adjustmentnumberUsual size, or classic with a product offset: the product's merchant offset that was used.
precision requirednullStays null until calibrated (S14.09).
accuracy_validated requiredfalse
confidence_probability requirednull
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404Unknown resource, or a product with no reco (product_not_eligible with reason).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedValues fail validation (validation_failed); error.fields names them.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/reco/social_proof

Social-proof size recommendation: sizes kept by similar shoppers (CONTRACTS §8, S05.05)

From a shop page on an allowed domain of client_id (or a server with a recommend API key); anyone else gets {eligible:false, reason:"not_whitelisted", comfort:[]} (D39). Values outside the widget limits, unknown genders or shapes, unknown tape keys and a ± over 5 cm answer 422 validation_failed with error.fields (never the values). An unknown or not-eligible product, or a paused client, answers 404 product_not_eligible with its D50 reason. 60 calls a minute per tracking_id (429), on top of the client + IP limit. Answers are no-store; nothing the shopper sent is logged or stored. The database down answers 503. For clothes, shoes and headwear in social-proof mode (the product's reco_mode override, or the client's default mode): gender, age, height and weight are required, foot_length / head_circumference sharpen shoes and headwear, sizing_chart_slug is null. The shares come from the shopper's nearest neighbours (same gender, about 50) among the shoppers who kept the product, falling back to its category + body part, then its body part; social_proof_sizes lists them best first, rounded to 2 decimals and summing to 1, and the top one is recommended_size (available: false when out of stock; no other size is swapped in). The older widget keys first_size, second_size, size_conversions, primary_system and sizing_chart_id (null without a chart) are kept. Fewer than the configured minimum (default 30) similar shoppers at every level, or no training data yet, answer status: no_match with message_key: SORRY_SOCIAL_PROOF, empty social_proof_sizes and null first_size / second_size. A product that is not in social-proof mode answers 404 product_not_eligible with reason: not_social_proof. Shares change only after the next daily training run; training data that cannot be read answers 503.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | stringThe client's numeric id (a number, as the D19 widget sends it, or digits).
product_id requiredstringThe merchant's product id, exactly as in the feed.
tracking_idstringThe device's tracking id; absent before analytics consent (D7). Never changes the size.
available_sizesarray of stringThe page's sizes (live stock). Only sets available; never changes the pick.
shop_countrystring
channelstring
languagestring
modelstring | number | nullThe A/B "Model Variation" value.
referencesarray of objectReference brands (used from S05.04).
autobooleanAuto-reco (T05.08.01): the widget asked silently at page load from the saved profile. Same answer; an incomplete profile answers needs_measurements instead of 422. Counted and logged apart.
gender required"male" | "female" | "unisex" | "boys" | "girls" | "babies"
agenumber
heightnumbercm
weightnumberkg
foot_lengthnumber
head_circumferencenumber
sizing_chart_slugstring | null
Answer fields (200)
NameTypeDescription
comfort requiredarray of objectD19: best first; comfort[0] is the shown size. Empty when none.
sizing_chart_info requiredobject | nullD19: the full chart used.
user_measurements requiredobjectD19: the estimated body under the chart measurement names; Height and Weight as given.
size_conversions requiredobjectD19, D89: every chart size (primary label) → its label in up to 3 other systems, the shop country's system first: {M: {FR: {size_name: "38"}, UK: {size_name: "10"}}}. Never the primary system; an empty entry for a size with no other label; present for every status.
contract_version required2
id requiredstringReco id, sent as reco_id in the reco event.
engine_version requiredstring
body_model_versionstring
predictor_versionstring
chart_id requiredstring | nullThe chart used; null for a social-proof answer on a product with no chart.
chart_revision requiredstring | nullThe chart's content hash; null when chart_id is null.
product_id requiredstring
mode required"classic" | "social_proof" | "usual_size" | "shoes" | "head"
input_source required"questions" | "measurements" | "mixed" | "usual_size" | "profile"
status required"recommended" | "between_sizes" | "needs_measurements" | "no_match"
recommended_size requiredstring | null
closest_size requiredstring | null
alternatives requiredarray of string
direction_hint required"size_up_for_relaxed" | "size_down_for_slim" | null
deciding_measurementstring | nullD98, T07.09.09 (additive, contract version unchanged): the predicted primary measurement (vocabulary id, e.g. waist) whose tape value would most raise the chance of the right size; null when the gain is under the engine threshold, when every primary value is entered, for needs_measurements and single-size answers. Absent on usual-size, social-proof, shoe and head answers.
available requiredboolean | null
message_key required"RESULT_RECOMMENDED" | "RESULT_BETWEEN_SIZES" | "RESULT_NEEDS_MEASUREMENTS" | "RESULT_NO_MATCH" | "RESULT_NOT_AVAILABLE" | "PRODUCT_UNIQUE_SIZE" | "EDGE_OF_CHART" | "SORRY_SOCIAL_PROOF"
missing_measurements requiredarray of string
fit_preference required"slim" | "regular" | "relaxed" | "brand"
measurements_used requiredobject
candidates requiredarray of object
comforts requiredobjectVocabulary id → level of the shown size.
primary_size_system requiredstring
incoherent_referencesarray of objectS05.04: references that contradict the body data or each other (D91), by their place in the request. On every classic answer (empty when none is flagged); a flag never blocks the size.
social_proof_sizesarray of objectSocial proof (S05.05): each size's share among similar shoppers who kept the product, best first, ratios rounded to 2 decimals summing to 1; empty for no_match.
first_sizeobjectSocial proof (D19 key): the top size and its ratio; {name: null, ratio: null} for no_match.
second_sizeobjectSocial proof (D19 key): the second size, or nulls.
primary_systemobject | nullSocial proof (D19 key): the size system of the names, {name: "EU"}; null without a chart.
sizing_chart_idinteger | nullSocial proof (D19 key): the product's linked chart, or null when it has none.
ratio_highbooleanSocial proof: the top ratio is above 0.6.
social_proof_level"product" | "category_gender_body_part" | "body_part_gender" | nullSocial proof: which rows gave the shares (the product, then fallbacks); null for no_match.
social_proof_samplesintegerSocial proof: how many similar shoppers were used (or found, for no_match).
morph_paramsarray of number
foundbooleanUsual size (D19 key): a size was found.
namestring | nullUsual size (D19 key): the size, as recommended_size.
size_orderinteger | nullUsual size (D19 key): 0-based place in the chart.
is_usual_sizetrueUsual size: lets the widget send usual_size_add_to_cart.
offset_appliedintegerUsual size: sizes actually moved from the usual size. Classic with a product offset (T04.07.03): sizes the offset moved (0 at the chart's edge).
sizing_adjustmentnumberUsual size, or classic with a product offset: the product's merchant offset that was used.
precision requirednullStays null until calibrated (S14.09).
accuracy_validated requiredfalse
confidence_probability requirednull
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404Unknown resource, or a product with no reco (product_not_eligible with reason).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedValues fail validation (validation_failed); error.fields names them.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/reco/usual_size

Usual-size recommendation: the shopper's usual size moved by fit feeling and desired fit (CONTRACTS §8)

From a shop page on an allowed domain of client_id (or a server with a recommend API key); anyone else gets {eligible:false, reason:"not_whitelisted", comfort:[]} (D39). Values outside the widget limits, unknown genders or shapes, unknown tape keys and a ± over 5 cm answer 422 validation_failed with error.fields (never the values). An unknown or not-eligible product, or a paused client, answers 404 product_not_eligible with its D50 reason. 60 calls a minute per tracking_id (429), on top of the client + IP limit. Answers are no-store; nothing the shopper sent is logged or stored. The database down answers 503. The usual size (one of plugin_info.usual_size_list, read in size_system_id or else the product's preferred system) moves by the fit answers: narrow and oversize +½ size, comfy and slim −½, plus the product's own offset, rounded away from zero, at most ±2 (D89). A sizing_adjustment sent here is ignored. Answers the §8 shape with mode: usual_size and the D19 keys found, name, size_order, precision, plus is_usual_size and offset_applied. A name outside the list, or an unknown size_system_id, is a 422 naming the field. Needs no predictor.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | stringThe client's numeric id (a number, as the D19 widget sends it, or digits).
product_id requiredstringThe merchant's product id, exactly as in the feed.
tracking_idstringThe device's tracking id; absent before analytics consent (D7). Never changes the size.
available_sizesarray of stringThe page's sizes (live stock). Only sets available; never changes the pick.
shop_countrystring
channelstring
languagestring
modelstring | number | nullThe A/B "Model Variation" value.
referencesarray of objectReference brands (used from S05.04).
autobooleanAuto-reco (T05.08.01): the widget asked silently at page load from the saved profile. Same answer; an incomplete profile answers needs_measurements instead of 422. Counted and logged apart.
size_system_idinteger | stringA size system id of GET /v1/sizes (EU is 2). Default: the product's preferred system.
usual_size_name requiredstringOne of plugin_info.usual_size_list.
fit_feeling required"narrow" | "perfect" | "comfy"How the usual size fits: narrow, perfect or comfy.
desired_fit required"slim" | "regular" | "oversize"How this product should fit: slim, regular or oversize.
sizing_adjustmentnumberIgnored: the product's own offset is used (D89).
Answer fields (200)
NameTypeDescription
comfort requiredarray of objectD19: best first; comfort[0] is the shown size. Empty when none.
sizing_chart_info requiredobject | nullD19: the full chart used.
user_measurements requiredobjectD19: the estimated body under the chart measurement names; Height and Weight as given.
size_conversions requiredobjectD19, D89: every chart size (primary label) → its label in up to 3 other systems, the shop country's system first: {M: {FR: {size_name: "38"}, UK: {size_name: "10"}}}. Never the primary system; an empty entry for a size with no other label; present for every status.
contract_version required2
id requiredstringReco id, sent as reco_id in the reco event.
engine_version requiredstring
body_model_versionstring
predictor_versionstring
chart_id requiredstring | nullThe chart used; null for a social-proof answer on a product with no chart.
chart_revision requiredstring | nullThe chart's content hash; null when chart_id is null.
product_id requiredstring
mode required"classic" | "social_proof" | "usual_size" | "shoes" | "head"
input_source required"questions" | "measurements" | "mixed" | "usual_size" | "profile"
status required"recommended" | "between_sizes" | "needs_measurements" | "no_match"
recommended_size requiredstring | null
closest_size requiredstring | null
alternatives requiredarray of string
direction_hint required"size_up_for_relaxed" | "size_down_for_slim" | null
deciding_measurementstring | nullD98, T07.09.09 (additive, contract version unchanged): the predicted primary measurement (vocabulary id, e.g. waist) whose tape value would most raise the chance of the right size; null when the gain is under the engine threshold, when every primary value is entered, for needs_measurements and single-size answers. Absent on usual-size, social-proof, shoe and head answers.
available requiredboolean | null
message_key required"RESULT_RECOMMENDED" | "RESULT_BETWEEN_SIZES" | "RESULT_NEEDS_MEASUREMENTS" | "RESULT_NO_MATCH" | "RESULT_NOT_AVAILABLE" | "PRODUCT_UNIQUE_SIZE" | "EDGE_OF_CHART" | "SORRY_SOCIAL_PROOF"
missing_measurements requiredarray of string
fit_preference required"slim" | "regular" | "relaxed" | "brand"
measurements_used requiredobject
candidates requiredarray of object
comforts requiredobjectVocabulary id → level of the shown size.
primary_size_system requiredstring
incoherent_referencesarray of objectS05.04: references that contradict the body data or each other (D91), by their place in the request. On every classic answer (empty when none is flagged); a flag never blocks the size.
social_proof_sizesarray of objectSocial proof (S05.05): each size's share among similar shoppers who kept the product, best first, ratios rounded to 2 decimals summing to 1; empty for no_match.
first_sizeobjectSocial proof (D19 key): the top size and its ratio; {name: null, ratio: null} for no_match.
second_sizeobjectSocial proof (D19 key): the second size, or nulls.
primary_systemobject | nullSocial proof (D19 key): the size system of the names, {name: "EU"}; null without a chart.
sizing_chart_idinteger | nullSocial proof (D19 key): the product's linked chart, or null when it has none.
ratio_highbooleanSocial proof: the top ratio is above 0.6.
social_proof_level"product" | "category_gender_body_part" | "body_part_gender" | nullSocial proof: which rows gave the shares (the product, then fallbacks); null for no_match.
social_proof_samplesintegerSocial proof: how many similar shoppers were used (or found, for no_match).
morph_paramsarray of number
foundbooleanUsual size (D19 key): a size was found.
namestring | nullUsual size (D19 key): the size, as recommended_size.
size_orderinteger | nullUsual size (D19 key): 0-based place in the chart.
is_usual_sizetrueUsual size: lets the widget send usual_size_add_to_cart.
offset_appliedintegerUsual size: sizes actually moved from the usual size. Classic with a product offset (T04.07.03): sizes the offset moved (0 at the chart's edge).
sizing_adjustmentnumberUsual size, or classic with a product offset: the product's merchant offset that was used.
precision requirednullStays null until calibrated (S14.09).
accuracy_validated requiredfalse
confidence_probability requirednull
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404Unknown resource, or a product with no reco (product_not_eligible with reason).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedValues fail validation (validation_failed); error.fields names them.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

Events and tracking

Where the tag, the widget and your server send events. Every event needs a non-null tracking_id; see Event schemas for each event's fields.

POST /events

Send one tracking event, an array or {events:[…]} of up to 50 (64 KiB)

One event {client_id, event, tracking_id, …} (the compatible form, D19), a JSON array or {events:[…]}. text/plain (sendBeacon) is read as JSON. The call is queued and answered 202 {received, accepted, rejected} (D72); events are validated, enriched and stored by the workers. An unknown event is listed in rejected (a single event: 400 unknown_event); an event_id already sent with other data is event_id_conflict (a single event: 409). An unknown client answers 400 unknown_client; a bot or a paused client is dropped with a 2xx. Events from a domain the client does not allow are dropped with a 200 (D39). Limits: the per-call limit plus 200 events/s per tracking_id and 5,000 events/s per client (429). The queue down answers 503 queue_unavailable; another content type 415.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger
event requiredstring
tracking_idstring
timestamp_msinteger
event_idstring
schema_version1
user_agentstring
shop_countrystring | null
channelstring | null
instance_idstring
product_idstring
brand_idinteger | null
product_associatedboolean
ab_dataobject
testEventtrue
extraobject
Answer fields (202)
NameTypeDescription
received requiredintegerEvents in the call (the compatible key, D19).
acceptedintegerEvents taken into the queue (or already stored).
rejectedarray of objectEvents refused at the door, by their index in the call.
droppedintegerEvents dropped without an error to fix: origin not allowed (D39), a bot, a paused client.
reason"not_whitelisted" | "bot" | "client_not_serving"
Answers
StatusError codesMeaning
202Accepted
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /debug

Integration-script heartbeat and debug events (__debug__)

Same body forms, limits, origin rules and answer as POST /events; every item is stored as a __debug__ event (kept 7 days).

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger
event requiredstring
tracking_idstring
timestamp_msinteger
event_idstring
schema_version1
user_agentstring
shop_countrystring | null
channelstring | null
instance_idstring
product_idstring
brand_idinteger | null
product_associatedboolean
ab_dataobject
testEventtrue
extraobject
Answer fields (202)
NameTypeDescription
received requiredintegerEvents in the call (the compatible key, D19).
acceptedintegerEvents taken into the queue (or already stored).
rejectedarray of objectEvents refused at the door, by their index in the call.
droppedintegerEvents dropped without an error to fix: origin not allowed (D39), a bot, a paused client.
reason"not_whitelisted" | "bot" | "client_not_serving"
Answers
StatusError codesMeaning
202Accepted
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /events/mixpanel/{name}

Product-analytics proxy: one UX event under its name (the compatible sendMixpanelEvent)

The compatible body {payload: {client_id, tracking_id, …event fields}, mixpanel_properties: {$referrer, $screen_width, $screen_height, utm_source}}; only those four page properties are kept. The name (URL-encoded) is a catalogue event name or a mapped older product-analytics name (Plugin Opened → plugin_open, Size Validated → size_validated, …); any other name is listed in rejected as unknown_event (202). The event then goes through the same intake as POST /events (origin rules and the D39 soft refusal, bots, rate limits, 64 KiB, the queue) and is stored like it (ux events are kept 90 days). No vendor is called in the request: with the client's analytics forwarding on, the worker forwards stored UX events later (T08.08.02).

Parameters
NameInTypeDescription
name requiredpathstringThe event name, URL-encoded, such as size_validated or Size%20Validated.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
payload requiredobjectThe event without its name: client_id, tracking_id and the event's fields.
mixpanel_propertiesobject
Answer fields (202)
NameTypeDescription
received requiredintegerEvents in the call (the compatible key, D19).
acceptedintegerEvents taken into the queue (or already stored).
rejectedarray of objectEvents refused at the door, by their index in the call.
droppedintegerEvents dropped without an error to fix: origin not allowed (D39), a bot, a paused client.
reason"not_whitelisted" | "bot" | "client_not_serving"
Answers
StatusError codesMeaning
202Accepted
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /events/pixel/shopify

Shopify Web Pixel event (cart, checkout)

Sent from Shopify's pixel sandbox, so the body's origin (the shop page) must be an allowed domain of client_id; otherwise the event is dropped with a 200. An optional ingest_key is verified when sent: a wrong one answers 401 invalid_ingest_key. Accepted events are queued (202) like POST /events; checkout becomes transaction_end.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
client_id requiredinteger | string
originstring
event requiredstring
ingest_keyobjectOptional fmt_ik_… key (a string); verified when sent.
tracking_idstring | null
shop_countrystring | null
product_idstring | number | null
variant_idstring | number | null
timestampstring
Answer fields (202)
NameTypeDescription
received requiredintegerEvents in the call (the compatible key, D19).
acceptedintegerEvents taken into the queue (or already stored).
rejectedarray of objectEvents refused at the door, by their index in the call.
droppedintegerEvents dropped without an error to fix: origin not allowed (D39), a bot, a paused client.
reason"not_whitelisted" | "bot" | "client_not_serving"
Answers
StatusError codesMeaning
202Accepted
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

3D body

GET /v1/body/schema

The measurement vocabulary (ids, label keys, groups, ranges, definitions) and the body model version

Served from memory, built once per model version from the shared vocabulary; fitter-only dimensions (pelvisGirth) are left out. ETag names the model version and a digest of the vocabulary; cached 5 min. Model data with no shopper data: any origin may read it (D82). The body-service down or loading answers 503.

Answer fields (200)
NameTypeDescription
modelVersion requiredstring
units required"cm"
dimensions requiredarray of object
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/body/default

The default female or male body of the current body model (a public, immutable CDN artifact)

Without modelVersion: a 302 (cached 60 s) to ?bodyType=…&modelVersion=<current>. With it: the body-service's default-body report (params, measurements, rest joints, tape paths), Cache-Control: public, max-age=31536000, immutable, built once per body type and model version (no job per shopper). Another model version answers 404. Model data with no shopper data: any origin may read it (D82).

Parameters
NameInTypeDescription
bodyType requiredquery"female" | "male"female or male.
modelVersionquerystringBody model version (anny-0.6.1-fmt-1). Without it the answer is a 302 to the current one.
Answer fields (200)
NameTypeDescription
modelVersion requiredstringBody model version; part of the URL and of every cache key.
bodyType required"female" | "male"
params requiredarray of number[logScale, ...morphs] of the default body.
measuredCm requiredobjectEvery mesh dimension of the vocabulary, in cm.
Answers
StatusError codesMeaning
200OK
302To the current model version's immutable URL.
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/body/predict

Simple-mode answers → every body measurement with its own sd, and the live body's morph params

Anonymous shopper call from an allowed domain of client_id (D14); another origin, no Origin or an unknown or paused client is refused (403). Units are cm and kg. Synchronous, answered by the body-service predictor within 1 s (no job). Boys and girls get {status:"needs_measurements"} while the predictor has no child data. Values outside the widget's limits, unknown fields, a shape of the other gender or a bra on a male body answer 422 validation_failed with error.fields (never the values). No answer and no measurement is logged or stored. Rate limited per device (tracking_id, else client and IP): 429. The body-service down or slow answers 503; no trained predictor answers 501 predictor_not_available with accuracyValidated:false. accuracyValidated stays false until the accuracy validation (S14.09) signs off.

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id (or the X-Fmt-Client header).
tracking_idquerystringThe widget's tracking id: the device the rate limit counts (else client and IP).

Request body: application/json, text/plain

Request body fields
NameTypeDescription
gender required"female" | "male" | "boys" | "girls"
age requiredinteger
heightCm requirednumber
weightKg requirednumber
bodyShapestring | null
bra"N/A" | object | null
enteredobject
Answer fields (200)
NameTypeDescription
status required"predicted"
bodyType required"female" | "male"
measurements requiredobject
morphParams requiredarray of number
morphParamLabels requiredarray of string
bodyGapCm requiredobject
outsideTrainingData requiredboolean
modelVersion requiredstring
predictorVersion requiredstring
accuracyValidated requiredboolean
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
501predictor_not_availableNo trained predictor yet (predictor_not_available, accuracyValidated:false).
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/body/fit

Fit the body to tape values in a background job (202 + the job)

Anonymous shopper call from an allowed domain of client_id (D14); another origin, no Origin or an unknown or paused client is refused (403). Needs Idempotency-Key. Answers 202 with the job and Location; poll GET /v1/body/jobs/{id} every retryAfterMs. The request and the result are stored encrypted and kept 24 h. Limits: per device (tracking_id, else client and IP; 15 a minute) and per client (429), at most 100 queued fits per client (429). The body-service down or loading answers 503.

Parameters
NameInTypeDescription
client_id requiredquerystringThe client's numeric id (or the X-Fmt-Client header).
tracking_idquerystringThe widget's tracking id: the device the rate limit counts (else client and IP).
Idempotency-Key requiredheaderstring

Request body: application/json, text/plain

Request body fields
NameTypeDescription
bodyType required"female" | "male"
measurementsCm requiredobject
lockedarray of string
parametersarray of number
shapeobject
populationobject
Answer fields (202)
NameTypeDescription
id requiredstring
kind required"fit"
state required"queued" | "running" | "done" | "failed" | "cancelled"
createdAt requiredstring
finishedAt requiredstring | null
expiresAt requiredstring
attempts requiredinteger
retryAfterMs requiredinteger | null
modelVersion requiredstring | null
error requiredobject | null
results requiredobject | null
Answers
StatusError codesMeaning
202Accepted
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
409conflictThe Idempotency-Key was used with another body (conflict).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/body/jobs/{id}

A body job's state (queued, running, done, failed, cancelled) and, when done, its result links

Anonymous shopper call from an allowed domain of client_id (D14); another origin, no Origin or an unknown or paused client is refused (403). Another client's job, or an unknown id, is 404; after 24 h 410. retryAfterMs (also Retry-After) is 300 ms during the first 3 s, then longer; null once final. A failed job carries an error code (invalid_request, body_service_unavailable, deadline_exceeded, fit_failed).

Parameters
NameInTypeDescription
id requiredpathstringThe job id from POST /v1/body/fit.
client_id requiredquerystringThe client's numeric id (or the X-Fmt-Client header).
tracking_idquerystringThe widget's tracking id: the device the rate limit counts (else client and IP).
Answer fields (200)
NameTypeDescription
id requiredstring
kind required"fit"
state required"queued" | "running" | "done" | "failed" | "cancelled"
createdAt requiredstring
finishedAt requiredstring | null
expiresAt requiredstring
attempts requiredinteger
retryAfterMs requiredinteger | null
modelVersion requiredstring | null
error requiredobject | null
results requiredobject | null
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

DELETE /v1/body/jobs/{id}

Cancel a queued or running body job

Anonymous shopper call from an allowed domain of client_id (D14); another origin, no Origin or an unknown or paused client is refused (403). A queued or running job becomes cancelled and never gets a result, even when its fit ends later. A job already done, failed or cancelled stays as it is, with cancelled: false.

Parameters
NameInTypeDescription
id requiredpathstringThe job id from POST /v1/body/fit.
client_id requiredquerystringThe client's numeric id (or the X-Fmt-Client header).
tracking_idquerystringThe widget's tracking id: the device the rate limit counts (else client and IP).
Answer fields (200)
NameTypeDescription
id requiredstring
kind required"fit"
state required"queued" | "running" | "done" | "failed" | "cancelled"
createdAt requiredstring
finishedAt requiredstring | null
expiresAt requiredstring
attempts requiredinteger
retryAfterMs requiredinteger | null
modelVersion requiredstring | null
error requiredobject | null
results requiredobject | null
cancelled requiredboolean
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/body/results/{file}

A fitted body: <id>.glb (Y-up metres, joints as nodes), <id>.json (the fit report) or <id>.obj

Anonymous shopper call from an allowed domain of client_id (D14); another origin, no Origin or an unknown or paused client is refused (403). Only for a done job of this client (else 404); 410 after 24 h. The GLB has the feet at y=0 and, with morphs=1, one morph target per morph parameter. The JSON report holds body numbers only. The old .mtl routes are gone (404).

Parameters
NameInTypeDescription
file requiredpathstring<job id>.glb, <job id>.json or <job id>.obj.
client_id requiredquerystringThe client's numeric id (or the X-Fmt-Client header).
tracking_idquerystringThe widget's tracking id: the device the rate limit counts (else client and IP).
morphsquery"0" | "1"1: the GLB carries one morph target per morph parameter (GLB only).
Answer fields (200)
NameTypeDescription
modelVersion requiredstring
bodyType required"female" | "male"
units required"cm"
coordinateSystem requiredstring
requestedCm requiredobject
constrained requiredarray of string
notConstrained requiredarray of string
params requiredarray of number
paramLabels requiredarray of string
measuredCm requiredobject
errorsCm requiredobject
gapsCmobject
toleranceCm requirednumber
matched requiredboolean
message requiredstring
shape requiredobject
cupSize requiredstring | null
cupConvention requiredstring | null
solver requiredobject
vertexCount requiredinteger
triangleCount requiredinteger
restJoints requiredarray of object
paths requiredobject
pathNotes requiredobject
convention requiredstring
limitations requiredstring
populationobject
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/body/morph_basis

The client morph basis GLB: base mesh, 16 positive and 16 negative morphs, bust grid, joints, paths

Without modelVersion: a 302 (cached 60 s) to the current version. With it: the GLB (model/gltf-binary, gzip when accepted), Cache-Control: public, max-age=31536000, immutable; another model version is 404, a bodyType other than female/male or a lod other than 1/2 is 400. The manifest (bounds, target scales, joint deltas, measurement indices) is at asset.extras.fmtMorphBasis (contract: morphBasisManifestSchema). Model data with no shopper data: any origin may read it (D82).

Parameters
NameInTypeDescription
bodyType requiredquery"female" | "male"female or male.
lod requiredquery"1" | "2"1 full resolution (Body Studio), 2 decimated (widget).
modelVersionquerystringBody model version (anny-0.6.1-fmt-1). Without it the answer is a 302 to the current one.
Answers
StatusError codesMeaning
200OK
302To the current model version's immutable URL.
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/body/garments/{template}

A garment GLB bound to the body: rest mesh, 41 morph targets in the basis order, binding, fit zones

T14.14.02 (D127–D129). Without modelVersion: a 302 (cached 60 s) to the current version. With it: the GLB (model/gltf-binary, brotli when accepted, else gzip), Cache-Control: public, max-age=31536000, immutable. An unknown template, bodyType, lod (only 1) or look (tee is a top look) is 400 naming the field; another model version is 404 not_found; a template not built yet (shoe, helmet) is 404 garment_not_built; the body-service down is 503, never cached. The manifest is at asset.extras.fmtGarment (contract: garmentManifestSchema). Model data with no shopper data: any origin may read it (D82).

Parameters
NameInTypeDescription
template requiredpath"top" | "jacket" | "tights" | "shorts" | "one_piece" | "shoe" | "helmet"Garment template (top, jacket, …).
bodyType requiredquery"female" | "male"female or male.
lod requiredquery"1"Level of detail: only 1 (the full Anny mesh, D92).
lookquery"base" | "tee"base (generated, default) or tee (the CC0 T-shirt, top only).
modelVersionquerystringBody model version (anny-0.6.1-fmt-1). Without it the answer is a 302 to the current one.
Answers
StatusError codesMeaning
200OK
302To the current model version's immutable URL.
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
410goneExpired: jobs and results are kept 24 h (gone).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedThe body fails validation (validation_failed); error.fields names the fields, never the values.
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

API keys

GET /v1/api_keys/me

The scoped API key of this call (server-to-server)

Send Authorization: Bearer fmt_sk_…. A missing or invalid key answers 401 invalid_api_key. With a key, POST /events needs the events:write scope (403 insufficient_scope), and a call may only name the key's own client.

Answer fields (200)
NameTypeDescription
key_id requiredstring
client_id requiredinteger
scopes requiredarray of "catalog:read" | "recommend" | "catalog:write" | "analytics" | "orders:write" | "launch" | "events:write"
expires_at requiredstring
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

Shopify app

POST /v1/shopify/setup-requests

Ask FitMyType to set up the Shopify app through collaborator access

The docs site's white-glove form (T12.01.06). Browser calls only from the docs origin. One open ops ticket per shop: a second request adds to it. Limited to 5 requests per hour per IP, 5 per day per shop and 200 per day in total (429 with Retry-After). A bad address or code answers 422 validation_failed with fields and a top-level field_errors map of plain messages. The code is never sent back.

Request body: application/json, text/plain

Request body fields
NameTypeDescription
shop_domain requiredstringThe shop's xxx.myshopify.com address; a pasted admin URL is accepted.
request_code requiredstringShopify's collaborator request code (Settings → Users → Security).
Answer fields (201)
NameTypeDescription
id requiredinteger
shop_domain requiredstring
status required"open" | "closed"
added_to_existing requiredbooleanTrue when the shop already had an open request: this one was added to it.
created_at requiredstring
Answers
StatusError codesMeaning
201Created
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
422validation_failedInvalid shop address or request code (validation_failed, with field_errors).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/shopify/app

The Shopify app's embedded home page (the app's application_url)

HTML page Shopify frames in the admin (T12.01.05). Only https://<shop> and https://admin.shopify.com may frame it (Content-Security-Policy: frame-ancestors); without a valid shop nobody may. It holds no shop data: it loads App Bridge, then the checklist from GET /v1/shopify/home/status.

Parameters
NameInTypeDescription
shopquerystring
hostquerystring
embeddedquerystring
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401invalid_api_key invalid_ingest_keyA sent API or ingest key is not valid (invalid_api_key, invalid_ingest_key).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

GET /v1/shopify/home/status

The app home's five setup checks for the shop of the session token

Store connected, products imported (count, pending validation, order history backfill), size button added (published theme's product template), tracking connected (web pixel), first events received (product, cart, transaction_end in the last 24 h). The shop comes only from the session token: a shop naming another shop answers 404. Shopify's theme and pixel checks run at most once a minute per shop. Accept: text/html answers the checklist as an HTML fragment. A missing or invalid token answers 401 with X-Shopify-Retry-Invalid-Session-Request: 1.

Parameters
NameInTypeDescription
shopquerystring
Authorization requiredheaderstring
Answer fields (200)
NameTypeDescription
shop requiredstring
state required"installed" | "waiting_for_ops" | "not_connected"
steps requiredarray of object
done requiredinteger
total required5
missing_scopes requiredarray of string
approve_url requiredstring
dashboard_url requiredstring
support_email requiredstring
checked_at requiredstring
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401unauthorizedMissing or invalid Shopify session token (unauthorized).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/shopify/home/dashboard-link

The app home's "Open dashboard": a one-time back-office sign-in link

With the Shopify session token, Shopify is asked who the admin user is (online token exchange; the token is not kept). When a back-office member of the shop's client has that verified email (not staff, no two-factor sign-in), the answer is {kind: sso, url} with a code in the URL fragment, valid 60 s and usable once (POST /bo/v1/auth/shopify-sso); otherwise {kind: login, url} (the sign-in page).

Parameters
NameInTypeDescription
Authorization requiredheaderstring
Answer fields (200)
NameTypeDescription
kind required"sso" | "login"
url requiredstring
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401unauthorizedMissing or invalid Shopify session token (unauthorized).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/shopify/session

The Shopify app opens: install on the first open, then the shop's state

Called by the embedded app home with Authorization: Bearer <Shopify session token> (T12.01.01). The first open exchanges the session token for an expiring offline token (sealed, never returned), links the shop to the one client that has its domain, writes the fitmytype_app.client_id metafield, connects the web pixel and starts the first import. A shop no client has, or two clients have, waits for ops (waiting_for_ops). A missing or invalid token answers 401 with X-Shopify-Retry-Invalid-Session-Request: 1; 503 when the app is not set up on this server.

Parameters
NameInTypeDescription
Authorization requiredheaderstring
Answer fields (200)
NameTypeDescription
shop requiredstring
state required"installed" | "waiting_for_ops" | "uninstalled" | "redacted"
client_id requiredinteger | null
source_id requiredinteger | null
message requiredstring | nullWhat the merchant reads while ops links the shop; null otherwise.
missing_scopes requiredarray of string
metafield requiredobject | null
pixel requiredobject | null
import requiredobject | null
Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401unauthorizedMissing or invalid Shopify session token (unauthorized).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)

POST /v1/shopify/webhooks

Shopify's webhooks to the app (uninstall, scope changes, GDPR)

Only Shopify calls this. X-Shopify-Hmac-SHA256 (HMAC-SHA256 of the raw body, keyed by the app's credentials) is checked before the body is read: wrong or missing answers 401 with an empty body; a valid one answers 200 with an empty body (never the D38 error format). Topics: app/uninstalled, app/scopes_update, customers/data_request, customers/redact, shop/redact; repeats are dropped by X-Shopify-Webhook-Id.

Request body: application/json, text/plain

Answers
StatusError codesMeaning
200OK
400bad_request invalid_json invalid_client_idMalformed request or client_id (bad_request, invalid_json, invalid_client_id).
401Wrong or missing HMAC signature (empty body).
403not_whitelistedPreflight from an origin the client does not allow (not_whitelisted), or a key without the scope or of another client.
404not_found brand_not_foundUnknown resource (not_found, brand_not_found).
413payload_too_largeBody over the route's limit (payload_too_large).
429rate_limitedRate limited (rate_limited); wait Retry-After seconds. (header Retry-After)
503capacity_busy rate_limit_unavailableBusy or a dependency is down (capacity_busy, rate_limit_unavailable, …); retry after Retry-After. (header Retry-After)