{
  "openapi": "3.1.0",
  "info": {
    "title": "Angel Cruz · Software Developer — API de contenido",
    "version": "1.0.0",
    "summary": "Endpoints públicos de solo lectura del sitio angelcruz.dev.",
    "description": "Superficie de solo lectura de un sitio de contenido. Todos los endpoints son GET, ninguno requiere autenticación y ninguno muta nada.\n\nAdemás de las rutas descritas aquí, **cualquier página del sitio devuelve markdown** de dos formas equivalentes: enviando la cabecera `Accept: text/markdown`, o añadiendo el sufijo `.md` a su ruta. Las respuestas markdown llevan `Vary: Accept` y un header `Link` con el `rel=\"canonical\"` hacia el gemelo HTML.\n\nEl inventario completo de superficies de discovery se anuncia en el header `Link` (RFC 8288) de toda respuesta y en `/.well-known/api-catalog` (RFC 9727).",
    "contact": {
      "name": "Angel Cruz",
      "email": "hola@angelcruz.dev",
      "url": "https://www.angelcruz.dev/contacto"
    },
    "license": {
      "name": "Contenido con licencia de uso descrita en el aviso legal",
      "url": "https://www.angelcruz.dev/aviso-legal"
    }
  },
  "servers": [
    {
      "url": "https://www.angelcruz.dev",
      "description": "Producción"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Discovery",
      "description": "Índices y catálogos para descubrir el corpus."
    },
    {
      "name": "Contenido",
      "description": "Artículos y notas en markdown."
    },
    {
      "name": "Feeds",
      "description": "Sindicación RSS."
    },
    {
      "name": "Entidades",
      "description": "EntityMap: entidades del sitio con su evidencia."
    }
  ],
  "paths": {
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsIndex",
        "tags": [
          "Discovery"
        ],
        "summary": "Índice del sitio para LLMs",
        "description": "Índice estilo llmstxt.org: una línea por página y por artículo, con título, URL y descripción. Es el punto de entrada recomendado para recorrer el sitio.",
        "responses": {
          "200": {
            "description": "Índice en texto plano.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullCorpus",
        "tags": [
          "Discovery"
        ],
        "summary": "Corpus completo en un solo documento",
        "description": "Como /llms.txt pero con el cuerpo íntegro de cada artículo en markdown inline. Un único fetch para todo el contenido; pesa varios cientos de KB.",
        "responses": {
          "200": {
            "description": "Corpus completo en texto plano.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.md": {
      "get": {
        "operationId": "getSitemapMarkdown",
        "tags": [
          "Discovery"
        ],
        "summary": "Mapa del sitio en markdown",
        "description": "Versión jerárquica y legible del sitemap, agrupada por sección. Pensada para orientarse, no para rastrear (para eso está el XML).",
        "responses": {
          "200": {
            "description": "Mapa del sitio en markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemapIndex",
        "tags": [
          "Discovery"
        ],
        "summary": "Sitemap XML (índice)",
        "description": "Sitemap estándar con las páginas estáticas y las categorías.",
        "responses": {
          "200": {
            "description": "Documento sitemap XML.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap-posts.xml": {
      "get": {
        "operationId": "getSitemapPosts",
        "tags": [
          "Discovery"
        ],
        "summary": "Sitemap XML de artículos",
        "description": "Solo los artículos del blog, cada uno con su `<lastmod>`.",
        "responses": {
          "200": {
            "description": "Documento sitemap XML.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap-categories.xml": {
      "get": {
        "operationId": "getSitemapCategories",
        "tags": [
          "Discovery"
        ],
        "summary": "Sitemap XML de categorías",
        "responses": {
          "200": {
            "description": "Documento sitemap XML.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap-pages.xml": {
      "get": {
        "operationId": "getSitemapPages",
        "tags": [
          "Discovery"
        ],
        "summary": "Sitemap XML de páginas estáticas",
        "responses": {
          "200": {
            "description": "Documento sitemap XML.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/AGENTS.md": {
      "get": {
        "operationId": "getAgentsGuide",
        "tags": [
          "Discovery"
        ],
        "summary": "Guía de consumo para agentes",
        "description": "Qué publica el sitio, en qué formatos y bajo qué condiciones. Es también el documento al que apunta el campo `type` de los errores Problem Details.",
        "responses": {
          "200": {
            "description": "Guía en markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobots",
        "tags": [
          "Discovery"
        ],
        "summary": "Política de rastreo",
        "description": "Permite explícitamente a GPTBot, ClaudeBot, PerplexityBot, Google-Extended y CCBot, entre otros.",
        "responses": {
          "200": {
            "description": "Directivas robots.txt.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/api-catalog": {
      "get": {
        "operationId": "getApiCatalog",
        "tags": [
          "Discovery"
        ],
        "summary": "Catálogo de recursos (RFC 9727)",
        "description": "Linkset con las superficies públicas del sitio, incluida esta descripción OpenAPI en `service-desc`.",
        "responses": {
          "200": {
            "description": "Linkset JSON (RFC 9264).",
            "content": {
              "application/linkset+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/agent-skills/index.json": {
      "get": {
        "operationId": "getAgentSkillsIndex",
        "tags": [
          "Discovery"
        ],
        "summary": "Índice de Agent Skills",
        "description": "Skills publicadas según el RFC de agentskills.io v0.2.0, cada una con su digest sha256.",
        "responses": {
          "200": {
            "description": "Índice de skills en JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDescription",
        "tags": [
          "Discovery"
        ],
        "summary": "Esta descripción OpenAPI",
        "responses": {
          "200": {
            "description": "Documento OpenAPI 3.1.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/post/{slug}.md": {
      "get": {
        "operationId": "getPostMarkdown",
        "tags": [
          "Contenido"
        ],
        "summary": "Artículo en markdown",
        "description": "Devuelve el markdown fuente del artículo, sin conversión desde HTML, con un pie de discovery. Equivale a pedir `/post/{slug}` con `Accept: text/markdown`. Los slugs válidos se listan en /llms.txt y /sitemap-posts.xml.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug del artículo, sin la extensión.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            },
            "example": "content-negotiation-agentes-ia"
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown fuente del artículo.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "El recurso pedido no existe o no puede representarse. El cuerpo sigue Problem Details (RFC 9457) e incluye `code`, `resolution` y `discovery`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/lab/{slug}.md": {
      "get": {
        "operationId": "getTilMarkdown",
        "tags": [
          "Contenido"
        ],
        "summary": "TIL en markdown",
        "description": "Una nota breve (TIL) en markdown. Los TIL no tienen página HTML propia: su gemelo canónico es un ancla dentro de /lab. Los slugs válidos se listan en /til.md y /llms.txt.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug del TIL, sin la extensión.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown de la nota.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "El recurso pedido no existe o no puede representarse. El cuerpo sigue Problem Details (RFC 9457) e incluye `code`, `resolution` y `discovery`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/til.md": {
      "get": {
        "operationId": "getAllTilMarkdown",
        "tags": [
          "Contenido"
        ],
        "summary": "Todos los TIL en markdown",
        "description": "El conjunto completo de notas breves, sin el chrome de la página.",
        "responses": {
          "200": {
            "description": "Notas en markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/feed.xml": {
      "get": {
        "operationId": "getArticlesFeed",
        "tags": [
          "Feeds"
        ],
        "summary": "RSS de artículos",
        "responses": {
          "200": {
            "description": "Feed RSS 2.0.",
            "content": {
              "application/rss+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/til.xml": {
      "get": {
        "operationId": "getTilFeed",
        "tags": [
          "Feeds"
        ],
        "summary": "RSS de TIL",
        "description": "Feed separado del de artículos, a propósito: quien sigue las notas breves no necesariamente quiere los artículos largos.",
        "responses": {
          "200": {
            "description": "Feed RSS 2.0.",
            "content": {
              "application/rss+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/entitymap.json": {
      "get": {
        "operationId": "getEntityMap",
        "tags": [
          "Entidades"
        ],
        "summary": "EntityMap del sitio",
        "description": "Entidades del sitio según EntityMap v1.0: qué es cada una, la evidencia que la respalda y a quién se atribuye. /entitymap.html es el mismo documento en forma legible, con JSON-LD por entidad.",
        "responses": {
          "200": {
            "description": "Documento EntityMap en JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "Error en formato Problem Details (RFC 9457), con tres extensiones propias: `code`, `resolution` y `discovery`.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "instance",
          "code",
          "resolution"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI que identifica el tipo de problema; apunta a la sección correspondiente de /AGENTS.md."
          },
          "title": {
            "type": "string",
            "description": "Resumen corto y estable del tipo de error."
          },
          "status": {
            "type": "integer",
            "description": "Código HTTP."
          },
          "detail": {
            "type": "string",
            "description": "Qué falló en esta petición concreta."
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "description": "URI del recurso pedido."
          },
          "code": {
            "type": "string",
            "description": "Código estable, apto para hacer switch sin parsear prosa.",
            "enum": [
              "markdown_not_found",
              "markdown_unavailable"
            ]
          },
          "resolution": {
            "type": "string",
            "description": "Qué hacer a continuación."
          },
          "discovery": {
            "type": "array",
            "description": "Endpoints donde redescubrir el contenido publicado.",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Guía completa de consumo para agentes",
    "url": "https://www.angelcruz.dev/AGENTS.md"
  }
}