How we built Kreyo's docs: Starlight, OpenAPI, and llms.txt

For a product with no UI, documentation is the product surface. It needs to be indexable by Google, indexable by LLMs, and readable by humans, ideally all three without separate maintenance. Here's the stack we landed on, what we tried first that didn't work, and what the llms.txt file actually does.

Technical documentation and developer tools
Photo by Krismas on Unsplash

The hypothesis: docs as a primary acquisition channel

Most B2B SaaS treats documentation as a support cost — something you write so developers stop emailing you. We made the opposite bet: documentation is the primary acquisition channel.

The reasoning: Kreyo's target buyers are tech leads and developers who will Google for the specific problem before they contact a vendor. If the docs are the best explanation of VeriFactu signing, FacturaE XAdES, or NIF validation on the internet, they will find Kreyo before they find anyone else. The docs don't just support the product — they are the front door.

This changes the requirements. The docs need to:


What we tried first: Scalar

The initial docs were Scalar — an open-source interactive API reference renderer that takes an OpenAPI spec and produces a rich single-page experience. Scalar is excellent for interactive API exploration. It wasn't right for Kreyo for two reasons.

Indexability. Scalar renders as a client-side SPA. The HTML served to a crawler is essentially empty — the content loads via JavaScript. Google can render JavaScript, but it does so on a delayed crawl queue, which means new content gets indexed slowly and the cached version often lags behind updates. For documentation, this is a meaningful SEO penalty.

Visual coherence. The Scalar UI is polished but opinionated. Matching it to Kreyo's design tokens — the color scheme, typography, spacing — required overriding CSS at a level that felt like fighting the tool rather than using it. Every update to Scalar's package risked breaking the overrides.

Both problems pointed in the same direction: a static site generator that produces HTML at build time.


Why Starlight

Starlight is a documentation framework built on Astro. It produces static HTML at build time. Content is Markdown or MDX. The output is indexed as plain HTML by crawlers, with no JavaScript required for initial render.

The reasons Starlight won over alternatives:

Native OpenAPI plugin

starlight-openapi takes an OpenAPI 3.x spec and generates a documentation page per endpoint at build time. The generated pages are HTML — linkable, indexable, diffable. When the API spec changes, npm run build regenerates the pages. No manual sync.

Native llms.txt support

starlight-llms-txt generates llms.txt and llms-full.txt from the same Markdown content used for the human-readable docs. No separate file to maintain.

Override-friendly chrome

Starlight allows component overrides via Astro's component slot system. Replacing the default Header and Footer with Kreyo's HTML components required replacing two files and pointing Starlight at them in the config. The visual result is a docs site that is indistinguishable from the landing pages — same font, same nav, same color scheme, same footer.


The visual integration

Kreyo's design tokens live in /tokens.css — a single file that defines custom properties for colors, spacing, and typography used across all landing pages, the blog, and the docs. Starlight's CSS variables are mapped to Kreyo's tokens in a single override file:

/* starlight-overrides.css */
:root {
  --sl-color-accent: var(--kr-color-accent);
  --sl-color-accent-high: var(--kr-color-accent-high);
  --sl-font: var(--kr-font-family);
  --sl-text-code: var(--kr-font-mono);
}

The data-product attribute system that cascades violet for VeriFactu content, teal for Extract, and so on — also works in the docs. Each product's documentation section carries the correct accent automatically.

The Starlight Header component is replaced by the same nav HTML used on the landing pages. Same markup, same behavior, same keyboard navigation. No separate nav to maintain.


OpenAPI as the single source of truth

ASP.NET Core generates the OpenAPI 3.x specification from the controller attributes at startup. The spec is served at /openapi/v1.json and written to a static file during the build step. Starlight consumes the static file.

The controller annotations that drive the spec are minimal — the goal is accurate schema definitions, not marketing text in the comments:

[HttpPost("sign")]
[ProducesResponseType<FacturaeSignResponse>(200)]
[ProducesResponseType<KreyoError>(400)]
[ProducesResponseType<KreyoError>(422)]
public async Task<IActionResult> Sign([FromBody] InvoiceData request) { ... }

The advantage of this model: the docs cannot go stale. If an endpoint is removed, its page disappears on the next build. If a request schema changes, the generated schema documentation reflects it immediately. The "docs are outdated" problem (endemic to hand-written API docs) doesn't exist here.


llms.txt and llms-full.txt

The llms.txt specification (proposed by Jeremy Howard) defines a convention for exposing a plain-text or Markdown version of a site's content at a predictable URL, optimized for LLM context windows rather than browser rendering.

Two files:

The practical use case: a developer integrating Kreyo's VeriFactu API pastes llms-full.txt into their AI coding assistant. The assistant now has the complete API reference in context and can generate correct, up-to-date integration code without hallucinating endpoint paths or request schemas.

starlight-llms-txt generates both files automatically from the same Markdown source. They're available at https://www.kreyo.io/llms.txt and https://www.kreyo.io/llms-full.txt.


The build pipeline

The full build process:

  1. ASP.NET Core runs with --environment Build, which triggers a startup hook that writes the OpenAPI spec to docs/src/openapi/v1.json.
  2. npm run build in the docs/ directory runs the Starlight/Astro build, consuming the OpenAPI spec and all Markdown content.
  3. The output goes to wwwroot/docs/, served by UseStaticFiles alongside the landing pages and blog.

The deployment is a single container push. The docs, landing pages, blog, and API all ship together. There's no separate docs deployment, no CDN configuration for a separate domain, no webhook to trigger a Netlify rebuild.


What this looks like for developers

A developer integrating Kreyo's API has three paths:

All three stay synchronized automatically. The maintenance cost of the docs system is effectively zero once it's set up — the cost is in writing good content, not in keeping systems in sync.