{
  "openapi": "3.0.3",
  "info": {
    "title": "ListLoco API",
    "version": "1.0.0",
    "description": "ListLoco localizes e-commerce product listings into marketplace-compliant text and verifies them with a deterministic quality gate. Send a source-language listing; ListLoco applies harness-owned marketplace rules (currently the Amazon DE template), localizes it, and runs deterministic gates (title length, banned words, required attributes, preservation of numbers/model numbers/units, glossary, back-translation drift), returning one structured, machine-checkable result. Hard facts (model numbers, sizes, units) and your brand glossary are preserved. No card data or secrets are handled by this API.",
    "contact": { "name": "ListLoco", "email": "sakai.work259601@gmail.com" }
  },
  "servers": [
    { "url": "https://listloco.hayasaka.app", "description": "Production" }
  ],
  "security": [{ "apiKeyAuth": [] }],
  "paths": {
    "/localize": {
      "post": {
        "operationId": "localizeListing",
        "summary": "Localize a listing and run the quality gates",
        "description": "Accepts either (1) a bare listing object, or (2) a { listing, ...deps } wrapper. Marketplace rules are always the harness-owned Amazon DE template and are never read from untrusted input. Returns HTTP 200 even when a gate fails (pass=false); the verdict lives in the response body's `pass` field. Requires a valid API key.",
        "security": [{ "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "description": "The listing to localize: a bare listing, or a wrapper with the listing plus optional deps (glossary/dictionary/marketplace/sourceLang/targetLang/backTranslationThreshold).",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "$ref": "#/components/schemas/Listing" },
                  { "$ref": "#/components/schemas/LocalizeWrapperRequest" }
                ]
              },
              "examples": {
                "wrapper": {
                  "summary": "Wrapper form (with deps)",
                  "value": {
                    "marketplace": "amazon",
                    "sourceLang": "en",
                    "targetLang": "de",
                    "listing": {
                      "title": "Cordless drill AB-1200 with 8.5 kg battery and 2 m cable",
                      "description": "Cordless drill AB-1200 with a 8.5 kg battery and a 2 m cable.",
                      "brand": "Bosch",
                      "material": "Metall",
                      "size": "M",
                      "color": "blue",
                      "quantity": 1
                    },
                    "glossary": { "preserve": ["Bosch"] },
                    "dictionary": { "cordless": "kabellos", "drill": "Bohrmaschine", "battery": "Akku", "cable": "Kabel" }
                  }
                },
                "bareListing": {
                  "summary": "Bare listing form",
                  "value": { "title": "Cordless drill AB-1200", "brand": "Bosch", "quantity": 1 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pipeline ran (200 even when a gate fails; see the body's `pass`).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LocalizeResponse" } } }
          },
          "400": { "description": "Invalid request body (empty, not JSON, or not a JSON object).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing or invalid API key, or the request did not come through the RapidAPI gateway.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "405": { "description": "Method not allowed (only POST is supported on /localize).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "Request body exceeds the 1 MiB limit.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "Valid JSON but structurally invalid input (e.g. missing listing).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "description": "Unexpected internal error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Health check",
        "description": "Returns 200 and { status: \"ok\" } when the service is up. No authentication required.",
        "security": [],
        "responses": {
          "200": { "description": "Service is up.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "API key sent in the x-api-key header (Authorization: Bearer <key> is also accepted)."
      }
    },
    "schemas": {
      "Listing": {
        "type": "object",
        "description": "A source-language product listing: localizable text fields (title/description) plus structured attributes (brand/quantity, etc.). Additional properties are allowed.",
        "properties": {
          "title": { "type": "string", "description": "Product title (subject to the title-length gate)." },
          "description": { "type": "string", "description": "Product description." },
          "brand": { "type": "string", "description": "Brand name (may be a required attribute / glossary-preserved term)." },
          "material": { "type": "string" },
          "size": { "type": "string" },
          "color": { "type": "string" },
          "quantity": { "type": "number" }
        },
        "additionalProperties": true,
        "example": { "title": "Cordless drill AB-1200", "brand": "Bosch", "quantity": 1 }
      },
      "LocalizeWrapperRequest": {
        "type": "object",
        "description": "Wrapper request bundling the listing plus optional dependencies.",
        "required": ["listing"],
        "properties": {
          "listing": { "$ref": "#/components/schemas/Listing" },
          "marketplace": { "type": "string", "description": "Target marketplace id (informational; rules are always the harness-owned template).", "example": "amazon" },
          "sourceLang": { "type": "string", "description": "Source language (informational).", "example": "en" },
          "targetLang": { "type": "string", "description": "Target language (informational).", "example": "de" },
          "glossary": {
            "type": "object",
            "description": "Customer glossary: preserve (terms kept verbatim) and forceTranslations (forced translations).",
            "properties": {
              "preserve": { "type": "array", "items": { "type": "string" } },
              "forceTranslations": { "type": "object", "additionalProperties": { "type": "string" } }
            },
            "additionalProperties": true
          },
          "dictionary": { "type": "object", "description": "Source->target word map used by the default translator.", "additionalProperties": { "type": "string" } },
          "backTranslationThreshold": { "type": "number", "description": "Max back-translation divergence allowed to pass (0-1).", "minimum": 0, "maximum": 1 }
        },
        "additionalProperties": false
      },
      "LocalizeResponse": {
        "type": "object",
        "description": "Aggregated pipeline result.",
        "required": ["localized", "gates", "violations", "pass"],
        "properties": {
          "localized": { "allOf": [{ "$ref": "#/components/schemas/Listing" }], "description": "Localized listing (same key structure as the input)." },
          "gates": { "$ref": "#/components/schemas/Gates" },
          "violations": { "type": "array", "description": "Flattened set of structured violations from all gates; each item carries at least a gate name.", "items": { "$ref": "#/components/schemas/Violation" } },
          "pass": { "type": "boolean", "description": "True only when every enforced gate passes (ready to publish)." }
        }
      },
      "Gates": {
        "type": "object",
        "description": "Per-gate results keyed by gate name; each has a boolean pass.",
        "required": ["titleLength", "bannedWords", "requiredAttributes", "preservation", "glossary", "backTranslation"],
        "properties": {
          "titleLength": { "$ref": "#/components/schemas/GateResult" },
          "bannedWords": { "$ref": "#/components/schemas/GateResult" },
          "requiredAttributes": { "$ref": "#/components/schemas/GateResult" },
          "preservation": { "$ref": "#/components/schemas/GateResult" },
          "glossary": { "$ref": "#/components/schemas/GateResult" },
          "backTranslation": {
            "type": "object",
            "description": "Back-translation divergence gate (advisory unless a real back-translator transformed the text).",
            "required": ["pass"],
            "properties": {
              "pass": { "type": "boolean" },
              "score": { "type": "number", "description": "Measured divergence (0-1)." },
              "threshold": { "type": "number", "description": "Max divergence allowed to pass." },
              "enforced": { "type": "boolean", "description": "Whether this gate counts toward the overall pass." }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "GateResult": {
        "type": "object",
        "description": "Standard gate result with a violations array.",
        "required": ["pass", "violations"],
        "properties": {
          "pass": { "type": "boolean" },
          "violations": { "type": "array", "items": { "$ref": "#/components/schemas/Violation" } }
        },
        "additionalProperties": true
      },
      "Violation": {
        "type": "object",
        "description": "A single rule violation; type identifies the kind, other fields vary. Aggregated violations also carry the gate name.",
        "properties": {
          "gate": { "type": "string", "description": "Gate that raised the violation (added on aggregation)." },
          "type": { "type": "string", "description": "Violation type identifier." }
        },
        "additionalProperties": true
      },
      "HealthResponse": {
        "type": "object",
        "required": ["status"],
        "properties": { "status": { "type": "string", "example": "ok" } },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "description": "Error response.",
        "required": ["error"],
        "properties": { "error": { "type": "string", "description": "Human-readable error message." } },
        "additionalProperties": true
      }
    }
  }
}
