{
  "openapi": "3.1.0",
  "info": {
    "title": "Botzi Public API",
    "summary": "Catálogo público de los negocios que usan Botzi.",
    "description": "API pública de **Botzi**, la plataforma de WhatsApp con IA y mini-ERP para pymes de LATAM (https://botzi.co).\n\n## Qué se puede hacer con esta API\n\nLeer la **vitrina pública** de un negocio que usa Botzi: los datos de la tienda y\nsu catálogo activo, con precios ya rebajados si hay promociones vigentes. Es la\nmisma información que sirve la página `https://botzi.co/t/{slug}`, en JSON.\n\nSirve para: mostrar el catálogo de un negocio dentro de otra aplicación,\nconstruir un agente que responda \"¿qué vende X y a qué precio?\", o sincronizar\nprecios y stock hacia un tercero.\n\n## Autenticación\n\n**Ninguna.** Estos endpoints son públicos por diseño: el `slug` de una tienda\nlo comparte el propio dueño con sus clientes. No hay API key, no hay OAuth, no\nhay cabeceras que mandar.\n\nConsecuencia importante: la API es de **sólo lectura** y sólo alcanza lo que el\nnegocio decidió publicar. No hay forma de leer pedidos, conversaciones, clientes\nni caja desde acá — eso vive detrás de la sesión del panel y no tiene API\npública. Ver https://botzi.co/desarrolladores.\n\n## Límite de uso\n\n**30 peticiones por minuto** por combinación de `slug` e\nIP, en ventana deslizante de 60 segundos. Al pasarse, la respuesta es\n`429` y basta con esperar un minuto. No hay cuota diaria ni costo por llamada.\n\n## Estabilidad\n\nLos campos existentes no se quitan ni cambian de tipo sin subir la versión de\neste documento. Se pueden **agregar** campos nuevos en cualquier momento: un\ncliente correcto ignora los que no conoce.",
    "version": "1.0.0",
    "termsOfService": "https://botzi.co/terminos",
    "contact": {
      "name": "Botzi",
      "url": "https://botzi.co/desarrolladores",
      "email": "legal@botzi.co"
    },
    "x-whatsapp": "https://wa.me/573239261083"
  },
  "externalDocs": {
    "description": "Documentación para desarrolladores y agentes de IA",
    "url": "https://botzi.co/desarrolladores"
  },
  "servers": [
    {
      "url": "https://botzi.co/api",
      "description": "Producción"
    }
  ],
  "tags": [
    {
      "name": "Vitrina pública",
      "description": "Lectura del catálogo que un negocio publicó en su tienda de Botzi. Sin autenticación."
    }
  ],
  "paths": {
    "/public/store/{slug}": {
      "get": {
        "operationId": "getPublicStore",
        "summary": "Obtener la vitrina pública de un negocio",
        "description": "Devuelve los datos públicos del negocio (nombre, mensaje de bienvenida, WhatsApp si está conectado) y su catálogo activo. Los precios ya vienen con las promociones vigentes aplicadas: cuando hay descuento, el producto trae además `original_price` y `discount_label`. Las propiedades inmobiliarias reservadas, arrendadas o vendidas no se incluyen. No requiere autenticación.",
        "tags": [
          "Vitrina pública"
        ],
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Identificador público de la tienda, el mismo de la URL `botzi.co/t/{slug}`. Lo comparte el dueño del negocio.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "examples": [
                "mi-negocio-a1b2"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "La vitrina del negocio con su catálogo activo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicStore"
                }
              }
            }
          },
          "404": {
            "description": "No existe una tienda activa con ese `slug`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/public/store/{slug}/products/{productId}/variants": {
      "get": {
        "operationId": "getPublicStoreProductVariants",
        "summary": "Listar las variantes de un producto",
        "description": "Devuelve las variantes activas de un producto del catálogo público (tallas, colores, presentaciones). Se incluyen también las agotadas, con `stock_quantity` en 0, para que se puedan mostrar deshabilitadas en vez de desaparecer. El producto debe pertenecer al negocio del `slug`, así que este endpoint no sirve para enumerar productos ajenos. No requiere autenticación.",
        "tags": [
          "Vitrina pública"
        ],
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Identificador público de la tienda.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "examples": [
                "mi-negocio-a1b2"
              ]
            }
          },
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "UUID del producto, tal como viene en el campo `id` de `getPublicStore`.",
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-4000-8000-000000000000"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "El producto y sus variantes activas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductVariants"
                }
              }
            }
          },
          "404": {
            "description": "No existe la tienda, o el producto no pertenece a esa tienda o no está activo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "RateLimited": {
        "description": "Se superaron las 30 peticiones por minuto para ese `slug` desde esa IP. Reintentar después de 60 segundos.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "PublicStore": {
        "type": "object",
        "description": "Datos públicos de un negocio y su catálogo activo.",
        "required": [
          "agent",
          "products"
        ],
        "properties": {
          "agent": {
            "$ref": "#/components/schemas/StoreProfile"
          },
          "products": {
            "type": "array",
            "description": "Catálogo activo, en el orden en que el negocio lo ordenó. Puede venir vacío si todavía no cargó nada.",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          }
        }
      },
      "StoreProfile": {
        "type": "object",
        "description": "Ficha pública del negocio. No incluye datos del dueño de la cuenta (ni correo, ni identificadores internos).",
        "required": [
          "name",
          "slug",
          "business_name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre del asistente de Botzi del negocio."
          },
          "slug": {
            "type": "string",
            "description": "Identificador público de la tienda."
          },
          "business_name": {
            "type": "string",
            "description": "Nombre comercial que se muestra al cliente."
          },
          "whatsapp_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número de WhatsApp del negocio en formato internacional. Es `null` si el negocio todavía no conectó su WhatsApp.",
            "examples": [
              "573001234567"
            ]
          },
          "welcome_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mensaje con el que el asistente saluda a un cliente nuevo."
          },
          "business_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rubro del negocio, cuando está configurado. Determina qué campos del catálogo tienen sentido (por ejemplo `bedrooms` sólo aplica a inmobiliarias).",
            "examples": [
              "veterinaria"
            ]
          }
        }
      },
      "Product": {
        "type": "object",
        "description": "Un producto, servicio o inmueble publicado. Los campos específicos de un rubro vienen en `null` para los demás.",
        "required": [
          "id",
          "name",
          "resource_type",
          "has_variants"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador del producto."
          },
          "name": {
            "type": "string",
            "description": "Nombre visible."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descripción larga."
          },
          "short_description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descripción corta para listados."
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Precio vigente, con la promoción ya aplicada si hay alguna activa. Moneda: la del negocio (COP en Colombia). `null` cuando el negocio no publica precio.",
            "examples": [
              1850000
            ]
          },
          "original_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Precio antes del descuento. Sólo aparece cuando hay una promoción vigente aplicada sobre `price`."
          },
          "discount_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Etiqueta legible del descuento aplicado. Sólo aparece junto a `original_price`.",
            "examples": [
              "20% OFF"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "Categoría dentro del catálogo del negocio."
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Imagen principal."
          },
          "gallery_urls": {
            "type": [
              "array",
              "null"
            ],
            "description": "Galería adicional de imágenes.",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "stock": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unidades disponibles. `null` cuando el negocio no lleva inventario de ese ítem (por ejemplo, un servicio)."
          },
          "resource_type": {
            "type": "string",
            "description": "Qué clase de ítem es.",
            "examples": [
              "product"
            ]
          },
          "has_variants": {
            "type": "boolean",
            "description": "Si es `true`, pedir las opciones con `getPublicStoreProductVariants` antes de mostrar precio o stock."
          },
          "operation": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inmobiliarias: si el inmueble se vende o se arrienda.",
            "examples": [
              "venta"
            ]
          },
          "zone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inmobiliarias: zona o barrio."
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Inmobiliarias: número de habitaciones."
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Inmobiliarias: número de baños."
          },
          "area_m2": {
            "type": [
              "number",
              "null"
            ],
            "description": "Inmobiliarias: área construida en m²."
          },
          "rent_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Inmobiliarias: canon de arriendo, cuando la operación lo incluye."
          },
          "property_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inmobiliarias: estado del inmueble. Los reservados, arrendados y vendidos no se devuelven.",
            "examples": [
              "disponible"
            ]
          }
        }
      },
      "ProductVariants": {
        "type": "object",
        "description": "Un producto con sus variantes activas.",
        "required": [
          "product",
          "variants"
        ],
        "properties": {
          "product": {
            "$ref": "#/components/schemas/ProductSummary"
          },
          "variants": {
            "type": "array",
            "description": "Variantes activas, en el orden en que se crearon. Incluye las agotadas.",
            "items": {
              "$ref": "#/components/schemas/ProductVariant"
            }
          }
        }
      },
      "ProductSummary": {
        "type": "object",
        "description": "Datos mínimos del producto al que pertenecen las variantes.",
        "required": [
          "id",
          "has_variants"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador del producto."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nombre visible del producto."
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Precio base del producto."
          },
          "has_variants": {
            "type": "boolean",
            "description": "Si el producto se vende por variantes."
          }
        }
      },
      "ProductVariant": {
        "type": "object",
        "description": "Una combinación concreta de atributos con su propio precio y stock.",
        "required": [
          "id",
          "attributes",
          "stock_quantity"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador de la variante."
          },
          "attributes": {
            "type": "object",
            "description": "Atributos que definen la variante, como pares clave/valor.",
            "additionalProperties": {
              "type": "string"
            },
            "examples": [
              {
                "color": "Negro",
                "talla": "M"
              }
            ]
          },
          "stock_quantity": {
            "type": "integer",
            "description": "Unidades disponibles de esta variante. `0` significa agotada."
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Precio de esta variante. Si es `null`, aplica el precio base del producto."
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Imagen propia de esta variante, si tiene una distinta a la del producto."
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Formato de error de la API.",
        "required": [
          "detail"
        ],
        "properties": {
          "detail": {
            "type": "string",
            "description": "Explicación legible de qué salió mal.",
            "examples": [
              "Tienda no encontrada"
            ]
          }
        }
      }
    }
  },
  "security": []
}
