{
  "openapi": "3.0.3",
  "info": {
    "title": "Vitruko API",
    "version": "1.1.0",
    "description": "Extrae fichas públicas de productos con galería, marca, precio, disponibilidad y calidad de los datos cuando la fuente los publica. No ejecuta JavaScript ni supera bloqueos de las tiendas."
  },
  "servers": [
    {
      "url": "https://api.vitruko.lat"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/v1/extract": {
      "post": {
        "summary": "Extraer ficha de producto",
        "operationId": "extractProduct",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ficha extraída. Cuenta una petición incluso desde caché.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "title",
                    "description",
                    "imageUrl",
                    "url",
                    "images",
                    "brand",
                    "price",
                    "availability",
                    "quality"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "imageUrl": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "images": {
                      "type": "array",
                      "maxItems": 20,
                      "uniqueItems": true,
                      "items": {
                        "type": "string",
                        "format": "uri"
                      },
                      "description": "URLs HTTPS, sin descargar imágenes. La primera coincide con imageUrl cuando existe."
                    },
                    "brand": {
                      "type": "string",
                      "maxLength": 120,
                      "description": "Marca publicada por la fuente; cadena vacía si falta."
                    },
                    "price": {
                      "type": "object",
                      "nullable": true,
                      "required": [
                        "amount",
                        "currency"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "amount": {
                          "type": "string",
                          "pattern": "^(0|[1-9][0-9]{0,17})(\\.[0-9]{1,6})?$",
                          "description": "Importe decimal en unidades principales, nunca centavos. Se conserva como string para evitar pérdida de precisión."
                        },
                        "currency": {
                          "type": "string",
                          "pattern": "^[A-Z]{3}$",
                          "description": "Código de moneda publicado por la fuente; no se infiere del dominio ni del símbolo."
                        }
                      },
                      "description": "null si falta importe o moneda, o no hay una oferta única identificable. No incluye envío ni conversión de moneda."
                    },
                    "availability": {
                      "type": "string",
                      "enum": [
                        "in_stock",
                        "out_of_stock",
                        "preorder",
                        "backorder",
                        "discontinued",
                        "unknown"
                      ],
                      "description": "Estado publicado al consultar la fuente. unknown indica que no se pudo confirmar; no garantiza inventario actual."
                    },
                    "quality": {
                      "type": "object",
                      "required": [
                        "productDetected",
                        "missingFields",
                        "fieldSources",
                        "warnings",
                        "extractedAt"
                      ],
                      "properties": {
                        "productDetected": {
                          "type": "boolean",
                          "description": "La fuente identificó datos como producto; no equivale a validación comercial."
                        },
                        "missingFields": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "title",
                              "description",
                              "images",
                              "brand",
                              "price",
                              "availability"
                            ]
                          }
                        },
                        "fieldSources": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string",
                            "enum": [
                              "json_ld",
                              "microdata",
                              "open_graph",
                              "html_meta",
                              "html_title",
                              "product_meta",
                              "shopify_json",
                              "vtex_json"
                            ]
                          }
                        },
                        "warnings": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "product_not_confirmed",
                              "multiple_products",
                              "multiple_offers",
                              "price_range",
                              "variant_not_confirmed"
                            ]
                          }
                        },
                        "extractedAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "Momento de extracción UTC; una respuesta desde caché conserva el valor original."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "URL o JSON inválido"
          },
          "401": {
            "description": "Clave ausente, inválida o revocada"
          },
          "403": {
            "description": "Sin permiso extract o cuenta suspendida"
          },
          "405": {
            "description": "Usa POST"
          },
          "415": {
            "description": "Envía application/json"
          },
          "422": {
            "description": "Tienda bloqueada o ficha no accesible"
          },
          "429": {
            "description": "Cuota, velocidad o límite temporal de la tienda. code=source_rate_limited identifica una restricción de la fuente, distinta de la cuota de tu cuenta. Respeta Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que debes esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "503": {
            "description": "Servicio ocupado o renovación pendiente"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "También se admite Authorization: Bearer. Nunca envíes la clave en la URL."
      }
    }
  }
}
