{
  "openapi": "3.1.0",
  "info": {
    "title": "API pública de Antonysia Helados",
    "version": "1.1.0",
    "summary": "Catálogo y servidor MCP de la heladería artesanal Antonysia Helados (Loma Hermosa, Tres de Febrero, Buenos Aires).",
    "description": "Endpoints publicos y de solo lectura, pensados para agentes: la carta completa en JSON y un servidor MCP con las mismas reglas de precio, envio y horario que cobra el sitio. Sin autenticacion: no hay API keys ni registro, porque no hay nada que proteger ni nada que escribir, y por eso tampoco hay un sandbox aparte. No hay endpoint publico que cree pedidos: un pedido se arma en https://www.antonysiahelados.com.ar/ y se confirma por WhatsApp.\n\nVersion en la ruta: /api/v1/... es la forma canonica y las rutas sin version son alias permanentes de la v1. Un cambio incompatible estrena /api/v2/...; el retiro de una ruta se avisa con las cabeceras Deprecation (RFC 9745) y Sunset (RFC 8594) con un minimo de 180 dias. Cuota: 60 pedidos por minuto y por IP, con cabeceras RateLimit en cada respuesta. Los errores salen en application/problem+json (RFC 9457) con un `code` estable; el detalle esta en https://www.antonysiahelados.com.ar/developers.",
    "contact": {
      "name": "Antonysia Helados",
      "url": "https://www.antonysiahelados.com.ar/developers"
    },
    "license": {
      "name": "Uso libre de los datos publicos, con atribucion",
      "identifier": "CC-BY-4.0"
    }
  },
  "servers": [
    {
      "url": "https://www.antonysiahelados.com.ar",
      "description": "Producción"
    }
  ],
  "paths": {
    "/api/v1/catalogo": {
      "get": {
        "operationId": "obtenerCatalogo",
        "summary": "Catálogo completo de Antonysia Helados",
        "description": "La carta entera en un solo GET: sabores con descripción y marca de vegano, potes con su precio y su máximo de sabores, combos, adicionales, costos de envío, zona de reparto y horario del local con la hora de Buenos Aires. Sin autenticación. Esta es la ruta canónica.",
        "tags": [
          "catalogo"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "El catálogo vigente. Se cachea cinco minutos en el CDN; `horario.abiertoAhora` caduca antes que eso.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Catalogo"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MetodoNoPermitido"
          },
          "429": {
            "$ref": "#/components/responses/CuotaExcedida"
          }
        }
      }
    },
    "/api/catalogo": {
      "get": {
        "operationId": "obtenerCatalogoSinVersion",
        "summary": "Catálogo completo de Antonysia Helados",
        "description": "La carta entera en un solo GET: sabores con descripción y marca de vegano, potes con su precio y su máximo de sabores, combos, adicionales, costos de envío, zona de reparto y horario del local con la hora de Buenos Aires. Sin autenticación. Alias permanente de GET /api/v1/catalogo: misma respuesta, sin retiro previsto.",
        "tags": [
          "catalogo"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "El catálogo vigente. Se cachea cinco minutos en el CDN; `horario.abiertoAhora` caduca antes que eso.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Catalogo"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MetodoNoPermitido"
          },
          "429": {
            "$ref": "#/components/responses/CuotaExcedida"
          }
        }
      }
    },
    "/.well-known/mcp": {
      "get": {
        "operationId": "obtenerManifiestoMcp",
        "summary": "Manifiesto del servidor MCP (descubrimiento)",
        "description": "Descubrimiento del servidor MCP: nombre, versión del protocolo, transporte, autenticación (ninguna) y lista de herramientas con su descripción.",
        "tags": [
          "mcp"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "El manifiesto.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManifiestoMcp"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MetodoNoPermitido"
          },
          "429": {
            "$ref": "#/components/responses/CuotaExcedida"
          }
        }
      }
    },
    "/api/v1/mcp": {
      "get": {
        "operationId": "obtenerManifiestoMcpDirecto",
        "summary": "Manifiesto del servidor MCP (misma respuesta que /.well-known/mcp)",
        "description": "Descubrimiento del servidor MCP: nombre, versión del protocolo, transporte, autenticación (ninguna) y lista de herramientas con su descripción.",
        "tags": [
          "mcp"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "El manifiesto.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManifiestoMcp"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MetodoNoPermitido"
          },
          "429": {
            "$ref": "#/components/responses/CuotaExcedida"
          }
        }
      },
      "post": {
        "operationId": "llamarMcp",
        "summary": "Endpoint MCP (JSON-RPC 2.0 sobre Streamable HTTP)",
        "description": "Acepta initialize, ping, tools/list, tools/call, resources/list y prompts/list. El servidor es sin estado: no usa Mcp-Session-Id y contesta en application/json en vez de abrir un stream SSE, que es lo que la especificación permite. Una notificación (mensaje sin id) se contesta con 202 y sin cuerpo. No se admiten lotes: el protocolo los quitó en 2025-06-18. Sin autenticación. Esta es la ruta canónica.",
        "tags": [
          "mcp"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PeticionJsonRpc"
              },
              "examples": {
                "initialize": {
                  "summary": "Handshake",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "mi-agente",
                        "version": "1.0.0"
                      }
                    }
                  }
                },
                "listarSabores": {
                  "summary": "Llamar una herramienta",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "listar_sabores",
                      "arguments": {
                        "solo_veganos": true
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "La respuesta JSON-RPC. Un error de protocolo MCP viaja acá, con status 200 y el objeto `error`, porque es lo que espera un cliente MCP.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaJsonRpc"
                }
              }
            }
          },
          "202": {
            "description": "Notificación aceptada, sin cuerpo.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            }
          },
          "400": {
            "description": "El cuerpo no es un JSON-RPC 2.0 válido, o llegó un lote. Se contesta como error JSON-RPC (-32700 o -32600), no en problem+json, para que un cliente MCP lo entienda.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaJsonRpc"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MetodoNoPermitido"
          },
          "429": {
            "$ref": "#/components/responses/CuotaExcedida"
          }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "operationId": "llamarMcpSinVersion",
        "summary": "Endpoint MCP (JSON-RPC 2.0 sobre Streamable HTTP)",
        "description": "Acepta initialize, ping, tools/list, tools/call, resources/list y prompts/list. El servidor es sin estado: no usa Mcp-Session-Id y contesta en application/json en vez de abrir un stream SSE, que es lo que la especificación permite. Una notificación (mensaje sin id) se contesta con 202 y sin cuerpo. No se admiten lotes: el protocolo los quitó en 2025-06-18. Sin autenticación. Alias permanente de POST /api/v1/mcp: misma respuesta, sin retiro previsto.",
        "tags": [
          "mcp"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PeticionJsonRpc"
              },
              "examples": {
                "initialize": {
                  "summary": "Handshake",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "mi-agente",
                        "version": "1.0.0"
                      }
                    }
                  }
                },
                "listarSabores": {
                  "summary": "Llamar una herramienta",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "listar_sabores",
                      "arguments": {
                        "solo_veganos": true
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "La respuesta JSON-RPC. Un error de protocolo MCP viaja acá, con status 200 y el objeto `error`, porque es lo que espera un cliente MCP.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaJsonRpc"
                }
              }
            }
          },
          "202": {
            "description": "Notificación aceptada, sin cuerpo.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              }
            }
          },
          "400": {
            "description": "El cuerpo no es un JSON-RPC 2.0 válido, o llegó un lote. Se contesta como error JSON-RPC (-32700 o -32600), no en problem+json, para que un cliente MCP lo entienda.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaJsonRpc"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MetodoNoPermitido"
          },
          "429": {
            "$ref": "#/components/responses/CuotaExcedida"
          }
        }
      }
    },
    "/api/{rutaDesconocida}": {
      "parameters": [
        {
          "name": "rutaDesconocida",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Cualquier ruta no publicada bajo /api/."
        }
      ],
      "get": {
        "operationId": "obtenerRutaDesconocidaApi",
        "summary": "Cualquier otra ruta bajo /api/ (404 tipado)",
        "description": "Toda ruta que no exista bajo /api/ contesta 404 en application/problem+json, nunca en HTML ni en texto plano, y el cuerpo lista los endpoints que sí existen. Está documentado porque es la respuesta con la que un agente se topa cuando erra la ruta.",
        "tags": [
          "catalogo"
        ],
        "security": [],
        "responses": {
          "404": {
            "$ref": "#/components/responses/RutaNoEncontrada"
          }
        }
      }
    },
    "/": {
      "get": {
        "operationId": "obtenerPaginaInicio",
        "summary": "Página de inicio, en HTML o en Markdown",
        "description": "Con Accept: text/markdown devuelve la misma página en Markdown (Content-Type: text/markdown y Vary: Accept). Con cualquier otro Accept devuelve el HTML. Lo mismo vale para /about, /contact, /privacy y /developers, y cada una tiene además su .md en una URL propia.",
        "tags": [
          "paginas"
        ],
        "security": [],
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "text/markdown",
                "text/html"
              ]
            },
            "description": "text/markdown para la versión en texto; cualquier otro valor devuelve el HTML."
          }
        ],
        "responses": {
          "200": {
            "description": "La página.",
            "headers": {
              "Vary": {
                "description": "Siempre `Accept`: esta URL negocia el tipo.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "Accept"
                  ]
                }
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/Pagina"
                }
              },
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/Pagina"
                }
              }
            }
          },
          "404": {
            "description": "La ruta no existe. Con Accept: text/markdown el cuerpo del error también es Markdown, con los enlaces para seguir.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/Pagina"
                }
              },
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/Pagina"
                }
              }
            }
          }
        }
      }
    },
    "/developers": {
      "get": {
        "operationId": "obtenerPortalDesarrolladores",
        "summary": "Portal de desarrolladores de Antonysia Helados",
        "description": "La documentación de esta API en prosa: endpoints, autenticación, cuotas, catálogo de errores con sus anclas, versionado y ejemplos con curl. Los URI de `type` de cada problema RFC 9457 resuelven a un ancla de esta página. En Markdown: /developers.md.",
        "tags": [
          "paginas"
        ],
        "security": [],
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "text/markdown",
                "text/html"
              ]
            },
            "description": "text/markdown para la versión en texto; cualquier otro valor devuelve el HTML."
          }
        ],
        "responses": {
          "200": {
            "description": "La documentación.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/Pagina"
                }
              },
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/Pagina"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Catalogo": {
        "type": "object",
        "properties": {
          "negocio": {
            "type": "object",
            "description": "Nombre, contacto y dirección del local.",
            "properties": {
              "nombre": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              },
              "telefono": {
                "type": "string"
              },
              "whatsapp": {
                "type": "string",
                "format": "uri"
              },
              "instagram": {
                "type": "string",
                "format": "uri"
              },
              "direccion": {
                "type": "object"
              }
            }
          },
          "moneda": {
            "type": "string",
            "examples": [
              "ARS"
            ]
          },
          "potes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "examples": [
                    "k100"
                  ]
                },
                "nombre": {
                  "type": "string"
                },
                "precio": {
                  "type": "integer"
                },
                "saboresMax": {
                  "type": "integer"
                }
              }
            }
          },
          "promos": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "adicionales": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "sabores": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "nombre": {
                  "type": "string"
                },
                "categoria": {
                  "type": "string"
                },
                "descripcion": {
                  "type": "string"
                },
                "vegano": {
                  "type": "boolean"
                }
              }
            }
          },
          "entrega": {
            "type": "object",
            "properties": {
              "envio": {
                "type": "integer"
              },
              "envioGratisDesde": {
                "type": "integer"
              },
              "pedidoMinimoEnvio": {
                "type": "integer"
              },
              "zonas": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "radioKm": {
                "type": "number"
              }
            }
          },
          "pagos": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "horario": {
            "type": "object",
            "properties": {
              "zonaHoraria": {
                "type": "string",
                "examples": [
                  "America/Argentina/Buenos_Aires"
                ]
              },
              "texto": {
                "type": "string"
              },
              "abiertoAhora": {
                "type": "boolean"
              },
              "semana": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "proximaApertura": {
                "type": [
                  "object",
                  "null"
                ]
              }
            }
          },
          "comoPedir": {
            "type": "string"
          },
          "consultado": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ManifiestoMcp": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "protocolVersion": {
            "type": "string"
          },
          "transport": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "examples": [
                  "streamable-http"
                ]
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "capabilities": {
            "type": "object"
          },
          "instructions": {
            "type": "string"
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "PeticionJsonRpc": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string"
            ]
          },
          "method": {
            "type": "string",
            "examples": [
              "initialize",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object"
          }
        }
      },
      "RespuestaJsonRpc": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "result": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ResultadoHerramienta"
              },
              {
                "type": "object"
              }
            ],
            "description": "El resultado del metodo. Para tools/call es un ResultadoHerramienta."
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "description": "El error JSON-RPC, con los codigos de la especificacion (-32700 parseo, -32600 pedido invalido, -32601 metodo inexistente, -32602 parametros invalidos).",
            "properties": {
              "code": {
                "type": "integer",
                "examples": [
                  -32601,
                  -32602
                ]
              },
              "message": {
                "type": "string"
              },
              "data": {
                "type": "object",
                "description": "Contexto extra, cuando lo hay."
              }
            }
          }
        },
        "description": "La respuesta JSON-RPC 2.0. Trae `result` o `error`, nunca los dos. Un error de protocolo MCP viaja aca con status HTTP 200, que es lo que espera un cliente MCP; los errores que no son de protocolo (cuota, metodo) salen en application/problem+json."
      },
      "Problema": {
        "type": "object",
        "title": "Problema (RFC 9457)",
        "description": "El error de la API, en application/problem+json segun RFC 9457. Ademas de los campos de la RFC lleva dos miembros de extension: `code`, que es un identificador estable pensado para ramificar en codigo, y `pista`, que dice como salir del error. El catalogo de codigos esta en https://www.antonysiahelados.com.ar/developers#errores.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI que identifica el tipo de problema. Resuelve a un ancla de /developers con la explicacion.",
            "examples": [
              "https://www.antonysiahelados.com.ar/developers#error-ruta-no-encontrada"
            ]
          },
          "title": {
            "type": "string",
            "description": "Resumen corto y estable del tipo de problema.",
            "examples": [
              "La ruta no existe"
            ]
          },
          "status": {
            "type": "integer",
            "description": "El mismo codigo de estado HTTP de la respuesta.",
            "enum": [
              400,
              404,
              405,
              429
            ]
          },
          "detail": {
            "type": "string",
            "description": "Que paso en esta llamada puntual."
          },
          "code": {
            "type": "string",
            "description": "Codigo estable del error. Es el campo para ramificar en codigo, no `title` ni `detail`.",
            "enum": [
              "ruta_no_encontrada",
              "metodo_no_permitido",
              "cuerpo_invalido",
              "cuota_excedida"
            ]
          },
          "pista": {
            "type": "string",
            "description": "Como resolverlo: que corregir o cuanto esperar."
          },
          "documentacion": {
            "type": "string",
            "format": "uri",
            "description": "La documentacion de la API.",
            "examples": [
              "https://www.antonysiahelados.com.ar/developers"
            ]
          },
          "instance": {
            "type": "string",
            "description": "La ruta que se pidio, cuando el error depende de ella."
          },
          "permitidos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Solo en 405: los metodos que acepta la ruta."
          },
          "retryAfter": {
            "type": "integer",
            "description": "Solo en 429: segundos a esperar, el mismo valor de la cabecera Retry-After."
          },
          "endpoints": {
            "type": "array",
            "description": "Solo en 404: los endpoints publicos que si existen.",
            "items": {
              "type": "object",
              "properties": {
                "metodo": {
                  "type": "string"
                },
                "ruta": {
                  "type": "string"
                },
                "que": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Pagina": {
        "type": "string",
        "title": "Pagina del sitio",
        "description": "El cuerpo de la pagina: HTML cuando el Accept pide HTML, Markdown cuando pide text/markdown."
      },
      "ResultadoHerramienta": {
        "type": "object",
        "title": "Resultado de tools/call",
        "description": "El resultado de una herramienta MCP. `content` trae dos bloques de texto: el primero es el resumen en prosa para leerle a una persona; el segundo es el mismo dato como JSON serializado, para hacer cuentas.",
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "array",
            "minItems": 2,
            "items": {
              "type": "object",
              "required": [
                "type",
                "text"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "const": "text"
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "isError": {
            "type": "boolean",
            "description": "true cuando la herramienta rechazo los argumentos (por ejemplo un sabor que no esta en la carta)."
          }
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "La cuota de la ventana: 60 pedidos por minuto y por IP.",
        "schema": {
          "type": "integer",
          "examples": [
            60
          ]
        }
      },
      "RateLimit-Remaining": {
        "description": "Cuantos pedidos quedan en la ventana en curso. Orientativo: el contador es por instancia y esta respuesta puede venir del CDN.",
        "schema": {
          "type": "integer",
          "examples": [
            59
          ]
        }
      },
      "RateLimit-Reset": {
        "description": "Segundos hasta que la ventana se renueva.",
        "schema": {
          "type": "integer",
          "examples": [
            60
          ]
        }
      },
      "RateLimit-Policy": {
        "description": "La politica declarada, como campo estructurado del draft de la IETF.",
        "schema": {
          "type": "string",
          "examples": [
            "\"catalogo\";q=60;w=60"
          ]
        }
      },
      "RateLimit": {
        "description": "El estado de la cuota, como campo estructurado del draft de la IETF.",
        "schema": {
          "type": "string",
          "examples": [
            "\"catalogo\";r=59;t=60"
          ]
        }
      },
      "Retry-After": {
        "description": "Solo en 429: segundos a esperar antes de reintentar. Este si es exacto.",
        "schema": {
          "type": "integer",
          "examples": [
            37
          ]
        }
      },
      "API-Version": {
        "description": "La version de la API que contesto. Hoy siempre 1.",
        "schema": {
          "type": "string",
          "examples": [
            "1"
          ]
        }
      }
    },
    "responses": {
      "RutaNoEncontrada": {
        "description": "La ruta no existe bajo /api/. El cuerpo lista los endpoints que si existen: corregir la ruta, no reintentar.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimit-Limit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimit-Remaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimit-Reset"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "API-Version": {
            "$ref": "#/components/headers/API-Version"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problema"
            }
          }
        }
      },
      "MetodoNoPermitido": {
        "description": "La ruta existe pero no acepta ese metodo.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimit-Limit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimit-Remaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimit-Reset"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "API-Version": {
            "$ref": "#/components/headers/API-Version"
          },
          "Allow": {
            "description": "Los metodos que acepta la ruta.",
            "schema": {
              "type": "string",
              "examples": [
                "GET, HEAD, OPTIONS"
              ]
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problema"
            }
          }
        }
      },
      "CuotaExcedida": {
        "description": "Se supero la cuota de la ruta. Esperar los segundos de Retry-After y reintentar.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimit-Limit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimit-Remaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimit-Reset"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "API-Version": {
            "$ref": "#/components/headers/API-Version"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problema"
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Portal de desarrolladores: endpoints, errores, cuotas y versionado",
    "url": "https://www.antonysiahelados.com.ar/developers"
  },
  "tags": [
    {
      "name": "catalogo",
      "description": "La carta entera como datos."
    },
    {
      "name": "mcp",
      "description": "El servidor MCP y su manifiesto."
    },
    {
      "name": "paginas",
      "description": "Las paginas del sitio, en HTML o en Markdown."
    }
  ]
}
