{
  "openapi": "3.1.0",
  "info": {
    "title": "bianca.codes content API",
    "version": "1.0.0",
    "summary": "Read-only markdown, feed, and JSON endpoints for the bianca.codes blog.",
    "description": "A static, unauthenticated content surface for AI agents and crawlers. Every blog post is published both as HTML and as a markdown twin, either at an explicit `.md` URL or via `Accept: text/markdown` content negotiation on the canonical page URL. Start at `/llms.txt` to resolve a topic to a post slug. Content responses are static files: safe to cache and cheap to fetch.\n\n## Versioning\n\nThis surface is versioned with semver and is currently `1.0.0`, the `info.version` of this document. Compare it with the version you integrated against to detect drift.\n\n## Deprecation policy\n\nA breaking change to a documented endpoint means a new major version at a new path prefix; the previous major keeps working. Before an endpoint is withdrawn it is marked in two machine-readable ways: a `Deprecation` header (RFC 9745) carrying the date the deprecation was announced, and a `Sunset` header (RFC 8594) carrying the date it will stop responding. Sunset is never less than 180 days after Deprecation. Operations retired in this way are also flagged `deprecated: true` in this document before removal.",
    "license": {
      "name": "Content © Bianca W - all rights reserved",
      "url": "https://next.bianca.codes/terms/"
    },
    "contact": {
      "name": "Bianca W",
      "url": "https://next.bianca.codes/contact/"
    }
  },
  "servers": [
    {
      "url": "https://next.bianca.codes",
      "description": "Production - latest version"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "discovery",
      "description": "Indexes that map topics and slugs to content URLs."
    },
    {
      "name": "content",
      "description": "The posts and pages themselves, as markdown."
    },
    {
      "name": "feeds",
      "description": "Syndication and crawl formats."
    }
  ],
  "paths": {
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsIndex",
        "tags": [
          "discovery"
        ],
        "summary": "Get the llms.txt site index",
        "description": "The llmstxt.org index: a one-line site summary, when-to-use guidance, the topic taxonomy, and every published post with its excerpt and markdown URL. Fetch this first: it is the cheapest way to map a user question to a specific post slug.",
        "responses": {
          "200": {
            "description": "The llms.txt index. Served as `text/plain`; the body itself is markdown-formatted per the llmstxt.org convention.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "An llmstxt.org index document."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "tags": [
          "discovery"
        ],
        "summary": "Get this OpenAPI description",
        "description": "Returns this document, the machine-readable description of the content surface, so an agent can discover the available operations without out-of-band knowledge.",
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document describing this content API.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An OpenAPI 3.1 document."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/blog/{slug}.md": {
      "parameters": [
        {
          "name": "slug",
          "in": "path",
          "required": true,
          "description": "The post slug, as published in /llms.txt and /sitemap.xml. Lowercase, hyphen-separated, no file extension.",
          "schema": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "example": "hello-world"
        }
      ],
      "get": {
        "operationId": "getPostMarkdown",
        "tags": [
          "content"
        ],
        "summary": "Get a blog post as markdown",
        "description": "Returns the full text of one post as CommonMark, led by YAML frontmatter carrying `title`, `date`, optional `updated`, `tags`, and the `canonical` HTML URL. Check `date` before presenting a technique as current. The same document is served from the canonical page URL when the request carries `Accept: text/markdown`.",
        "responses": {
          "200": {
            "description": "The post as markdown, with YAML frontmatter.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark document. Post documents lead with YAML frontmatter."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/blog/index.md": {
      "get": {
        "operationId": "getBlogIndexMarkdown",
        "tags": [
          "discovery"
        ],
        "summary": "Get the blog index as markdown",
        "description": "Every published post as a markdown list (title, excerpt, and markdown URL), newest first. Narrower than /llms.txt: posts only, no topic taxonomy or usage guidance.",
        "responses": {
          "200": {
            "description": "The blog index as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark document. Post documents lead with YAML frontmatter."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/docs.md": {
      "get": {
        "operationId": "getDocsMarkdown",
        "tags": [
          "discovery"
        ],
        "summary": "Get the developer and agent documentation as markdown",
        "description": "The full guide to this content surface: both ways to fetch a post as markdown, the discovery endpoints, the JSON error envelope, and what this site is and is not a good source for. The same document is served from /docs/ when the request carries `Accept: text/markdown`.",
        "responses": {
          "200": {
            "description": "The developer documentation as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark document. Post documents lead with YAML frontmatter."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/about.md": {
      "get": {
        "operationId": "getAboutMarkdown",
        "tags": [
          "content"
        ],
        "summary": "Get the About page as markdown",
        "description": "Bianca's background, areas of expertise, and the stack this site covers. Use this to decide whether a question is in scope before searching individual posts.",
        "responses": {
          "200": {
            "description": "The About page as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark document. Post documents lead with YAML frontmatter."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/contact.md": {
      "get": {
        "operationId": "getContactMarkdown",
        "tags": [
          "content"
        ],
        "summary": "Get contact information as markdown",
        "description": "How to reach Bianca. There is no static contact form: /contact/ hosts an interactive chat assistant that answers questions about her background and can take a message.",
        "responses": {
          "200": {
            "description": "Contact information as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark document. Post documents lead with YAML frontmatter."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/index.md": {
      "get": {
        "operationId": "getHomeMarkdown",
        "tags": [
          "content"
        ],
        "summary": "Get the home page as markdown",
        "description": "A short markdown summary of the site with links onward to the blog, About, and Contact. Prefer /llms.txt when the goal is to enumerate content.",
        "responses": {
          "200": {
            "description": "The home page summary as markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark document. Post documents lead with YAML frontmatter."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": [
          "feeds"
        ],
        "summary": "Get the XML sitemap",
        "description": "Sitemaps 0.9 XML listing every canonical URL: static pages, topic hubs, posts, and pages. Only post entries carry a `lastmod` timestamp; every other entry is listed with `changefreq` and `priority` only, so `lastmod` detects changes to posts but not to the rest.",
        "responses": {
          "200": {
            "description": "A Sitemaps 0.9 urlset.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string",
                  "description": "Sitemaps 0.9 XML urlset."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/rss.xml": {
      "get": {
        "operationId": "getRssFeed",
        "tags": [
          "feeds"
        ],
        "summary": "Get the RSS 2.0 feed",
        "description": "RSS 2.0 feed of recent posts with titles, links, publication dates, and descriptions. Use it to poll for new content; use /sitemap.xml for the complete URL set.",
        "responses": {
          "200": {
            "description": "An RSS 2.0 feed document.",
            "content": {
              "application/rss+xml": {
                "schema": {
                  "type": "string",
                  "description": "RSS 2.0 XML."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt",
        "tags": [
          "discovery"
        ],
        "summary": "Get the robots.txt crawl policy",
        "description": "The crawl policy for this site. All user agents are allowed, and the sitemap location is advertised here.",
        "responses": {
          "200": {
            "description": "The robots.txt policy.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "A robots.txt policy document."
                }
              }
            }
          },
          "404": {
            "description": "No such path. The body lists the sitemap, llms.txt, and blog index so an agent can recover.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Human- and agent-readable recovery guidance."
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "Structured error body. Returned as JSON when the client accepts JSON, and as markdown prose otherwise. `ok`, `error`, `message`, and `docs` are always present; `hint` appears when the failure has an actionable remedy.",
        "required": [
          "ok",
          "error",
          "message",
          "docs"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false,
            "description": "Always false on an error response. Branch on this to detect failure."
          },
          "error": {
            "type": "string",
            "description": "Stable, machine-readable error code in snake_case.",
            "examples": [
              "not_found"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation of what went wrong. Show this to a person, never the `error` code."
          },
          "hint": {
            "type": "string",
            "description": "How to resolve the error, when a remedy exists. Absent when there is no useful action for the caller to take.",
            "examples": [
              "Resolve a URL from the sitemap or llms.txt."
            ]
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "URL of documentation explaining how to resolve this error."
          }
        }
      }
    }
  }
}