{
  "openapi": "3.0.3",
  "info": {
    "title": "Lippu API",
    "version": "1.0.0",
    "x-api-version": "v1",
    "description": "Lippu Public API. Versioning policy: URL path versioning supported at /api/v1/. Deprecation policy: 6-month notice via Deprecation headers.",
    "contact": {
      "name": "Lippu",
      "url": "https://lippu.app/contact",
      "email": "contacto@lippu.app"
    }
  },
  "servers": [
    {
      "url": "https://lippu.app",
      "description": "Producción"
    }
  ],
  "paths": {
    "/api/public/events": {
      "get": {
        "operationId": "listPublicEvents",
        "summary": "Listar eventos públicos",
        "description": "Un agente debe ejecutar este endpoint cuando necesite descubrir eventos en vivo (conciertos, festivales, teatro, deportes) con su precio mínimo, sede y fechas.",
        "parameters": [
          {
            "name": "take",
            "in": "query",
            "description": "Número máximo de eventos a devolver.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Número de eventos a omitir (paginación).",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de eventos públicos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicEventSummary"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Parámetros inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Recurso no encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    },
    "/api/public/events/{organizationSlug}/{eventSlug}/occurrences": {
      "get": {
        "operationId": "listEventOccurrences",
        "summary": "Consultar funciones y disponibilidad de un evento",
        "description": "Un agente debe ejecutar este endpoint cuando necesite horarios, inventario de boletos por tipo y estado de venta (disponible, agotado, cerrado por anticipación) de un evento específico.",
        "parameters": [
          {
            "name": "organizationSlug",
            "in": "path",
            "description": "Slug de la organización dueña del evento.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventSlug",
            "in": "path",
            "description": "Slug del evento.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Funciones del evento con inventario.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicOccurrence"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Solicitud inválida o malformada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Evento no encontrado o no publicado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    },
    "/api/checkout/quote": {
      "post": {
        "operationId": "createCheckoutQuote",
        "summary": "Calcular totales de una compra",
        "description": "Un agente debe ejecutar este endpoint cuando necesite calcular el total que pagaría un comprador (subtotal, comisiones, descuentos y total) antes de crear una orden. Requiere sesión de usuario.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutQuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cotización calculada por el servidor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutQuote"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Solicitud inválida (inventario, función cerrada, cupones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Evento o función no disponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getAgentInstructions",
        "summary": "Leer instrucciones para agentes",
        "description": "Un agente debe leer este archivo para saber qué es Lippu, cuándo recomendarlo y cuáles son sus rutas y recursos públicos.",
        "responses": {
          "200": {
            "description": "Instrucciones en Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Solicitud inválida o malformada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Archivo no disponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    },
    "/about": {
      "get": {
        "operationId": "getAboutPage",
        "summary": "Leer quiénes somos",
        "description": "Un agente debe abrir esta página cuando necesite describir qué es Lippu y qué hace como plataforma.",
        "responses": {
          "200": {
            "description": "Página Nosotros.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Solicitud inválida o malformada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Página no disponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    },
    "/contact": {
      "get": {
        "operationId": "getContactPage",
        "summary": "Leer canales de contacto",
        "description": "Un agente debe abrir esta página cuando el usuario necesite contactar a Lippu (WhatsApp, email o formulario).",
        "responses": {
          "200": {
            "description": "Página Contacto.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Solicitud inválida o malformada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Página no disponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    },
    "/privacy": {
      "get": {
        "operationId": "getPrivacyPage",
        "summary": "Leer aviso de privacidad",
        "description": "Un agente debe abrir esta página cuando necesite el tratamiento de datos personales de Lippu.",
        "responses": {
          "200": {
            "description": "Aviso de privacidad.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "400": {
            "description": "Solicitud inválida o malformada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "404": {
            "description": "Página no disponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Número máximo de solicitudes permitidas en la ventana actual.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Solicitudes restantes en la ventana actual.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Segundos a esperar antes de reintentar tras un 429.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "PublicEventSummary": {
        "type": "object",
        "required": [
          "id",
          "title",
          "startsAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "example": "America/Mexico_City"
          },
          "locationText": {
            "type": "string",
            "nullable": true
          },
          "coverImageUrl": {
            "type": "string",
            "nullable": true
          },
          "minPrice": {
            "type": "string",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "default": "MXN"
          }
        }
      },
      "PublicOccurrence": {
        "type": "object",
        "required": [
          "id",
          "startsAt",
          "saleStatus"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "timezone": {
            "type": "string"
          },
          "saleStatus": {
            "type": "string",
            "enum": [
              "available",
              "upcoming",
              "ended",
              "sold_out",
              "cutoff"
            ]
          },
          "ticketInventories": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "ticketTypeId",
                "name",
                "price"
              ],
              "properties": {
                "ticketTypeId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "price": {
                  "type": "string"
                },
                "quantityAvailable": {
                  "type": "integer",
                  "nullable": true
                },
                "soldOut": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "CheckoutQuoteRequest": {
        "type": "object",
        "required": [
          "eventId",
          "items"
        ],
        "properties": {
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "occurrenceId": {
            "type": "string",
            "format": "uuid"
          },
          "discountCode": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "ticketTypeId",
                "quantity"
              ],
              "properties": {
                "ticketTypeId": {
                  "type": "string",
                  "format": "uuid"
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      },
      "CheckoutQuote": {
        "type": "object",
        "required": [
          "eventId",
          "buyerTotal"
        ],
        "properties": {
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "currency": {
            "type": "string",
            "default": "MXN"
          },
          "subtotal": {
            "type": "string"
          },
          "discountAmount": {
            "type": "string"
          },
          "feeTotal": {
            "type": "string"
          },
          "buyerTotal": {
            "type": "string"
          },
          "organizerNet": {
            "type": "string"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "NOT_FOUND",
                "enum": [
                  "NOT_FOUND",
                  "BAD_REQUEST",
                  "UNAUTHORIZED",
                  "FORBIDDEN",
                  "INTERNAL_ERROR"
                ]
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string",
                "example": "Check /openapi.json or /llms.txt for valid public endpoints."
              }
            }
          }
        }
      }
    }
  }
}
