{
  "openapi": "3.1.0",
  "info": {
    "title": "Tiska API",
    "version": "1.0.0",
    "summary": "API REST pública de sólo lectura de los negocios de belleza publicados con Tiska.",
    "description": "Tiska es el software de agenda y turnos de los negocios de belleza de Argentina (peluquerías, barberías, centros de estética, spas, estudios de manicura, lashistas y depilación). Esta API devuelve exactamente los mismos datos que ya publica la página web de cada negocio: servicios con precio y duración, equipo, horarios de atención, reseñas y los tramos horarios ya ocupados.\n\nNo requiere clave, token ni registro: todo lo que expone es público. Es de sólo lectura. Para reservar un turno no hay endpoint REST a propósito: la reserva se completa en la página pública del negocio (`booking_url`), que es la que aplica horarios, señas y política de cancelación, o con la tool `create_appointment` del servidor MCP en https://usetiska.com/mcp.\n\nLímites: las consultas se limitan por IP (60 por minuto). Las respuestas se cachean 5 minutos en el CDN, así que repetir la misma consulta no consume cupo.",
    "termsOfService": "https://usetiska.com/terminos",
    "contact": { "name": "Tiska", "email": "hola@usetiska.com", "url": "https://usetiska.com/desarrolladores" }
  },
  "servers": [ { "url": "https://usetiska.com", "description": "Producción" } ],
  "externalDocs": { "description": "Documentación para desarrolladores y agentes de IA", "url": "https://usetiska.com/desarrolladores" },
  "tags": [ { "name": "businesses", "description": "Negocios con página pública en Tiska" } ],
  "paths": {
    "/api/v1/businesses": {
      "get": {
        "operationId": "listBusinesses",
        "tags": ["businesses"],
        "summary": "Listar los negocios publicados en Tiska",
        "description": "Devuelve todos los negocios de belleza argentinos con página pública en Tiska. Es el punto de entrada: da el `slug` que necesitan los demás endpoints. Usalo cuando haga falta encontrar un negocio por nombre aproximado antes de pedir sus servicios u horarios.",
        "parameters": [
          { "name": "query", "in": "query", "required": false, "description": "Filtro opcional: texto que debe contener el slug del negocio, en minúsculas.", "schema": { "type": "string", "maxLength": 80 }, "example": "barberia" },
          { "name": "limit", "in": "query", "required": false, "description": "Máximo de negocios a devolver.", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }, "example": 20 }
        ],
        "responses": {
          "200": { "description": "Listado de negocios.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BusinessList" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/businesses/{slug}": {
      "get": {
        "operationId": "getBusiness",
        "tags": ["businesses"],
        "summary": "Ver un negocio con sus servicios, equipo y horarios",
        "description": "Devuelve la ficha completa de un negocio: nombre, descripción, dirección, sucursales, servicios con id, precio en pesos argentinos y duración, equipo que toma reservas, horarios de atención por día, reseñas y la URL para reservar. El `id` de cada servicio es el que pide la tool `create_appointment` del servidor MCP.",
        "parameters": [
          { "$ref": "#/components/parameters/Slug" },
          { "$ref": "#/components/parameters/Local" }
        ],
        "responses": {
          "200": { "description": "Ficha del negocio.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Business" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/businesses/{slug}/availability": {
      "get": {
        "operationId": "getAvailability",
        "tags": ["businesses"],
        "summary": "Ver el horario de atención y los tramos ocupados de un día",
        "description": "Devuelve, para una fecha concreta, si el negocio abre, su horario de atención de ese día y los tramos horarios ya tomados. No calcula los huecos reservables: la disponibilidad final depende del servicio, del profesional y de las opciones elegidas, y la resuelve el formulario de reserva en `booking_url`. Usalo para descartar días cerrados y horarios ocupados antes de mandar a la persona a reservar.",
        "parameters": [
          { "$ref": "#/components/parameters/Slug" },
          { "name": "date", "in": "query", "required": true, "description": "Día a consultar, en formato YYYY-MM-DD y hora de Argentina.", "schema": { "type": "string", "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "example": "2026-09-15" },
          { "$ref": "#/components/parameters/Local" }
        ],
        "responses": {
          "200": { "description": "Horario del día y tramos ocupados.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Availability" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Slug": { "name": "slug", "in": "path", "required": true, "description": "Identificador del negocio: el último tramo de su URL pública, https://usetiska.com/{slug}.", "schema": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,79}$" }, "example": "barberia-mitre" },
      "Local": { "name": "local", "in": "query", "required": false, "description": "Sucursal concreta de una marca con varios locales. Si se omite, se usa la primera sucursal activa.", "schema": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,79}$" }, "example": "centro" }
    },
    "responses": {
      "BadRequest": { "description": "Algún parámetro no cumple el formato declarado.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No existe un negocio publicado con ese slug.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "Se superó el límite de consultas por minuto desde esa IP.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unavailable": { "description": "La base de Tiska no respondió. Es transitorio.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Sobre de error de toda la API. Nunca se devuelve HTML desde una ruta /api/.",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "description": "Detalle del error.",
            "required": ["code", "message", "resolution"],
            "properties": {
              "code": { "type": "string", "description": "Código estable para ramificar en el cliente.", "enum": ["not_found", "invalid_parameter", "business_not_found", "rate_limited", "upstream_unavailable", "method_not_allowed"] },
              "message": { "type": "string", "description": "Qué pasó, en castellano y legible para una persona." },
              "resolution": { "type": "string", "description": "Qué hacer para resolverlo." }
            }
          }
        }
      },
      "BusinessSummary": {
        "type": "object",
        "description": "Un negocio en el listado.",
        "required": ["slug", "url_publica", "booking_url"],
        "properties": {
          "slug": { "type": "string", "description": "Identificador del negocio.", "pattern": "^[a-z0-9][a-z0-9-]{0,79}$" },
          "url_publica": { "type": "string", "format": "uri", "description": "Página pública del negocio." },
          "booking_url": { "type": "string", "format": "uri", "description": "Formulario de reserva del negocio." }
        }
      },
      "BusinessList": {
        "type": "object",
        "description": "Resultado de listBusinesses.",
        "required": ["total", "negocios"],
        "properties": {
          "total": { "type": "integer", "description": "Cantidad de negocios devueltos." },
          "negocios": { "type": "array", "description": "Negocios publicados.", "items": { "$ref": "#/components/schemas/BusinessSummary" } }
        }
      },
      "Service": {
        "type": "object",
        "description": "Un servicio que ofrece el negocio.",
        "required": ["id", "nombre"],
        "properties": {
          "id": { "type": "string", "description": "Identificador del servicio. Es el service_id que pide create_appointment en el servidor MCP." },
          "nombre": { "type": "string", "description": "Nombre del servicio." },
          "precio_ars": { "type": ["number", "null"], "description": "Precio en pesos argentinos. null si el negocio no lo publica." },
          "duracion_min": { "type": ["integer", "null"], "description": "Duración base en minutos. null si no está cargada." }
        }
      },
      "StaffMember": {
        "type": "object",
        "description": "Profesional que toma reservas online.",
        "required": ["id", "nombre"],
        "properties": {
          "id": { "type": "string", "description": "Identificador del profesional, opcional en create_appointment." },
          "nombre": { "type": "string", "description": "Nombre del profesional." }
        }
      },
      "Review": {
        "type": "object",
        "description": "Reseña dejada por un cliente del negocio.",
        "properties": {
          "estrellas": { "type": ["number", "null"], "description": "Puntaje de 1 a 5.", "minimum": 1, "maximum": 5 },
          "comentario": { "type": ["string", "null"], "description": "Texto de la reseña." }
        }
      },
      "TimeRange": {
        "type": "object",
        "description": "Tramo horario de atención, hora local de Argentina.",
        "required": ["from", "to"],
        "properties": {
          "from": { "type": "string", "description": "Hora de inicio, HH:MM.", "pattern": "^\\d{1,2}:\\d{2}$" },
          "to": { "type": "string", "description": "Hora de fin, HH:MM.", "pattern": "^\\d{1,2}:\\d{2}$" }
        }
      },
      "BusyRange": {
        "type": "object",
        "description": "Tramo horario ya ocupado por un turno.",
        "required": ["desde", "hasta"],
        "properties": {
          "desde": { "type": "string", "description": "Hora de inicio, HH:MM.", "pattern": "^\\d{1,2}:\\d{2}$" },
          "hasta": { "type": "string", "description": "Hora de fin, HH:MM.", "pattern": "^\\d{1,2}:\\d{2}$" }
        }
      },
      "Branch": {
        "type": "object",
        "description": "Sucursal de una marca con varios locales.",
        "properties": {
          "nombre": { "type": ["string", "null"], "description": "Nombre de la sucursal." },
          "slug": { "type": ["string", "null"], "description": "Identificador de la sucursal, para el parámetro local." },
          "direccion": { "type": ["string", "null"], "description": "Dirección de la sucursal." }
        }
      },
      "OpeningHours": {
        "type": "object",
        "description": "Horario de atención por día de la semana. Un día sin tramos es un día cerrado.",
        "properties": {
          "lunes": { "type": "array", "description": "Tramos del lunes.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "martes": { "type": "array", "description": "Tramos del martes.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "miércoles": { "type": "array", "description": "Tramos del miércoles.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "jueves": { "type": "array", "description": "Tramos del jueves.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "viernes": { "type": "array", "description": "Tramos del viernes.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "sábado": { "type": "array", "description": "Tramos del sábado.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "domingo": { "type": "array", "description": "Tramos del domingo.", "items": { "$ref": "#/components/schemas/TimeRange" } }
        }
      },
      "Business": {
        "type": "object",
        "description": "Ficha pública completa de un negocio.",
        "required": ["slug", "servicios", "horarios_de_atencion", "url_publica", "booking_url"],
        "properties": {
          "nombre": { "type": ["string", "null"], "description": "Nombre comercial del negocio." },
          "slug": { "type": "string", "description": "Identificador del negocio." },
          "descripcion": { "type": ["string", "null"], "description": "Descripción que publica el negocio." },
          "reservas_online": { "type": "boolean", "description": "false si la cuenta está inhabilitada y no toma reservas." },
          "sucursal": { "description": "Sucursal pedida por el parámetro local, si se pidió una.", "oneOf": [ { "$ref": "#/components/schemas/Branch" }, { "type": "null" } ] },
          "sucursales": { "type": "array", "description": "Todas las sucursales activas. Sólo aparece si no se pidió una en particular.", "items": { "$ref": "#/components/schemas/Branch" } },
          "direccion": { "type": ["string", "null"], "description": "Dirección de la sucursal servida." },
          "servicios": { "type": "array", "description": "Servicios activos y visibles.", "items": { "$ref": "#/components/schemas/Service" } },
          "equipo": { "type": "array", "description": "Profesionales que aceptan reservas online.", "items": { "$ref": "#/components/schemas/StaffMember" } },
          "horarios_de_atencion": { "$ref": "#/components/schemas/OpeningHours" },
          "puntaje_promedio": { "type": ["number", "null"], "description": "Promedio de estrellas de las reseñas publicadas." },
          "resenas": { "type": "array", "description": "Últimas reseñas publicadas.", "items": { "$ref": "#/components/schemas/Review" } },
          "url_publica": { "type": "string", "format": "uri", "description": "Página pública del negocio." },
          "booking_url": { "type": "string", "format": "uri", "description": "Formulario de reserva del negocio." },
          "nota": { "type": "string", "description": "Aclaración en lenguaje natural sobre cómo reservar en este negocio." }
        }
      },
      "Availability": {
        "type": "object",
        "description": "Horario de atención y tramos ocupados de un día concreto.",
        "required": ["fecha", "abierto", "horario_de_atencion", "ocupados"],
        "properties": {
          "negocio": { "type": "string", "description": "Nombre del negocio." },
          "sucursal": { "type": ["string", "null"], "description": "Sucursal consultada." },
          "fecha": { "type": "string", "format": "date", "description": "Día consultado." },
          "dia": { "type": "string", "description": "Día de la semana en castellano." },
          "abierto": { "type": "boolean", "description": "false si el negocio no atiende ese día." },
          "horario_de_atencion": { "type": "array", "description": "Tramos en los que el negocio atiende ese día.", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "ocupados": { "type": "array", "description": "Tramos ya tomados por turnos existentes.", "items": { "$ref": "#/components/schemas/BusyRange" } },
          "nota": { "type": "string", "description": "Aclaración sobre qué significan estos datos." },
          "booking_url": { "type": "string", "format": "uri", "description": "Formulario de reserva del negocio." }
        }
      }
    }
  }
}
