{
  "openapi": "3.1.0",
  "info": {
    "title": "looks.nyc API",
    "version": "0.2.0",
    "summary": "The style index of New York menswear boutiques.",
    "description": "Structured, searchable knowledge about menswear boutiques in SoHo, Nolita, Williamsburg, East Williamsburg and Bushwick: every indexed item (category, materials, colors, fit, season weight, price, stock) and every boutique's profile (aesthetics, character scales, store model, observed price tier). Built for AI agents. The same index is available as a remote MCP server at https://mcp.looks.nyc (tools: search_products, search_retailers, get_retailer, get_product, get_vocab). Get a free key with POST /v1/keys (no auth, no body) or at https://looks.nyc/keys; no account needed. Style labels are model judgments with dates; prices and stock come from shop catalogs and can change. Requests are strict: an unknown query parameter or an unknown enum value is rejected with 400 invalid_parameter (naming the allowed values), never silently ignored, and search responses echo the filters actually applied in applied_filters. Common parameter aliases (store, shop, categories, colour, sizes, ...) are accepted for the canonical name; any other unknown parameter is rejected.",
    "contact": {
      "url": "https://looks.nyc"
    }
  },
  "servers": [
    {
      "url": "https://api.looks.nyc",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "externalDocs": {
    "description": "LLM-oriented overview",
    "url": "https://looks.nyc/llms.txt"
  },
  "tags": [
    {
      "name": "products"
    },
    {
      "name": "retailers"
    },
    {
      "name": "meta"
    }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "health",
        "summary": "Service health (no auth).",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/vocab": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "getVocab",
        "summary": "The controlled style vocabulary: aesthetics (with definitions), character scales, store models, price tiers, product categories, genders.",
        "responses": {
          "200": {
            "description": "Vocabulary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products/search": {
      "get": {
        "tags": [
          "products"
        ],
        "operationId": "searchProducts",
        "summary": "Search indexed items across all boutiques.",
        "description": "Full-text search (web-search syntax: quotes, OR, -exclude) over title, brand, category, materials, colors, details and description, combined with filters. Results link to the product on the shop's own site. Unless a category is requested, non-core categories (headwear, accessories, underwear_socks, jewelry_watches, home_goods, fragrance_beauty, non_apparel) rank below clothing, and items with unspecified gender (gender_basis none) rank below explicit men/unisex items; queries that name the item itself are not penalised. By default only menswear-relevant shops (relevance core, plausible) and non-duplicate items without fee/gift-card/bad-price flags are searched.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Free text, e.g. \"waxed cotton\" jacket -women."
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Product category. Comma-separated list. Allowed: outerwear, tailoring, knitwear, shirts, tees_tops, sweats_hoodies, trousers, denim, shorts, footwear, bags, headwear, accessories, underwear_socks, swim, non_apparel, dresses_skirts, home_goods, jewelry_watches, fragrance_beauty."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "subcategory",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Specific garment type, e.g. chore jacket, fisherman sweater."
          },
          {
            "name": "gender",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Who it's cut for. Comma-separated list. Allowed: men, women, unisex, kids, unknown."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "min_price",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0
            },
            "description": "Minimum price, USD. Only USD-priced items are matched; items priced in other currencies stay visible but are excluded from price filters and sorts."
          },
          {
            "name": "max_price",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0
            },
            "description": "Maximum price, USD. Only USD-priced items are matched; items priced in other currencies stay visible but are excluded from price filters and sorts."
          },
          {
            "name": "material",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Materials, e.g. corduroy, selvedge denim, shetland wool. Comma-separated list."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "color",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Plain color words. Comma-separated list."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "brand",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Brand name (fuzzy)."
          },
          {
            "name": "retailer",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Retailer slugs (see /v1/retailers) or exact display names, case-insensitive. Comma-separated list. An unknown retailer is a 400 with suggestions, never an empty filter."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "aesthetic",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Only items from boutiques carrying these aesthetics. Comma-separated list. Allowed: american_workwear, french_workwear, japanese_americana, artisanal_craft, ivy_prep, british_country, heritage_sportswear, streetwear, skate, surf_coastal, outdoor_gorpcore, techwear, parisian_minimal, quiet_luxury, sartorial_tailoring, relaxed_tailoring, avant_garde, vintage_archive, western."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "in_stock",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "Only items currently in stock."
          },
          {
            "name": "on_sale",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Only discounted items."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "relevance",
                "price_asc",
                "price_desc",
                "newest"
              ]
            },
            "description": "Default: relevance with q, else newest."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            },
            "description": "Page size (max 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's next_cursor."
          },
          {
            "name": "relevance",
            "in": "query",
            "required": false,
            "explode": false,
            "style": "form",
            "schema": {
              "type": "string",
              "description": "Shop relevance tiers to include. Comma-separated list. Allowed: core, plausible, low, noise, unknown. Default: core, plausible. Ignored when `retailer` names shops explicitly."
            }
          },
          {
            "name": "include_flagged",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Also return duplicates (dup_of / shop_dup_of), fee/gift-card lines and items with a zero/tiny/absurd price, which are hidden by default."
          },
          {
            "name": "neighborhood",
            "in": "query",
            "required": false,
            "explode": false,
            "style": "form",
            "schema": {
              "type": "string",
              "description": "Only items from shops with a storefront in these neighborhoods. Comma-separated list. Allowed: bushwick, east_williamsburg, nolita, soho, williamsburg."
            }
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "explode": false,
            "style": "form",
            "schema": {
              "type": "string",
              "description": "Sizes in stock, normalised: 32, M, 10.5, 7 1/8, one size (US/EU prefixes ignored; Medium = M). Comma-separated list. Items without size data never match."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "applied_filters": {
                      "type": "object",
                      "description": "The filters the server actually applied (normalized: retailer names resolved to slugs, defaults such as in_stock=true and the effective sort included, q echoed). Check it contains every constraint you meant to apply.",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_parameter: unknown parameter, unknown enum value, bad number/cursor. The message lists the allowed values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products/{retailer}/{id}": {
      "get": {
        "tags": [
          "products"
        ],
        "operationId": "getProduct",
        "summary": "One item in full, including description and price history.",
        "parameters": [
          {
            "name": "retailer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Item",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/retailers": {
      "get": {
        "tags": [
          "retailers"
        ],
        "operationId": "searchRetailers",
        "summary": "Find boutiques by point of view.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Free text over names and profiles."
          },
          {
            "name": "aesthetic",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Aesthetic labels. Comma-separated list. Allowed: american_workwear, french_workwear, japanese_americana, artisanal_craft, ivy_prep, british_country, heritage_sportswear, streetwear, skate, surf_coastal, outdoor_gorpcore, techwear, parisian_minimal, quiet_luxury, sartorial_tailoring, relaxed_tailoring, avant_garde, vintage_archive, western."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "store_model",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Store model. Comma-separated list. Allowed: multi_brand_curator, single_brand, single_brand_plus_collabs, vintage_resale, hybrid."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "price_tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Observed price tier: $ (<$100), $$ ($100-199), $$$ ($200-399), $$$$ ($400+). Comma-separated list. Allowed: $, $$, $$$, $$$$."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Evidence depth: A = full catalog, B = partial/own site, C = no online catalog. Comma-separated list. Allowed: A, B, C."
            },
            "explode": false,
            "style": "form"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "relevance",
                "name",
                "price_asc",
                "price_desc"
              ]
            },
            "description": "Default: relevance with q, else name."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            },
            "description": "Page size (max 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's next_cursor."
          },
          {
            "name": "neighborhood",
            "in": "query",
            "required": false,
            "explode": false,
            "style": "form",
            "schema": {
              "type": "string",
              "description": "Shops with a storefront in these neighborhoods. Comma-separated list. Allowed: bushwick, east_williamsburg, nolita, soho, williamsburg."
            }
          },
          {
            "name": "relevance",
            "in": "query",
            "required": false,
            "explode": false,
            "style": "form",
            "schema": {
              "type": "string",
              "description": "Shop relevance tiers to include. Comma-separated list. Allowed: core, plausible, low, noise, unknown. Default: core, plausible, unknown."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of boutiques",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Retailer"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_parameter: unknown parameter, unknown enum value, bad number/cursor. The message lists the allowed values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Free text q is a ranking signal (any word matches, more words rank higher), not a hard AND; filters narrow the set. An empty result includes a `hint` naming which constraint to drop. Cards include `neighborhoods`."
      }
    },
    "/v1/retailers/{slug}": {
      "get": {
        "tags": [
          "retailers"
        ],
        "operationId": "getRetailer",
        "summary": "A boutique's full profile: readable markdown dossier (profile_md), facets, socials, neighborhoods, live men's categories with median prices.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Retailer"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "post": {
        "tags": [
          "meta"
        ],
        "operationId": "createKey",
        "security": [],
        "summary": "Get a free API key (no auth, no body, no account).",
        "description": "Mints an anonymous free-tier key (30 requests/minute and 3,000/month). The key is returned once; only a hash is stored, so it cannot be recovered. New keys per day and the free tier's total daily traffic are capped (503 when reached, resets 00:00 UTC). Please use one key per app or agent.",
        "responses": {
          "201": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "key",
                    "key_id",
                    "rate_limit_per_min",
                    "monthly_quota"
                  ],
                  "properties": {
                    "key": {
                      "type": "string",
                      "pattern": "^lk_",
                      "description": "Send as Authorization: Bearer <key>. Shown once."
                    },
                    "key_id": {
                      "type": "integer",
                      "description": "Quote this to hello@looks.nyc to have the key revoked."
                    },
                    "shown_once": {
                      "type": "boolean"
                    },
                    "rate_limit_per_min": {
                      "type": "integer"
                    },
                    "monthly_quota": {
                      "type": "integer"
                    },
                    "use": {
                      "type": "object",
                      "description": "Ready-to-use REST example and MCP client settings."
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many key requests right now; retry shortly."
          },
          "503": {
            "description": "Today's limit on new keys is reached (Retry-After seconds until 00:00 UTC)."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key (lk_…), issued by invitation."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "retailer": {
            "type": "string"
          },
          "retailer_name": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Product page on the shop's site."
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Price as listed by the shop, in `currency` (null = unpriced)."
          },
          "compare_at_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Original price as listed, in `currency`."
          },
          "on_sale": {
            "type": "boolean"
          },
          "in_stock": {
            "type": "boolean"
          },
          "sizes_in_stock": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "gender": {
            "type": "string",
            "enum": [
              "men",
              "women",
              "unisex",
              "kids",
              "unknown"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "outerwear",
              "tailoring",
              "knitwear",
              "shirts",
              "tees_tops",
              "sweats_hoodies",
              "trousers",
              "denim",
              "shorts",
              "footwear",
              "bags",
              "headwear",
              "accessories",
              "underwear_socks",
              "swim",
              "non_apparel",
              "dresses_skirts",
              "home_goods",
              "jewelry_watches",
              "fragrance_beauty"
            ]
          },
          "subcategory": {
            "type": [
              "string",
              "null"
            ]
          },
          "materials": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "fit": {
            "type": [
              "string",
              "null"
            ]
          },
          "season_weight": {
            "type": "string",
            "enum": [
              "warm_weather",
              "all_season",
              "cold_weather"
            ]
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "origin": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The shop's own product image URL."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO currency of price/compare_at_price (the item's, else its shop's; null = unspecified, treated as USD). Prices are as listed in this currency."
          },
          "price_usd": {
            "type": [
              "number",
              "null"
            ],
            "description": "Equal to price when the item is USD-priced, else null. min_price/max_price and price sorts use USD-priced items only."
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Data-quality flags, e.g. price_zero, price_absurd, price_tiny, fee_item, gift_card, compare_at_gt_10x, no_image."
          },
          "variant_group": {
            "type": [
              "string",
              "null"
            ],
            "description": "Items sharing a value are variants (colors) of one design."
          },
          "gender_basis": {
            "type": [
              "string",
              "null"
            ],
            "description": "How `gender` was decided (e.g. title, product_type, tags, shop_default, none = no evidence). Null for items not yet re-labelled."
          },
          "category_basis": {
            "type": [
              "string",
              "null"
            ],
            "description": "How `category` was decided (rule or model). Null for items not yet re-labelled."
          },
          "dup_of": {
            "type": "string",
            "description": "Present on duplicates (only returned with include_flagged): id of the canonical item in the same shop."
          },
          "shop_dup_of": {
            "type": "string",
            "description": "Present on cross-shop duplicate rows (include_flagged only): slug of the shop that owns the product."
          }
        }
      },
      "Retailer": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "coverage_tier": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C"
            ]
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          },
          "aesthetics": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "Label -> weight (sums to 1)."
          },
          "scales": {
            "type": "object",
            "description": "refined_rugged, quiet_loud, serious_playful, classic_experimental; integers -2..+2."
          },
          "store_model": {
            "type": "string",
            "enum": [
              "multi_brand_curator",
              "single_brand",
              "single_brand_plus_collabs",
              "vintage_resale",
              "hybrid"
            ]
          },
          "price_tier": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "$",
              "$$",
              "$$$",
              "$$$$",
              null
            ]
          },
          "apparel_median_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "social": {
            "type": "object"
          },
          "profile_md": {
            "type": "string",
            "description": "Readable dossier (detail endpoint only)."
          },
          "profile_as_of": {
            "type": "string",
            "format": "date-time"
          },
          "neighborhoods": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Neighborhoods with a storefront: bushwick, east_williamsburg, nolita, soho, williamsburg."
          },
          "relevance": {
            "type": "object",
            "description": "How central the shop is to menswear outfit search (pipeline judgment): tier core|plausible|low|noise|unknown plus a kind label. Present once assessed.",
            "properties": {
              "tier": {
                "type": "string",
                "enum": [
                  "core",
                  "plausible",
                  "low",
                  "noise",
                  "unknown"
                ]
              },
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      }
    }
  },
  "x-mcp": {
    "url": "https://mcp.looks.nyc",
    "transport": "streamable-http",
    "auth": "bearer",
    "tools": [
      "search_products",
      "search_retailers",
      "get_retailer",
      "get_product",
      "get_vocab"
    ]
  }
}