{
  "openapi": "3.1.0",
  "info": {
    "title": "Webship quote intake",
    "version": "1.0.0",
    "description": "Public fulfilment quote request intake. No prices are returned; Webship follows up with a tailored proposal."
  },
  "paths": {
    "/api/submit": {
      "post": {
        "operationId": "submitQuoteRequest",
        "summary": "Submit a quote request",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/IntakeBody" },
              "example": {
                "language": "nl",
                "source": "ai_agent",
                "companyName": "Example BV",
                "contactName": "Alex Example",
                "contactEmail": "alex@example.com",
                "companyNumber": "BE0123456789",
                "productTypeDescription": "Cosmetica",
                "ordersPerMonth": 150,
                "totalSkus": 40,
                "avgOrderValueEur": 75,
                "avgSkusPerOrder": 2,
                "avgItemsPerOrder": 3,
                "avgShipmentWeightKg": 1.5,
                "avgColliPerOrder": 1,
                "storageCubicMeters": 10,
                "smallVsLargeRatio": 60,
                "destinationCountries": ["BE"],
                "needsShippingBox": true,
                "isBio": false,
                "isFood": false,
                "hasPalletOrders": false,
                "company_fax": ""
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["ok", "requestId"],
                  "properties": {
                    "ok": { "type": "boolean" },
                    "requestId": { "type": "string", "format": "uuid" },
                    "language": { "type": "string", "enum": ["nl", "en", "fr"] }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ValidationError" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (about 8 requests per minute per IP)"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "IntakeBody": {
        "type": "object",
        "required": [
          "companyName",
          "contactName",
          "contactEmail",
          "companyNumber",
          "productTypeDescription",
          "ordersPerMonth",
          "totalSkus",
          "avgOrderValueEur",
          "avgSkusPerOrder",
          "avgItemsPerOrder",
          "avgShipmentWeightKg",
          "storageCubicMeters",
          "smallVsLargeRatio"
        ],
        "properties": {
          "language": { "type": "string", "enum": ["nl", "en", "fr"] },
          "source": {
            "type": "string",
            "enum": ["website_calculator", "ai_agent"],
            "description": "Use ai_agent when an automated client submits on behalf of a user."
          },
          "companyName": { "type": "string", "minLength": 1 },
          "contactName": { "type": "string", "minLength": 1 },
          "contactEmail": { "type": "string", "format": "email" },
          "preferredContactChannel": {
            "type": "string",
            "enum": ["email", "phone"],
            "default": "email",
            "description": "Preferred follow-up channel. When phone, phone is required."
          },
          "phone": {
            "type": ["string", "null"],
            "description": "Required when preferredContactChannel is phone."
          },
          "companyNumber": { "type": "string", "minLength": 1, "description": "VAT or company registration number (BTW/KVK)." },
          "productTypeDescription": { "type": "string", "minLength": 1 },
          "maxProductSizeNotes": { "type": ["string", "null"] },
          "remarks": {
            "type": ["string", "null"],
            "maxLength": 2000,
            "description": "Optional free-text note from the prospect (stored on quoteRequests.customerRemarks)."
          },
          "isBio": { "type": "boolean" },
          "isFood": { "type": "boolean" },
          "ordersPerMonth": { "type": "integer", "minimum": 1 },
          "avgColliPerOrder": { "type": "integer", "minimum": 1, "maximum": 10 },
          "totalSkus": { "type": "integer", "minimum": 1 },
          "avgOrderValueEur": { "type": "number", "minimum": 0 },
          "avgSkusPerOrder": { "type": "number", "minimum": 0.1 },
          "avgItemsPerOrder": { "type": "number", "minimum": 0.1 },
          "avgShipmentWeightKg": { "type": "number", "minimum": 0 },
          "storageCubicMeters": { "type": "number", "minimum": 0 },
          "smallVsLargeRatio": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of storage volume that is small products (0 = all large, 100 = all small)."
          },
          "destinationCountry": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "Legacy single country; prefer destinationCountries."
          },
          "destinationCountries": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$",
              "description": "ISO 3166-1 alpha-2, e.g. BE, NL, US"
            }
          },
          "destinationShares": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "object",
              "required": ["code", "pct"],
              "properties": {
                "code": { "type": "string", "pattern": "^[A-Za-z]{2}$" },
                "pct": { "type": "number", "minimum": 0, "maximum": 100 }
              }
            },
            "description": "Required when more than one destinationCountries entry."
          },
          "needsShippingBox": { "type": "boolean" },
          "hasPalletOrders": { "type": "boolean" },
          "palletOrderPct": { "type": "number", "minimum": 0, "maximum": 100 },
          "salesChannelUrl": { "type": ["string", "null"] },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Shopify",
                "Woocommerce",
                "Bol.com",
                "Amazon",
                "Magento",
                "Lightspeed",
                "Andere"
              ]
            },
            "description": "Use when salesChannelUrl is set."
          },
          "company_fax": {
            "type": "string",
            "description": "Honeypot — must be empty or omitted."
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "required": ["error", "issues"],
        "properties": {
          "error": { "type": "string", "const": "Validation failed" },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["path", "message"],
              "properties": {
                "path": { "type": "string" },
                "message": { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}
