{
  "openapi": "3.1.0",
  "info": {
    "title": "Surify public content API",
    "version": "1.0.0",
    "summary": "Acceso de sólo lectura y sin autenticación a las páginas públicas de surify.mx en Markdown, más llms.txt, el sitemap y este documento.",
    "description": "Surify es el CRM para agentes de seguros y promotorías en México. Esta especificación describe lo único que es público y sin autenticación: el contenido del sitio en Markdown (por negociación de `Accept` o con el sufijo `.md`), el índice llms.txt, el texto completo llms-full.txt, el sitemap y este documento.\n\n**No hay API del CRM.** Ningún endpoint crea cuentas, contactos, pólizas o mensajes, ni lee los datos de una organización; la aplicación web (app.surify.mx) usa una API privada con sesión que no está documentada ni soportada para terceros.\n\nAutenticación: ninguna. Límite de peticiones: no publicado; se pide uso razonable (una petición por segundo basta para leer todo el sitio) y un `User-Agent` que identifique a tu agente o producto. Las respuestas Markdown son `Cache-Control: private, no-store`; cáchalas de tu lado.\n\nErrores: toda ruta bajo `/api/` que no exista responde `404` en JSON con la forma `ApiError` (`error.code`, `error.message`, `error.hint`, `error.docs`); un `Accept` que no encaje con una página responde `406`, en JSON si el cliente acepta `application/json`.\n\nGuía para desarrolladores y agentes: https://surify.mx/desarrolladores. Integraciones: hola@surify.mx.",
    "termsOfService": "https://surify.mx/terminos",
    "license": {
      "name": "Términos de servicio de Surify",
      "url": "https://surify.mx/terminos"
    },
    "contact": {
      "name": "Surify",
      "url": "https://surify.mx/contacto",
      "email": "hola@surify.mx"
    }
  },
  "externalDocs": {
    "description": "Guía para desarrolladores y agentes",
    "url": "https://surify.mx/desarrolladores"
  },
  "servers": [
    {
      "url": "https://surify.mx",
      "description": "Producción"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "pages",
      "description": "Páginas públicas del sitio, servidas como HTML o Markdown según `Accept`, o siempre como Markdown con el sufijo `.md`."
    },
    {
      "name": "discovery",
      "description": "Índices legibles por máquina: llms.txt, el texto completo, el sitemap, robots.txt y este documento."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getHome",
        "tags": [
          "pages"
        ],
        "summary": "Portada (HTML o Markdown)",
        "description": "La portada: qué es Surify, para quién, funciones, módulo de Promotorías, cómo empezar, precios de los cuatro planes y las preguntas más consultadas. Negociación de contenido según acceptmarkdown.com: `Accept` se ordena por q-value y especificidad, se respeta `q=0` y `text/markdown` gana cuando queda por encima de `text/html`. Sin cabecera `Accept`, o con `*/*`, la respuesta es HTML. El cuerpo Markdown es el mismo contenido que la página HTML, sin navegación y con enlaces absolutos.",
        "responses": {
          "200": {
            "description": "La página en la representación negociada.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              },
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          }
        }
      }
    },
    "/{page}": {
      "get": {
        "operationId": "getPage",
        "tags": [
          "pages"
        ],
        "summary": "Una página pública (HTML o Markdown)",
        "description": "Una de las páginas fijas: demos, aseguradoras, glosario, preguntas frecuentes, índice de la documentación, nosotros, contacto, desarrolladores, términos y aviso de privacidad. Negociación de contenido según acceptmarkdown.com: `Accept` se ordena por q-value y especificidad, se respeta `q=0` y `text/markdown` gana cuando queda por encima de `text/html`. Sin cabecera `Accept`, o con `*/*`, la respuesta es HTML. El cuerpo Markdown es el mismo contenido que la página HTML, sin navegación y con enlaces absolutos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en la representación negociada.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              },
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          }
        }
      }
    },
    "/{page}.md": {
      "get": {
        "operationId": "getPageMarkdown",
        "tags": [
          "pages"
        ],
        "summary": "Una página pública, siempre en Markdown",
        "description": "El gemelo Markdown de una página fija sin importar la cabecera `Accept`, siguiendo la convención llms.txt de añadir `.md`. `index.md` es la portada.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageSlugWithIndex"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en Markdown.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/docs/{section}": {
      "get": {
        "operationId": "getDocsSection",
        "tags": [
          "pages"
        ],
        "summary": "Una sección de la documentación (HTML o Markdown)",
        "description": "El índice de una sección de la documentación con sus artículos y descripciones. Negociación de contenido según acceptmarkdown.com: `Accept` se ordena por q-value y especificidad, se respeta `q=0` y `text/markdown` gana cuando queda por encima de `text/html`. Sin cabecera `Accept`, o con `*/*`, la respuesta es HTML. El cuerpo Markdown es el mismo contenido que la página HTML, sin navegación y con enlaces absolutos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/DocSection"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en la representación negociada.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              },
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          }
        }
      }
    },
    "/docs/{section}/{slug}": {
      "get": {
        "operationId": "getDoc",
        "tags": [
          "pages"
        ],
        "summary": "Un artículo de la documentación (HTML o Markdown)",
        "description": "Un artículo de la documentación del producto. Las parejas sección/slug válidas están en llms.txt y en el sitemap. Negociación de contenido según acceptmarkdown.com: `Accept` se ordena por q-value y especificidad, se respeta `q=0` y `text/markdown` gana cuando queda por encima de `text/html`. Sin cabecera `Accept`, o con `*/*`, la respuesta es HTML. El cuerpo Markdown es el mismo contenido que la página HTML, sin navegación y con enlaces absolutos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/DocSection"
          },
          {
            "$ref": "#/components/parameters/DocSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en la representación negociada.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              },
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          }
        }
      }
    },
    "/docs/{section}/{slug}.md": {
      "get": {
        "operationId": "getDocMarkdown",
        "tags": [
          "pages"
        ],
        "summary": "Un artículo de la documentación, siempre en Markdown",
        "description": "El gemelo Markdown de un artículo de la documentación sin importar la cabecera `Accept`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/DocSection"
          },
          {
            "$ref": "#/components/parameters/DocSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en Markdown.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/aseguradoras/{slug}": {
      "get": {
        "operationId": "getInsurer",
        "tags": [
          "pages"
        ],
        "summary": "Una aseguradora reconocida por la IA (HTML o Markdown)",
        "description": "Qué captura la IA de las carátulas de esa aseguradora, en cuatro pasos, con sus ramos habituales y preguntas frecuentes. Negociación de contenido según acceptmarkdown.com: `Accept` se ordena por q-value y especificidad, se respeta `q=0` y `text/markdown` gana cuando queda por encima de `text/html`. Sin cabecera `Accept`, o con `*/*`, la respuesta es HTML. El cuerpo Markdown es el mismo contenido que la página HTML, sin navegación y con enlaces absolutos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/InsurerSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en la representación negociada.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              },
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          }
        }
      }
    },
    "/aseguradoras/{slug}.md": {
      "get": {
        "operationId": "getInsurerMarkdown",
        "tags": [
          "pages"
        ],
        "summary": "Una aseguradora, siempre en Markdown",
        "description": "El gemelo Markdown de la página de una aseguradora sin importar la cabecera `Accept`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/InsurerSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en Markdown.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/demos/{flow}": {
      "get": {
        "operationId": "getDemo",
        "tags": [
          "pages"
        ],
        "summary": "Un demo en video (HTML o Markdown)",
        "description": "Un recorrido grabado del producto, con su transcripción narrada y el segundo en que empieza cada capítulo. Negociación de contenido según acceptmarkdown.com: `Accept` se ordena por q-value y especificidad, se respeta `q=0` y `text/markdown` gana cuando queda por encima de `text/html`. Sin cabecera `Accept`, o con `*/*`, la respuesta es HTML. El cuerpo Markdown es el mismo contenido que la página HTML, sin navegación y con enlaces absolutos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/DemoFlow"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en la representación negociada.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              },
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          }
        }
      }
    },
    "/demos/{flow}.md": {
      "get": {
        "operationId": "getDemoMarkdown",
        "tags": [
          "pages"
        ],
        "summary": "Un demo en video, siempre en Markdown",
        "description": "El gemelo Markdown de la página de un demo, con la transcripción, sin importar la cabecera `Accept`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/DemoFlow"
          }
        ],
        "responses": {
          "200": {
            "description": "La página en Markdown.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": [
          "discovery"
        ],
        "summary": "Índice llms.txt para agentes",
        "description": "El índice llmstxt.org: qué es el sitio, cuándo usarlo, precios y enlaces con notas a cada página pública, artículo de la documentación y aseguradora. Se regenera en cada despliegue.",
        "responses": {
          "200": {
            "description": "El archivo llms.txt.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          },
          "4XX": {
            "description": "Error de cliente (por ejemplo, un método distinto de GET o HEAD): la página HTML de error.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullTxt",
        "tags": [
          "discovery"
        ],
        "summary": "Todas las páginas públicas en un solo Markdown",
        "description": "Portada, documentación completa, glosario, aseguradoras, demos con transcripción y textos legales concatenados en Markdown, separados por reglas horizontales. Se regenera en cada despliegue.",
        "responses": {
          "200": {
            "description": "El texto completo.",
            "headers": {
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          },
          "4XX": {
            "description": "Error de cliente (por ejemplo, un método distinto de GET o HEAD): la página HTML de error.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": [
          "discovery"
        ],
        "summary": "Sitemap XML",
        "description": "Todas las URL públicas indexables: páginas, demos, aseguradoras y artículos de la documentación.",
        "responses": {
          "200": {
            "description": "El sitemap.",
            "content": {
              "application/xml": {
                "schema": {
                  "$ref": "#/components/schemas/XmlDocument"
                }
              }
            }
          },
          "4XX": {
            "description": "Error de cliente (por ejemplo, un método distinto de GET o HEAD): la página HTML de error.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt",
        "tags": [
          "discovery"
        ],
        "summary": "robots.txt",
        "description": "Reglas de rastreo (los crawlers de IA están permitidos por nombre) y la ubicación del sitemap.",
        "responses": {
          "200": {
            "description": "El archivo robots.txt.",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PlainText"
                }
              }
            }
          },
          "4XX": {
            "description": "Error de cliente (por ejemplo, un método distinto de GET o HEAD): la página HTML de error.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDocument",
        "tags": [
          "discovery"
        ],
        "summary": "Este documento OpenAPI",
        "description": "Esta especificación, como JSON (OpenAPI 3.1.0).",
        "responses": {
          "200": {
            "description": "El documento OpenAPI.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDocument"
                }
              }
            }
          },
          "4XX": {
            "description": "Error de cliente (por ejemplo, un método distinto de GET o HEAD): la página HTML de error.",
            "content": {
              "text/html": {
                "schema": {
                  "$ref": "#/components/schemas/HtmlDocument"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "PageSlug": {
        "name": "page",
        "in": "path",
        "required": true,
        "description": "La página pública a leer.",
        "schema": {
          "type": "string",
          "enum": [
            "demos",
            "aseguradoras",
            "glosario",
            "preguntas-frecuentes",
            "docs",
            "nosotros",
            "contacto",
            "desarrolladores",
            "aviso-privacidad",
            "terminos"
          ]
        }
      },
      "PageSlugWithIndex": {
        "name": "page",
        "in": "path",
        "required": true,
        "description": "La página pública a leer; `index` es la portada.",
        "schema": {
          "type": "string",
          "enum": [
            "index",
            "demos",
            "aseguradoras",
            "glosario",
            "preguntas-frecuentes",
            "docs",
            "nosotros",
            "contacto",
            "desarrolladores",
            "aviso-privacidad",
            "terminos"
          ]
        }
      },
      "DocSection": {
        "name": "section",
        "in": "path",
        "required": true,
        "description": "La sección de la documentación.",
        "schema": {
          "type": "string",
          "enum": [
            "primeros-pasos",
            "contactos-y-ventas",
            "polizas",
            "whatsapp-y-email",
            "analitica",
            "promotorias",
            "cuenta",
            "soporte"
          ]
        }
      },
      "DocSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "El artículo dentro de la sección, como lo lista llms.txt.",
        "schema": {
          "type": "string",
          "enum": [
            "introduccion",
            "crear-cuenta",
            "importar-contactos",
            "equipo-y-roles",
            "expediente-del-cliente",
            "pipeline-de-ventas",
            "agente-ia",
            "registrar-una-poliza",
            "lectura-de-caratulas-con-ia",
            "programa-de-pagos",
            "renovaciones-y-recordatorios",
            "siniestros",
            "vincular-whatsapp",
            "chat",
            "email",
            "constructor-de-widgets",
            "reportes",
            "como-funciona",
            "alta-de-agentes",
            "agentes-y-subcuentas",
            "cartera-consolidada",
            "facturacion-centralizada",
            "planes-y-limites",
            "pagos-y-suscripcion",
            "seguridad-y-privacidad",
            "soporte"
          ]
        }
      },
      "InsurerSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "La aseguradora, por su slug.",
        "schema": {
          "type": "string",
          "enum": [
            "gnp",
            "axa",
            "metlife",
            "mapfre",
            "bupa",
            "qualitas",
            "seguros-monterrey",
            "allianz",
            "hdi",
            "chubb",
            "inbursa",
            "ana-seguros",
            "seguros-atlas",
            "skandia",
            "el-potosi",
            "plan-seguro"
          ]
        }
      },
      "DemoFlow": {
        "name": "flow",
        "in": "path",
        "required": true,
        "description": "El demo grabado.",
        "schema": {
          "type": "string",
          "enum": [
            "tour",
            "promotoria"
          ]
        }
      }
    },
    "headers": {
      "Vary": {
        "description": "Siempre incluye `Accept`: la representación depende de la cabecera de la petición.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "MarkdownDocument": {
        "type": "string",
        "contentMediaType": "text/markdown",
        "description": "Un documento Markdown: un título H1, el contenido de la página con enlaces absolutos y una última línea con la URL de la página."
      },
      "HtmlDocument": {
        "type": "string",
        "contentMediaType": "text/html",
        "description": "La página HTML, tal como la recibe un navegador."
      },
      "NotFoundMarkdown": {
        "type": "string",
        "contentMediaType": "text/markdown",
        "description": "Una explicación en Markdown del 404 con enlaces a la portada, llms.txt, el sitemap y la documentación."
      },
      "NotAcceptable": {
        "type": "string",
        "contentMediaType": "text/plain",
        "description": "Un cuerpo de texto plano con las representaciones disponibles para la URL."
      },
      "ApiError": {
        "type": "object",
        "description": "La forma de todo error en JSON del sitio: un código estable, un mensaje y una pista.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "docs"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "not_found",
                  "not_acceptable",
                  "method_not_allowed",
                  "invalid_body",
                  "validation_error",
                  "upstream_error",
                  "server_misconfiguration"
                ],
                "description": "Código estable para que un programa decida."
              },
              "message": {
                "type": "string",
                "description": "Qué pasó, para una persona."
              },
              "hint": {
                "type": "string",
                "description": "Qué hacer al respecto."
              },
              "available": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "En un 406, las representaciones que sí existen para la URL."
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Dónde está documentada la superficie pública."
              }
            }
          }
        }
      },
      "XmlDocument": {
        "type": "string",
        "contentMediaType": "application/xml"
      },
      "PlainText": {
        "type": "string",
        "contentMediaType": "text/plain"
      },
      "OpenApiDocument": {
        "type": "object",
        "description": "Un documento OpenAPI 3.1.",
        "required": [
          "openapi",
          "info",
          "paths"
        ],
        "properties": {
          "openapi": {
            "type": "string",
            "const": "3.1.0"
          },
          "info": {
            "type": "object"
          },
          "paths": {
            "type": "object"
          }
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "No existe esa página. Markdown si el cliente pidió Markdown, HTML en otro caso.",
        "headers": {
          "Vary": {
            "$ref": "#/components/headers/Vary"
          }
        },
        "content": {
          "text/markdown": {
            "schema": {
              "$ref": "#/components/schemas/NotFoundMarkdown"
            }
          },
          "text/html": {
            "schema": {
              "$ref": "#/components/schemas/HtmlDocument"
            }
          }
        }
      },
      "NotAcceptable": {
        "description": "La cabecera `Accept` rechaza todas las representaciones que la URL puede producir. JSON si el cliente acepta `application/json`; texto plano si no.",
        "headers": {
          "Vary": {
            "$ref": "#/components/headers/Vary"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          },
          "text/plain": {
            "schema": {
              "$ref": "#/components/schemas/NotAcceptable"
            }
          }
        }
      }
    }
  }
}